fix: preserve managed sessions in sandbox work folders
Keep managed account session fingerprints stable across temporary homes and copy refreshed native Codex credentials through the actual CLI home. Handle child stdin EPIPE without losing the authoritative exit result. Verify the existing preview database upgrade and refresh the dedicated runner lock. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
3e0dae9952
commit
3bcbd7dab9
|
|
@ -316,7 +316,7 @@ COPY packages ./packages
|
|||
COPY server/package.json ./server/package.json
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY cli/package.json ./cli/package.json
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=3d4577e64214452e3c1f65e32c9223cf34d17d5cf84aa4658f0fefa158fe6de5
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=6e107822490361b4e46084c98b0c8ac11434bd3388186799d4cf3fe795481e07
|
||||
RUN printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \
|
||||
&& sha256sum -c /tmp/provider-lock.sha256 \
|
||||
&& pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# Work-folder preview migration reconciliation
|
||||
|
||||
The work-folder branch previously assigned `0275_sandbox_work_folders` after the
|
||||
mainline `0274_agent_chat` migration. Mainline subsequently assigned 0275 to
|
||||
iMessage support and 0276 to AI connections. Both mainline migrations must remain
|
||||
in the sequence and execute on existing preview databases.
|
||||
|
||||
The merged sequence preserves mainline SQL, journal entries and snapshots through
|
||||
0276. `pnpm --filter @paperclipai/db generate --name sandbox_work_folders` generates
|
||||
0277 and its snapshot from the complete merged schema. Its six work-folder table
|
||||
definitions equal the prior generated work-folder snapshot. The reviewed
|
||||
idempotent work-folder SQL is retained byte-for-byte at the generated 0277 path,
|
||||
including guards for preview tables, constraints, indexes and missing sync columns.
|
||||
Its SHA-256 remains
|
||||
`0c62020d37107e3b2284823c2a297a8dd4b505cbdd3993a0e757c93ac8f3a257`.
|
||||
No existing journal timestamp or database migration row is rewritten.
|
||||
|
||||
The application migrator identifies applied SQL by hash. Consequently a database
|
||||
that already ran the old 0275 work-folder SQL recognizes the renamed 0277 hash,
|
||||
while independently applying the missing mainline 0275 and 0276 migrations. An
|
||||
older preview with the four separate work-folder migrations applies the
|
||||
idempotent 0277 consolidation as well. A timestamp-only migrator cannot establish
|
||||
this behavior and is not an acceptable substitute.
|
||||
|
||||
`packages/db/src/work-folder-preview-migration.test.ts` reconstructs both the exact
|
||||
f3c67d50 history and the exact 64814d5 history from pinned SQL hashes and journals.
|
||||
It seeds legacy/native tasks and provider sessions, files, trash, operation
|
||||
receipts, pending uploads and unpushed checkpoint references before upgrading.
|
||||
It checks data and original journal preservation, both new mainline schemas,
|
||||
six-table schema equivalence, uniqueness/tenant constraints and idempotent replay.
|
||||
The 64814d5 history is also the migration history used by the later d4ae130 app.
|
||||
`migration-snapshot-drift.test.ts` independently compares the generated snapshot
|
||||
with the merged schema.
|
||||
|
||||
## Cloud deployment requirement
|
||||
|
||||
Cloud's ordered-prefix compatibility guard must not be bypassed. Inserting the
|
||||
two missing mainline migrations before the renamed work-folder migration changes
|
||||
the ordered manifest even though the work-folder SQL hash is retained. Before
|
||||
deployment, add a reviewed, exact-version transition to Cloud's consolidation
|
||||
catalog for the actual deployed source schema marker and final target package,
|
||||
including their full manifest fingerprints and required SQL hashes. The current
|
||||
staging markers are the prior 08dd27e and 64814d5 package versions; an additional
|
||||
f3/f4-to-target entry is necessary only for a direct upgrade from those versions.
|
||||
The final target cannot be bound until its candidate revision and artifacts exist.
|
||||
Snapshot, migration success, authenticated readiness and post-migration data
|
||||
checks remain mandatory. This document does not authorize deployment or merging.
|
||||
|
|
@ -29,7 +29,7 @@ COPY cli/package.json ./cli/package.json
|
|||
# The complete resolved lock (including transitive integrity hashes) is reviewed.
|
||||
# Reject registry-time drift BEFORE installing packages or running lifecycle code.
|
||||
# Refresh this digest together with source/provider dependency changes.
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=3d4577e64214452e3c1f65e32c9223cf34d17d5cf84aa4658f0fefa158fe6de5
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=6e107822490361b4e46084c98b0c8ac11434bd3388186799d4cf3fe795481e07
|
||||
RUN printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \
|
||||
&& sha256sum -c /tmp/provider-lock.sha256 \
|
||||
&& pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...'
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ overrides:
|
|||
rollup: '>=4.59.0'
|
||||
react: ^19.2.8
|
||||
react-dom: ^19.2.8
|
||||
'@codemirror/state': ^6.7.2
|
||||
'@codemirror/view': ^6.43.11
|
||||
|
||||
patchedDependencies:
|
||||
'@agentclientprotocol/claude-agent-acp@0.73.0':
|
||||
|
|
@ -624,11 +626,11 @@ importers:
|
|||
specifier: ^6.12.4
|
||||
version: 6.12.4
|
||||
'@codemirror/state':
|
||||
specifier: ^6.7.1
|
||||
version: 6.7.1
|
||||
specifier: ^6.7.2
|
||||
version: 6.7.2
|
||||
'@codemirror/view':
|
||||
specifier: ^6.43.9
|
||||
version: 6.43.9
|
||||
specifier: ^6.43.11
|
||||
version: 6.43.11
|
||||
'@lezer/highlight':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.3
|
||||
|
|
@ -889,6 +891,9 @@ importers:
|
|||
'@discordjs/ws':
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3(patch_hash=vbylpnnxt5av2zdnuqmf6hjyjy)
|
||||
'@grpc/grpc-js':
|
||||
specifier: 1.14.4
|
||||
version: 1.14.4
|
||||
'@opentelemetry/api':
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.1
|
||||
|
|
@ -940,6 +945,9 @@ importers:
|
|||
'@paperclipai/skills-catalog':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/skills-catalog
|
||||
'@photon-ai/advanced-imessage':
|
||||
specifier: 2.1.0
|
||||
version: 2.1.0(@grpc/grpc-js@1.14.4)(nice-grpc-common@2.0.4)(nice-grpc@2.1.17)
|
||||
'@vercel/connect':
|
||||
specifier: 0.6.1
|
||||
version: 0.6.1(@chat-adapter/slack@4.39.0(patch_hash=226jj24akljccsfbw7bcxujz4u)(zod@4.4.3))(better-auth@1.7.2(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(kysely@0.29.5)(pg@8.18.0)(postgres@3.4.9))(pg@8.18.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))))
|
||||
|
|
@ -979,12 +987,21 @@ importers:
|
|||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.2.1
|
||||
heif2jpeg:
|
||||
specifier: 0.1.6
|
||||
version: 0.1.6
|
||||
jsdom:
|
||||
specifier: ^30.0.1
|
||||
version: 30.0.1(@noble/hashes@2.4.0)
|
||||
multer:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
nice-grpc:
|
||||
specifier: 2.1.17
|
||||
version: 2.1.17
|
||||
nice-grpc-common:
|
||||
specifier: 2.0.4
|
||||
version: 2.0.4
|
||||
open:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1
|
||||
|
|
@ -1725,6 +1742,9 @@ packages:
|
|||
'@bufbuild/protobuf@1.10.0':
|
||||
resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==}
|
||||
|
||||
'@bufbuild/protobuf@2.15.0':
|
||||
resolution: {integrity: sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA==}
|
||||
|
||||
'@chat-adapter/discord@4.39.0':
|
||||
resolution: {integrity: sha512-9IS4HF0Jw+Ucsm3f7XaXGCGb22Swb/r6FGaHoSDYxOEraQ88pFH5S7xc8//C+pCnqhX7p8QwLB/dYaNxDnl9pw==}
|
||||
engines: {node: '>=20'}
|
||||
|
|
@ -1856,18 +1876,12 @@ packages:
|
|||
'@codemirror/search@6.6.0':
|
||||
resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
|
||||
|
||||
'@codemirror/state@6.7.1':
|
||||
resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==}
|
||||
|
||||
'@codemirror/state@6.7.2':
|
||||
resolution: {integrity: sha512-U3RiPX62Wl/Gx4ftQ7UxLlloSfsFTQqKa+7vFBYteZGCzkp6oBqcsD7iSwniWRGobCAmDcwZSY+Six3+3ztdfg==}
|
||||
|
||||
'@codemirror/view@6.43.11':
|
||||
resolution: {integrity: sha512-2+esucbQX6wB2JYi1eDvdCPFTA31BN8oSy6xCmk3G6CloV11yOvEjYk+gH7kLrP0MuHG94E8WDhjs5oMiu3+Wg==}
|
||||
|
||||
'@codemirror/view@6.43.9':
|
||||
resolution: {integrity: sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==}
|
||||
|
||||
'@connectrpc/connect-node@1.7.0':
|
||||
resolution: {integrity: sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
|
@ -2606,6 +2620,51 @@ packages:
|
|||
'@modelcontextprotocol/sdk':
|
||||
optional: true
|
||||
|
||||
'@grpc/grpc-js@1.14.4':
|
||||
resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==}
|
||||
engines: {node: '>=12.10.0'}
|
||||
|
||||
'@grpc/proto-loader@0.8.1':
|
||||
resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-darwin-arm64@0.1.6':
|
||||
resolution: {integrity: sha512-HSXg4gGIb/RUwdUyRfXMopxvs8JpTLexW5+XLV3MqNZ8bMjUINv8aAigBECZ7It8dFWfcpui236S+z/LLMMFHQ==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@heif2jpeg/heif2jpeg-darwin-x64@0.1.6':
|
||||
resolution: {integrity: sha512-2Rno3RuhB5jK61RoMThoD9XNG+p997xOs9qN+qAk4vlr8IVFvAamunWvmtumHiJOWQ97mL7WuvpTa4PGDadqcQ==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@heif2jpeg/heif2jpeg-linux-arm64-gnu@0.1.6':
|
||||
resolution: {integrity: sha512-myeXEKyYG39tPz7Tir526LozlRKk8a70zdpiBoEIZs1vxft3NAyj7OiR2SHbEQVOuRmcdOtJ6wxswvbafzOtAw==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@heif2jpeg/heif2jpeg-linux-x64-gnu@0.1.6':
|
||||
resolution: {integrity: sha512-fvxLcCxbmXNHEvGpnKjVZmO3Wx1ew/e2+LvOOKW1vHbH5/5E7xdgYpbJVGcVa4s4nneKbybhNXDfaMdcZYqRgg==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@heif2jpeg/heif2jpeg-win32-arm64-msvc@0.1.6':
|
||||
resolution: {integrity: sha512-WWh/rOngHL+q27Y8TXIwsBH111gBf5Z7kBc14LZg+LoD/EnK1KzXx5NcguIyhuKzAEQJk+BwTIqfP7HeFWCrmw==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@heif2jpeg/heif2jpeg-win32-x64-msvc@0.1.6':
|
||||
resolution: {integrity: sha512-Op3rpPzCwUxXHbzJD9hHNFBlV2nLr2/j+oNg5v0v4utKhLJ5GZg7NbfMJa07T1MkYMRs6lS60QhY/IWwWbSlFQ==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@hono/node-server@2.1.0':
|
||||
resolution: {integrity: sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==}
|
||||
engines: {node: '>=20'}
|
||||
|
|
@ -2793,6 +2852,9 @@ packages:
|
|||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@js-sdsl/ordered-map@4.4.2':
|
||||
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
||||
|
||||
'@lexical/a11y@0.48.0':
|
||||
resolution: {integrity: sha512-18W4ehyipkUim4YVoDZitoH63Om3j6iCN4c84zdqE9RgkWf/PE4rvI/8BHTm6Ni7NkVE14nimXgkpaP5ok15zA==}
|
||||
peerDependencies:
|
||||
|
|
@ -3039,9 +3101,6 @@ packages:
|
|||
'@lezer/yaml@1.0.4':
|
||||
resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.3':
|
||||
resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.4':
|
||||
resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==}
|
||||
|
||||
|
|
@ -3544,6 +3603,21 @@ packages:
|
|||
'@paralleldrive/cuid2@2.3.1':
|
||||
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
|
||||
|
||||
'@photon-ai/advanced-imessage@2.1.0':
|
||||
resolution: {integrity: sha512-KT/aCBWcq667alyKe8DclqDUa/0fUuW2ZYDCxfIKuQBm08U4JcNG56fCdNUd69MFkKkzZgW35j59EYsx5DnLcQ==}
|
||||
engines: {node: '>=18.17'}
|
||||
peerDependencies:
|
||||
'@grpc/grpc-js': ^1.12.6
|
||||
nice-grpc: ^2.1.11
|
||||
nice-grpc-common: ^2.0.2
|
||||
peerDependenciesMeta:
|
||||
'@grpc/grpc-js':
|
||||
optional: true
|
||||
nice-grpc:
|
||||
optional: true
|
||||
nice-grpc-common:
|
||||
optional: true
|
||||
|
||||
'@pierre/diffs@1.3.6':
|
||||
resolution: {integrity: sha512-a3woaW2QHy78JDxPJK0OJzwZUN4xoQLLIS/pceO8X6+L8gA5D682mP7/w3YxxEVRPXOaoe/p/RJ5Oj/3nrEzew==}
|
||||
peerDependencies:
|
||||
|
|
@ -5572,6 +5646,9 @@ packages:
|
|||
'@xterm/xterm@6.0.0':
|
||||
resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==}
|
||||
|
||||
abort-controller-x@0.5.0:
|
||||
resolution: {integrity: sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==}
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
|
@ -5964,6 +6041,10 @@ packages:
|
|||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -5972,8 +6053,8 @@ packages:
|
|||
resolution: {integrity: sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@codemirror/state': ^6.7.2
|
||||
'@codemirror/view': ^6.43.11
|
||||
'@lezer/highlight': ^1.0.0
|
||||
|
||||
cmdk@1.1.1:
|
||||
|
|
@ -6869,6 +6950,10 @@ packages:
|
|||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
heif2jpeg@0.1.6:
|
||||
resolution: {integrity: sha512-pXgHIbFS6HDcGhcYHkTltdKG+WSEb7Xtk8NSzQf7tVz3Nw8JzXojqyOqyqRfMLYLbHzxTuLN3a4FZKFlTHzcRA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
help-me@5.0.0:
|
||||
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
|
||||
|
||||
|
|
@ -7302,6 +7387,9 @@ packages:
|
|||
lodash-es@4.18.1:
|
||||
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
||||
|
||||
lodash.camelcase@4.3.0:
|
||||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
|
||||
lodash.clonedeep@4.5.0:
|
||||
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
||||
|
||||
|
|
@ -7690,6 +7778,12 @@ packages:
|
|||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
nice-grpc-common@2.0.4:
|
||||
resolution: {integrity: sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==}
|
||||
|
||||
nice-grpc@2.1.17:
|
||||
resolution: {integrity: sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
|
|
@ -8253,6 +8347,10 @@ packages:
|
|||
remend@1.3.1:
|
||||
resolution: {integrity: sha512-N3DiY5qbRPoa5vkxn1oDLMyXOVTeo6Hp+XOj6SIqJAYUgLS0Q587gILPMom/qm86AQ/ZrcOdwEIzCz8V3J0nxQ==}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -8671,6 +8769,9 @@ packages:
|
|||
resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==}
|
||||
engines: {node: '>=6.10'}
|
||||
|
||||
ts-error@1.0.6:
|
||||
resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==}
|
||||
|
||||
ts-mixer@6.0.4:
|
||||
resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==}
|
||||
|
||||
|
|
@ -9080,6 +9181,10 @@ packages:
|
|||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
|
|
@ -9091,6 +9196,14 @@ packages:
|
|||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@17.7.3:
|
||||
resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yjs@13.6.32:
|
||||
resolution: {integrity: sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ==}
|
||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||
|
|
@ -9761,6 +9874,8 @@ snapshots:
|
|||
|
||||
'@bufbuild/protobuf@1.10.0': {}
|
||||
|
||||
'@bufbuild/protobuf@2.15.0': {}
|
||||
|
||||
'@chat-adapter/discord@4.39.0(patch_hash=nywlhls7dd4npegrgax7jqdbxa)(zod@4.4.3)':
|
||||
dependencies:
|
||||
'@chat-adapter/shared': 4.39.0(zod@4.4.3)
|
||||
|
|
@ -9854,22 +9969,22 @@ snapshots:
|
|||
'@codemirror/autocomplete@6.20.0':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@codemirror/autocomplete@6.20.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@codemirror/commands@6.10.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@codemirror/commands@6.11.0':
|
||||
|
|
@ -9931,8 +10046,8 @@ snapshots:
|
|||
'@codemirror/autocomplete': 6.20.3
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/lint': 6.9.6
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/javascript': 1.5.4
|
||||
|
||||
|
|
@ -10082,8 +10197,8 @@ snapshots:
|
|||
|
||||
'@codemirror/language@6.12.4':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
|
@ -10095,14 +10210,14 @@ snapshots:
|
|||
|
||||
'@codemirror/lint@6.9.4':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
crelt: 1.0.7
|
||||
|
||||
'@codemirror/lint@6.9.6':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
crelt: 1.0.7
|
||||
|
||||
'@codemirror/merge@6.12.2':
|
||||
|
|
@ -10115,14 +10230,10 @@ snapshots:
|
|||
|
||||
'@codemirror/search@6.6.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
crelt: 1.0.7
|
||||
|
||||
'@codemirror/state@6.7.1':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.3
|
||||
|
||||
'@codemirror/state@6.7.2':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.4
|
||||
|
|
@ -10134,13 +10245,6 @@ snapshots:
|
|||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@codemirror/view@6.43.9':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.7.1
|
||||
crelt: 1.0.7
|
||||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@connectrpc/connect-node@1.7.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.0))':
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 1.10.0
|
||||
|
|
@ -10709,6 +10813,36 @@ snapshots:
|
|||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@grpc/grpc-js@1.14.4':
|
||||
dependencies:
|
||||
'@grpc/proto-loader': 0.8.1
|
||||
'@js-sdsl/ordered-map': 4.4.2
|
||||
|
||||
'@grpc/proto-loader@0.8.1':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.3.2
|
||||
protobufjs: 7.6.6
|
||||
yargs: 17.7.3
|
||||
|
||||
'@heif2jpeg/heif2jpeg-darwin-arm64@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-darwin-x64@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-linux-arm64-gnu@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-linux-x64-gnu@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-win32-arm64-msvc@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@heif2jpeg/heif2jpeg-win32-x64-msvc@0.1.6':
|
||||
optional: true
|
||||
|
||||
'@hono/node-server@2.1.0(hono@4.13.2)':
|
||||
dependencies:
|
||||
hono: 4.13.2
|
||||
|
|
@ -10863,6 +10997,8 @@ snapshots:
|
|||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@js-sdsl/ordered-map@4.4.2': {}
|
||||
|
||||
'@lexical/a11y@0.48.0(typescript@7.0.2)':
|
||||
dependencies:
|
||||
'@lexical/extension': 0.48.0(typescript@7.0.2)
|
||||
|
|
@ -11188,8 +11324,6 @@ snapshots:
|
|||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
||||
'@marijn/find-cluster-break@1.0.3': {}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.4': {}
|
||||
|
||||
'@mariozechner/clipboard-darwin-arm64@0.3.9':
|
||||
|
|
@ -11687,6 +11821,14 @@ snapshots:
|
|||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
|
||||
'@photon-ai/advanced-imessage@2.1.0(@grpc/grpc-js@1.14.4)(nice-grpc-common@2.0.4)(nice-grpc@2.1.17)':
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 2.15.0
|
||||
optionalDependencies:
|
||||
'@grpc/grpc-js': 1.14.4
|
||||
nice-grpc: 2.1.17
|
||||
nice-grpc-common: 2.0.4
|
||||
|
||||
'@pierre/diffs@1.3.6(@shikijs/themes@4.4.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@pierre/theme': 2.0.0
|
||||
|
|
@ -13667,6 +13809,8 @@ snapshots:
|
|||
|
||||
'@xterm/xterm@6.0.0': {}
|
||||
|
||||
abort-controller-x@0.5.0: {}
|
||||
|
||||
accepts@2.0.0:
|
||||
dependencies:
|
||||
mime-types: 3.0.2
|
||||
|
|
@ -14000,6 +14144,12 @@ snapshots:
|
|||
|
||||
classnames@2.5.1: {}
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
cm6-theme-basic-light@0.2.0(@codemirror/language@6.12.4)(@codemirror/state@6.7.2)(@codemirror/view@6.43.11)(@lezer/highlight@1.2.3):
|
||||
|
|
@ -14028,8 +14178,8 @@ snapshots:
|
|||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/lint': 6.9.4
|
||||
'@codemirror/search': 6.6.0
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.43.9
|
||||
'@codemirror/state': 6.7.2
|
||||
'@codemirror/view': 6.43.11
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
|
|
@ -14970,6 +15120,15 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/hast': 3.0.5
|
||||
|
||||
heif2jpeg@0.1.6:
|
||||
optionalDependencies:
|
||||
'@heif2jpeg/heif2jpeg-darwin-arm64': 0.1.6
|
||||
'@heif2jpeg/heif2jpeg-darwin-x64': 0.1.6
|
||||
'@heif2jpeg/heif2jpeg-linux-arm64-gnu': 0.1.6
|
||||
'@heif2jpeg/heif2jpeg-linux-x64-gnu': 0.1.6
|
||||
'@heif2jpeg/heif2jpeg-win32-arm64-msvc': 0.1.6
|
||||
'@heif2jpeg/heif2jpeg-win32-x64-msvc': 0.1.6
|
||||
|
||||
help-me@5.0.0: {}
|
||||
|
||||
highlight.js@10.7.3: {}
|
||||
|
|
@ -15339,6 +15498,8 @@ snapshots:
|
|||
|
||||
lodash-es@4.18.1: {}
|
||||
|
||||
lodash.camelcase@4.3.0: {}
|
||||
|
||||
lodash.clonedeep@4.5.0: {}
|
||||
|
||||
lodash.includes@4.3.0: {}
|
||||
|
|
@ -15997,6 +16158,16 @@ snapshots:
|
|||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
nice-grpc-common@2.0.4:
|
||||
dependencies:
|
||||
ts-error: 1.0.6
|
||||
|
||||
nice-grpc@2.1.17:
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.4
|
||||
abort-controller-x: 0.5.0
|
||||
nice-grpc-common: 2.0.4
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
|
|
@ -16679,6 +16850,8 @@ snapshots:
|
|||
|
||||
remend@1.3.1: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
|
@ -17204,6 +17377,8 @@ snapshots:
|
|||
|
||||
ts-dedent@2.3.0: {}
|
||||
|
||||
ts-error@1.0.6: {}
|
||||
|
||||
ts-mixer@6.0.4: {}
|
||||
|
||||
tsconfig-paths@4.2.0:
|
||||
|
|
@ -17576,12 +17751,26 @@ snapshots:
|
|||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yaml@2.9.0: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.3:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yjs@13.6.32:
|
||||
dependencies:
|
||||
lib0: 0.2.117
|
||||
|
|
|
|||
|
|
@ -618,6 +618,59 @@ describe("runChildProcess", () => {
|
|||
expect(finishedAt - startedAt).toBeGreaterThanOrEqual(spawnDelayMs);
|
||||
});
|
||||
|
||||
it.each([0, 7])(
|
||||
"preserves exit %i and output when the child closes stdin before an asynchronous write",
|
||||
async (exitCode) => {
|
||||
let ready!: () => void;
|
||||
const stdinClosed = new Promise<void>((resolve) => {
|
||||
ready = resolve;
|
||||
});
|
||||
let spawnCount = 0;
|
||||
const errors: unknown[] = [];
|
||||
const result = await runChildProcess(
|
||||
randomUUID(),
|
||||
process.execPath,
|
||||
[
|
||||
"-e",
|
||||
[
|
||||
"require('node:fs').closeSync(0);",
|
||||
"process.stdout.write('stdin-closed\\n');",
|
||||
"setTimeout(() => {",
|
||||
"require('node:fs').writeSync(1, 'final-output');",
|
||||
"require('node:fs').writeSync(2, 'final-error');",
|
||||
`process.exit(${exitCode});`,
|
||||
"}, 100);",
|
||||
].join(" "),
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: {},
|
||||
timeoutSec: 5,
|
||||
graceSec: 1,
|
||||
stdin: "input".repeat(1024 * 1024),
|
||||
onSpawn: async () => {
|
||||
spawnCount++;
|
||||
await stdinClosed;
|
||||
},
|
||||
onLog: async (stream, chunk) => {
|
||||
if (stream === "stdout" && chunk.includes("stdin-closed")) ready();
|
||||
},
|
||||
onLogError: (error) => {
|
||||
errors.push(error);
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(result.exitCode).toBe(exitCode);
|
||||
expect(result.signal).toBeNull();
|
||||
expect(result.timedOut).toBe(false);
|
||||
expect(result.stdout).toBe("stdin-closed\nfinal-output");
|
||||
expect(result.stderr).toBe("final-error");
|
||||
expect(spawnCount).toBe(1);
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toMatchObject({ code: "EPIPE" });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"kills descendant processes on timeout via the process group",
|
||||
async () => {
|
||||
|
|
|
|||
|
|
@ -4751,11 +4751,26 @@ export async function runChildProcess(
|
|||
|
||||
const stdin = child.stdin;
|
||||
if (opts.stdin != null && stdin) {
|
||||
void spawnPersistPromise.finally(() => {
|
||||
if (child.killed || stdin.destroyed) return;
|
||||
stdin.write(opts.stdin as string);
|
||||
stdin.end();
|
||||
});
|
||||
const reportStdinError = (err: unknown) => {
|
||||
onLogError(err, runId, "failed to write child stdin");
|
||||
};
|
||||
// The child can close its input before this asynchronous write drains.
|
||||
// Keep its exit and output authoritative, but handle the pipe's own
|
||||
// error event so an early exit cannot crash the parent process.
|
||||
stdin.on("error", reportStdinError);
|
||||
void spawnPersistPromise
|
||||
.then(() => {
|
||||
if (
|
||||
child.killed ||
|
||||
child.exitCode !== null ||
|
||||
child.signalCode !== null ||
|
||||
stdin.destroyed
|
||||
) {
|
||||
return;
|
||||
}
|
||||
stdin.end(opts.stdin as string);
|
||||
})
|
||||
.catch(reportStdinError);
|
||||
}
|
||||
|
||||
child.on("error", (err: Error) => {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -99,7 +99,10 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
}
|
||||
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
|
||||
|
||||
it("upgrades the exact f3c67d50 published history without losing files or provider sessions", async ({ onTestFinished }) => {
|
||||
it.for([
|
||||
{ source: "f3c67d50", sourceCommit: "f3c67d50dad32563c7eb5cef1ebae8e83584d4cf", count: 248, removedHashes: 4 },
|
||||
{ source: "64814d5", sourceCommit: "64814d5a4b1cf9a61e6f0a661856d3c92401fa3b", count: 274, removedHashes: 0 },
|
||||
])("upgrades the exact $source history without losing files or provider sessions", { timeout: EMBEDDED_POSTGRES_TEST_TIMEOUT_MS }, async ({ source, sourceCommit, count, removedHashes }, { onTestFinished }) => {
|
||||
const database = await startEmbeddedPostgresTestDatabase("work-folder-historical-");
|
||||
// Register each cleanup before acquiring the next resource or parsing the
|
||||
// fixture. Vitest runs these in reverse order even when setup fails.
|
||||
|
|
@ -108,11 +111,11 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
onTestFinished(() => admin.end());
|
||||
const migrationsFolder = mkdtempSync(path.join(os.tmpdir(), "work-folder-f3-history-"));
|
||||
onTestFinished(() => rmSync(migrationsFolder, { recursive: true, force: true }));
|
||||
const historyRoot = new URL("./__fixtures__/work-folders-f3c67d50/", import.meta.url);
|
||||
const historyRoot = new URL(`./__fixtures__/work-folders-${source}/`, import.meta.url);
|
||||
const history = JSON.parse(readFileSync(new URL("history.json", historyRoot), "utf8")) as {
|
||||
sourceCommit: string;
|
||||
journal: { entries: { tag: string; when: number }[] };
|
||||
files: { name: string; sha256: string }[];
|
||||
files: { name: string; sha256: string; currentFile?: string }[];
|
||||
};
|
||||
const historicalUrl = new URL(database.connectionString);
|
||||
historicalUrl.pathname = "/historical_preview";
|
||||
|
|
@ -121,32 +124,32 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
// The cluster helper's regular database is intentionally not the upgrade
|
||||
// subject: this separate database has never seen the current schema.
|
||||
await admin`CREATE DATABASE historical_preview`;
|
||||
expect(history.sourceCommit).toBe("f3c67d50dad32563c7eb5cef1ebae8e83584d4cf");
|
||||
expect(history.files).toHaveLength(248);
|
||||
expect(history.journal.entries).toHaveLength(248);
|
||||
expect(history.sourceCommit).toBe(sourceCommit);
|
||||
expect(history.files).toHaveLength(count);
|
||||
expect(history.journal.entries).toHaveLength(count);
|
||||
mkdirSync(path.join(migrationsFolder, "meta"));
|
||||
writeFileSync(path.join(migrationsFolder, "meta/_journal.json"), JSON.stringify(history.journal));
|
||||
for (const file of history.files) {
|
||||
const historicalFile = new URL(file.name, historyRoot);
|
||||
const source = existsSync(historicalFile) ? historicalFile : new URL(`./migrations/${file.name}`, import.meta.url);
|
||||
const bytes = readFileSync(source);
|
||||
// The 244 unchanged files are shared, but every byte is pinned to the
|
||||
// old package. The four removed preview migrations are verbatim fixtures.
|
||||
const sourceFile = existsSync(historicalFile) ? historicalFile : new URL(`./migrations/${file.currentFile ?? file.name}`, import.meta.url);
|
||||
const bytes = readFileSync(sourceFile);
|
||||
// Shared SQL, including the renumbered idempotent work-folder migration,
|
||||
// must match the exact old bytes. Removed SQL remains in the fixture.
|
||||
expect(createHash("sha256").update(bytes).digest("hex"), file.name).toBe(file.sha256);
|
||||
writeFileSync(path.join(migrationsFolder, file.name), bytes);
|
||||
}
|
||||
await migrate(drizzle(sql), { migrationsFolder });
|
||||
const journalBefore = [...await sql`SELECT id, hash, created_at FROM drizzle.__drizzle_migrations ORDER BY id`];
|
||||
expect(journalBefore).toHaveLength(248);
|
||||
expect(journalBefore).toHaveLength(count);
|
||||
expect(journalBefore.map(row => ({ hash: row.hash, created_at: String(row.created_at) }))).toEqual(
|
||||
history.journal.entries.map(entry => ({
|
||||
hash: history.files.find(file => file.name === `${entry.tag}.sql`)!.sha256,
|
||||
created_at: String(entry.when),
|
||||
})),
|
||||
);
|
||||
expect(await sql`SELECT to_regclass('public.email_messages') AS name`).toEqual([{ name: null }]);
|
||||
expect(await sql`SELECT to_regclass('public.email_messages') AS name`).toEqual([{ name: source === "f3c67d50" ? null : "email_messages" }]);
|
||||
expect(await sql`SELECT column_name FROM information_schema.columns WHERE table_name = 'heartbeat_runs'
|
||||
AND column_name = 'controller_boot_id'`).toHaveLength(0);
|
||||
AND column_name = 'controller_boot_id'`).toHaveLength(source === "f3c67d50" ? 0 : 1);
|
||||
|
||||
const company = randomUUID(), otherCompany = randomUUID(), project = randomUUID(), environment = randomUUID();
|
||||
const responsibleUser = "paperclip-id:historical-preview-user";
|
||||
|
|
@ -214,7 +217,11 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
const pending = await inspectMigrations(historicalUrl.toString());
|
||||
expect(pending.status).toBe("needsMigrations");
|
||||
if (pending.status !== "needsMigrations") throw new Error("Historical preview unexpectedly has current migrations");
|
||||
expect(pending.pendingMigrations).toContain("0277_sandbox_work_folders.sql");
|
||||
expect(pending.pendingMigrations).toEqual(expect.arrayContaining([
|
||||
"0275_easy_dragon_man.sql", "0276_hard_mandroid.sql",
|
||||
]));
|
||||
if (source === "f3c67d50") expect(pending.pendingMigrations).toContain("0277_sandbox_work_folders.sql");
|
||||
else expect(pending.pendingMigrations).not.toContain("0277_sandbox_work_folders.sql");
|
||||
await applyPendingMigrations(historicalUrl.toString());
|
||||
expect((await inspectMigrations(historicalUrl.toString())).status).toBe("upToDate");
|
||||
for (const table of preservedTables) {
|
||||
|
|
@ -225,6 +232,12 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
expect(await sql`SELECT to_regclass('public.email_messages') AS name`).toEqual([{ name: "email_messages" }]);
|
||||
expect(await sql`SELECT column_name FROM information_schema.columns WHERE table_name = 'heartbeat_runs'
|
||||
AND column_name IN ('controller_boot_id', 'controller_lease_expires_at', 'execution_stage')`).toHaveLength(3);
|
||||
// Both mainline migrations must execute even when the saved preview has a
|
||||
// later work-folder timestamp. A schema marker alone is not evidence.
|
||||
expect(await sql`SELECT to_regclass('public.ai_connection_defaults') AS name`).toEqual([{ name: "ai_connection_defaults" }]);
|
||||
expect(await sql`SELECT to_regclass('public.chat_endpoints_photon_number_uq') AS name`).toEqual([{ name: "chat_endpoints_photon_number_uq" }]);
|
||||
expect(await sql`SELECT column_name FROM information_schema.columns WHERE table_name = 'adapter_auth_sessions'
|
||||
AND column_name IN ('ai_connection', 'connection_id', 'connection_grant_id', 'connection_method')`).toHaveLength(4);
|
||||
// Compare the complete six-table catalog with the independently migrated
|
||||
// current database, ignoring physical column order from historical ADDs.
|
||||
async function workFolderSchema(connection: typeof sql) {
|
||||
|
|
@ -246,7 +259,7 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
}
|
||||
expect(await workFolderSchema(sql)).toEqual(await workFolderSchema(admin));
|
||||
const journalAfter = [...await sql`SELECT id, hash, created_at FROM drizzle.__drizzle_migrations ORDER BY id`];
|
||||
expect(journalAfter).toHaveLength(pending.availableMigrations.length + 4);
|
||||
expect(journalAfter).toHaveLength(pending.availableMigrations.length + removedHashes);
|
||||
expect(journalAfter.slice(0, journalBefore.length)).toEqual(journalBefore);
|
||||
expect(new Set(journalAfter.map(row => row.hash)).size).toBe(journalAfter.length);
|
||||
for (const hash of history.files.slice(-4).map(file => file.sha256)) expect(journalAfter.filter(row => row.hash === hash)).toHaveLength(1);
|
||||
|
|
@ -264,6 +277,6 @@ const migration = readFileSync(new URL("./migrations/0277_sandbox_work_folders.s
|
|||
} finally {
|
||||
await sql.end();
|
||||
}
|
||||
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import { createDb, companies, agents, heartbeatRuns, companyMemberships, connect
|
|||
import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded-postgres";
|
||||
import { aiConnectionService } from "../services/ai-connections.js";
|
||||
import * as executionTarget from "@paperclipai/adapter-utils/execution-target";
|
||||
import { prepareManagedAiRuntime, assertManagedAiProjectAuth } from "../services/ai-connection-runtime.js";
|
||||
import { prepareManagedAiRuntime, assertManagedAiProjectAuth, managedAiSessionFingerprintConfig } from "../services/ai-connection-runtime.js";
|
||||
import { buildEffectiveRunSessionConfigMetadata } from "../services/heartbeat.js";
|
||||
import { toolAccessService } from "../services/tool-access.js";
|
||||
import { secretService } from "../services/secrets.js";
|
||||
import { connectionPurposeTransportSchema, isAiConnectionCompatible } from "@paperclipai/shared";
|
||||
|
|
@ -130,6 +131,34 @@ describe("managed AI connections", () => {
|
|||
const account = await service.select({ ...input, userId: "alice" });
|
||||
await expect(service.select({ ...input, companyId: otherCompanyId, userId: "alice", binding: { ...binding, mode: "shared", connectionId: account.connection.id, grantId: account.grant.id } })).rejects.toThrow();
|
||||
});
|
||||
it("keeps managed task sessions across temporary homes while detecting account and configuration changes", async () => {
|
||||
const config = { model: "unchanged-model", env: { PLAIN_FLAG: "first" } };
|
||||
const runtimes = await Promise.all(["alice", "alice", "bob"].map(responsibleUserId =>
|
||||
prepareManagedAiRuntime(db, { ...input, responsibleUserId, config })));
|
||||
try {
|
||||
const fingerprint = async (runtime: typeof runtimes[number], effectiveConfig = runtime.config, managed = true) =>
|
||||
(await buildEffectiveRunSessionConfigMetadata({
|
||||
adapterType: input.adapterType,
|
||||
effectiveAdapterConfig: managedAiSessionFingerprintConfig(effectiveConfig, managed ? runtime : undefined),
|
||||
agentRuntimeConfig: { aiConnection: binding },
|
||||
issueOverrides: null, workspaceConfig: null, environment: null,
|
||||
environmentEnv: null, projectEnv: null, routineEnv: null, runtimeSkills: [],
|
||||
})).fingerprint;
|
||||
const [first, next, otherUser] = runtimes;
|
||||
expect(first.config.env.HOME).not.toBe(next.config.env.HOME);
|
||||
expect(first.identity).toBe(next.identity);
|
||||
expect(await fingerprint(first)).toBe(await fingerprint(next));
|
||||
expect(await fingerprint(first)).not.toBe(await fingerprint(otherUser));
|
||||
expect(await fingerprint(next, { ...next.config, env: { ...next.config.env, PLAIN_FLAG: "changed" } })).not.toBe(await fingerprint(first));
|
||||
expect(await fingerprint(next, { ...next.config, model: "changed-model" })).not.toBe(await fingerprint(first));
|
||||
// Homes supplied by an ordinary local/SSH configuration remain meaningful.
|
||||
expect(await fingerprint(first, first.config, false)).not.toBe(await fingerprint(next, next.config, false));
|
||||
expect(first.config.env.HOME).toBeTruthy();
|
||||
expect(next.config.env.CODEX_HOME).toBeTruthy();
|
||||
} finally {
|
||||
await Promise.all(runtimes.map(runtime => runtime.cleanup()));
|
||||
}
|
||||
});
|
||||
it("resolves shared encrypted credentials through the existing secret binding system", async () => {
|
||||
const created = await create("alice", "Shared credential proof", "shared");
|
||||
const selected = await service.select({ ...input, userId: "bob", binding: { ...binding, mode: "shared", ...created } });
|
||||
|
|
|
|||
|
|
@ -60,6 +60,24 @@ export function stripAiAuthBindings(env: unknown): Record<string, unknown> {
|
|||
delete result[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compare managed credentials by account identity, not their per-run staging directory. */
|
||||
export function managedAiSessionFingerprintConfig(
|
||||
config: Record<string, unknown>,
|
||||
runtime: { config: { env: Record<string, unknown> } } | undefined,
|
||||
): Record<string, unknown> {
|
||||
if (!runtime) return config;
|
||||
const env = { ...(config.env as Record<string, unknown> | undefined) };
|
||||
// Only the host's exact generated paths are omitted. Ordinary home settings,
|
||||
// provider routing, credentials, and the managed account identity still count.
|
||||
for (const key of ["HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "CODEX_HOME", "GROK_HOME", "CLAUDE_CONFIG_DIR"]) {
|
||||
if (typeof runtime.config.env[key] === "string" && env[key] === runtime.config.env[key]) {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
return { ...config, env };
|
||||
}
|
||||
|
||||
export async function assertManagedAiProjectAuth(
|
||||
config: Record<string, unknown>,
|
||||
provider: AiConnectionBinding["provider"],
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { hasRemoteTerminationReceipt, remoteExecutionHasStopped, remoteTerminati
|
|||
import { applyConnectorSkills, prepareConnectorSkillDelivery, resolveConnectorAssignments } from "./connector-runtime.js";
|
||||
import { admitExplicitNativeContinuation } from "./explicit-native-continuation.js";
|
||||
import { connectionIntentService } from "./connection-intents.js";
|
||||
import { prepareManagedAiRuntime, assertManagedAiProjectAuth, stripAiAuthBindings, AI_AUTH_ENV_KEYS } from "./ai-connection-runtime.js";
|
||||
import { prepareManagedAiRuntime, assertManagedAiProjectAuth, stripAiAuthBindings, AI_AUTH_ENV_KEYS, managedAiSessionFingerprintConfig } from "./ai-connection-runtime.js";
|
||||
import { aiConnectionBindingSchema } from "@paperclipai/shared";
|
||||
import { executionBlockerPredicate, getExecutionBlocker } from "./execution-blocker.js";
|
||||
import { CONVERSATION_CONTINUATION_POLICY, claimedAdapterType, runUsedConversationAdapter, hasConversationContinuationPolicy, isConversationAdapter } from "./conversation-continuation.js";
|
||||
|
|
@ -20896,7 +20896,7 @@ export function heartbeatService(
|
|||
const sessionConfigMetadata =
|
||||
await measureSandboxOperation("heartbeat.build_effective_run_session_config_metadata", { operationIndex: 61 }, async () => (buildEffectiveRunSessionConfigMetadata({
|
||||
adapterType: agent.adapterType,
|
||||
effectiveAdapterConfig: runtimeConfig,
|
||||
effectiveAdapterConfig: managedAiSessionFingerprintConfig(runtimeConfig, managedAiRuntime),
|
||||
agentRuntimeConfig: agent.runtimeConfig,
|
||||
issueOverrides: issueAssigneeOverrides,
|
||||
workspaceConfig: {
|
||||
|
|
|
|||
|
|
@ -9831,6 +9831,63 @@ describe("runnerd provider runtime wiring", () => {
|
|||
}));
|
||||
});
|
||||
|
||||
it.each(["/home/daytona", undefined])(
|
||||
"copies refreshed managed Codex auth from the actual remote CLI home (%s) and removes that copy",
|
||||
async (workFolderHome) => {
|
||||
const credentialHome = join(isolatedStateDirectory, "managed-credential");
|
||||
await mkdir(credentialHome);
|
||||
const auth = (refresh: string) => JSON.stringify({
|
||||
tokens: { account_id: "same-account", id_token: "test-id",
|
||||
access_token: `test-access-${refresh}`, refresh_token: `test-refresh-${refresh}` },
|
||||
last_refresh: refresh,
|
||||
});
|
||||
const original = auth("2026-01-01T00:00:00Z");
|
||||
const refreshed = auth("2026-01-02T00:00:00Z");
|
||||
const hostAuth = join(credentialHome, "auth.json");
|
||||
await writeFile(hostAuth, original, { mode: 0o600 });
|
||||
let remoteAuthPath = "";
|
||||
const operations: string[] = [];
|
||||
const close = vi.fn(async () => { operations.push("close"); });
|
||||
state.createBackend.mockReturnValueOnce({
|
||||
kind: "test", openSession: async () => ({ close }),
|
||||
} as never);
|
||||
const remoteExecute = vi.fn(async (command: { command: string; args?: string[] }) => {
|
||||
if (command.command === "base64") {
|
||||
operations.push("read");
|
||||
return { exitCode: command.args?.[0] === remoteAuthPath ? 0 : 1,
|
||||
stdout: Buffer.from(refreshed).toString("base64"), stderr: "", timedOut: false };
|
||||
}
|
||||
if (command.command === "rm") operations.push("remove");
|
||||
return { exitCode: 0, stdout: "", stderr: "", timedOut: false };
|
||||
});
|
||||
const backend = await createRunnerdBackend({
|
||||
db: leaseDb(execution), execution, runnerInstanceId: "managed-auth-runner",
|
||||
managedAiCredentialHome: credentialHome,
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "sandbox", remoteCwd: "/home/daytona/repos/main",
|
||||
workFolderHome, environmentId: "environment", leaseId: "lease", providerKey: "daytona",
|
||||
runner: { execute: remoteExecute, syncIn: vi.fn() },
|
||||
} as never,
|
||||
runnerPublicUrl: "wss://paperclip.example.test",
|
||||
});
|
||||
const providerEnvironment = state.createBackend.mock.calls.at(-1)![1].environment!;
|
||||
remoteAuthPath = `${providerEnvironment.CODEX_HOME}/auth.json`;
|
||||
if (workFolderHome) {
|
||||
expect(providerEnvironment.HOME).toBe(workFolderHome);
|
||||
expect(remoteAuthPath).toBe(`${workFolderHome}/.codex/auth.json`);
|
||||
}
|
||||
const session = await backend.openSession({} as never);
|
||||
await session.close({} as never);
|
||||
expect(await readFile(hostAuth, "utf8")).toBe(refreshed);
|
||||
expect((await lstat(hostAuth)).mode & 0o777).toBe(0o600);
|
||||
expect(operations).toEqual(["close", "read", "remove"]);
|
||||
expect(remoteExecute).toHaveBeenCalledWith(expect.objectContaining({ command: "base64", args: [remoteAuthPath] }));
|
||||
expect(remoteExecute).toHaveBeenCalledWith(expect.objectContaining({ command: "rm", args: ["-f", "--", remoteAuthPath] }));
|
||||
await session.close({} as never);
|
||||
expect(remoteExecute).toHaveBeenCalledTimes(2);
|
||||
},
|
||||
);
|
||||
|
||||
it("uses the image's shared Codex without uploading or installing artifacts", async () => {
|
||||
const syncIn = vi.fn(async () => undefined);
|
||||
const remoteExecute = vi.fn(
|
||||
|
|
|
|||
|
|
@ -10133,6 +10133,13 @@ async function createRunnerdBackendWithinSessionClaim(
|
|||
const remoteRunnerFilesystemRoot = remoteSessionRoot
|
||||
? posix.join(remoteSessionRoot, "filesystem")
|
||||
: null;
|
||||
// Credential copy-back must use the same CLI home as launch and persistence.
|
||||
const remoteCodexHome =
|
||||
remoteTarget?.transport === "sandbox" && remoteTarget.workFolderHome
|
||||
? posix.join(remoteTarget.workFolderHome, ".codex")
|
||||
: remoteRunnerFilesystemRoot
|
||||
? posix.join(remoteRunnerFilesystemRoot, "codex-home")
|
||||
: null;
|
||||
const persistenceProfile = resolveNativeHarnessPersistenceProfile(
|
||||
input.execution,
|
||||
);
|
||||
|
|
@ -10143,8 +10150,8 @@ async function createRunnerdBackendWithinSessionClaim(
|
|||
const remotePersistencePath = (
|
||||
directory: NativeHarnessPersistenceDirectory,
|
||||
): string | null =>
|
||||
remoteTarget?.transport === "sandbox" && remoteTarget.workFolderHome && directory.name === "codex-home"
|
||||
? posix.join(remoteTarget.workFolderHome, ".codex")
|
||||
directory.name === "codex-home"
|
||||
? remoteCodexHome
|
||||
: directory.location === "runner"
|
||||
? (remoteStateDirectory ?? null)
|
||||
: remoteRunnerFilesystemRoot
|
||||
|
|
@ -11475,7 +11482,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
|||
// permission profile. SSH retains its isolated provider home so its
|
||||
// host-home deny rules cannot shadow the assigned workspace.
|
||||
HOME: remoteTarget!.transport === "sandbox" && remoteTarget!.workFolderHome ? remoteTarget!.workFolderHome : posix.join(remoteRunnerFilesystemRoot!, "codex-home"),
|
||||
CODEX_HOME: remoteTarget!.transport === "sandbox" && remoteTarget!.workFolderHome ? posix.join(remoteTarget!.workFolderHome, ".codex") : posix.join(remoteRunnerFilesystemRoot!, "codex-home"),
|
||||
CODEX_HOME: remoteCodexHome!,
|
||||
PAPERCLIP_WORKSPACE_CWD: remoteTarget!.remoteCwd,
|
||||
...(remoteTarget!.transport === "sandbox"
|
||||
? { PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "1" }
|
||||
|
|
@ -12026,7 +12033,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
|||
await close(closeInput);
|
||||
if (copied) return;
|
||||
copied = true;
|
||||
const remoteAuth = remoteRunnerFilesystemRoot ? posix.join(remoteRunnerFilesystemRoot, "codex-home", "auth.json") : null;
|
||||
const remoteAuth = remoteCodexHome ? posix.join(remoteCodexHome, "auth.json") : null;
|
||||
const localAuth = join(root, "codex-home", "auth.json");
|
||||
try {
|
||||
await copyBackCodexAuth({
|
||||
|
|
|
|||
|
|
@ -1541,6 +1541,7 @@ test.describe.serial("native chat adapter UI", () => {
|
|||
).toBeVisible();
|
||||
await expectSetupRail(page);
|
||||
await selectMaya(page);
|
||||
// The selection schedules a request; clicking alone does not await it.
|
||||
await expect.poll(() => mock.createdWithAgentId).toBe(seed.agentId);
|
||||
expect(mock.createdWithAgentId).not.toBe(seed.otherAgentId);
|
||||
await expect(
|
||||
|
|
|
|||
Loading…
Reference in New Issue