@awak-app/simy-cli 0.7.16 → 0.7.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +180 -26
  2. package/package.json +3 -2
  3. package/src/agent.js +128 -7
  4. package/src/bounded-local-task-subtask-pool.js +32 -6
  5. package/src/bounded-local-task-subtasks-contract.js +1 -1
  6. package/src/cli-contract.js +1 -1
  7. package/src/codex-plugins/simy/.codex-plugin/plugin.json +2 -2
  8. package/src/codex-plugins/simy/hooks/README.md +8 -3
  9. package/src/codex-plugins/simy/hooks/delivery-loop.mjs +261 -67
  10. package/src/codex-plugins/simy/hooks/hooks.json +1 -1
  11. package/src/codex-plugins/simy/hooks/loop-kernel/conformance.json +136 -0
  12. package/src/codex-plugins/simy/hooks/loop-kernel/index.mjs +42 -0
  13. package/src/codex-plugins/simy/hooks/loop-kernel/outcome-review.mjs +31 -0
  14. package/src/codex-plugins/simy/hooks/loop-kernel/pin.json +6 -0
  15. package/src/codex-plugins/simy/hooks/loop-kernel/policy.json +141 -0
  16. package/src/codex-plugins/simy/hooks/loop-kernel/task-pool.mjs +34 -0
  17. package/src/codex-plugins/simy/hooks/loop-kernel/work-frontier.mjs +132 -0
  18. package/src/codex-plugins/simy/hooks/loop-kernel.mjs +6 -0
  19. package/src/codex-plugins/simy/hooks/outcome-progress.mjs +15 -3
  20. package/src/codex-plugins/simy/hooks/purpose-completion-contract.mjs +10 -0
  21. package/src/codex-plugins/simy/hooks/purpose-evidence.mjs +8 -6
  22. package/src/codex-plugins/simy/hooks/result-cycle.mjs +32 -6
  23. package/src/codex-plugins/simy/hooks/work-frontier.mjs +2 -0
  24. package/src/codex-plugins/simy/skills/diagnose/SKILL.md +10 -4
  25. package/src/coding-run-recovery.js +4 -2
  26. package/src/delivery-hooks/claude-workflow.md +10 -7
  27. package/src/delivery-hooks/command.js +45 -10
  28. package/src/delivery-hooks/consult.js +60 -0
  29. package/src/delivery-hooks/diagnosis.js +2 -2
  30. package/src/delivery-hooks/legacy-hook-routing.js +46 -8
  31. package/src/delivery-hooks/local-runtime.js +50 -1
  32. package/src/delivery-hooks/local-supervisor.js +1 -1
  33. package/src/delivery-hooks/repair-continuation.js +80 -0
  34. package/src/delivery-hooks/repair.js +164 -0
  35. package/src/delivery-hooks/supervisor-service.js +3 -1
  36. package/src/delivery-hooks/supervisor.js +88 -12
  37. package/src/desktop-access.js +2 -1
  38. package/src/desktop-host.js +35 -2
  39. package/src/durable-local-task-worker.js +372 -58
  40. package/src/execution-capability-contract.js +15 -1
  41. package/src/index.js +3 -3
  42. package/src/local-photos-read.swift +33 -0
  43. package/src/local-photos.js +47 -0
  44. package/src/local-task-attachment-store.js +31 -0
  45. package/src/local-task-destination.js +174 -0
  46. package/src/local-task.js +119 -7
  47. package/src/native-delegation-contract.js +68 -0
  48. package/src/native-provider-identity.js +60 -0
  49. package/src/orchestrator/budget.js +22 -11
  50. package/src/orchestrator/independent-audit.js +9 -18
  51. package/src/orchestrator/loop.js +27 -59
  52. package/src/orchestrator/recovery.js +32 -2
  53. package/src/provider-delegation.js +336 -0
  54. package/src/runner.js +2 -1
  55. package/src/session-supervision/artifact-observer.js +138 -0
  56. package/src/session-supervision/codex-app-server.js +19 -5
  57. package/src/session-supervision/completion-artifacts.js +44 -0
  58. package/src/session-supervision/consistency-auditor.js +8 -11
  59. package/src/session-supervision/continuation-analysis.js +46 -28
  60. package/src/session-supervision/final-response-recovery.js +5 -3
  61. package/src/session-supervision/index.js +22 -5
  62. package/src/session-supervision/purpose-recovery.js +12 -3
  63. package/src/session-supervision/purpose-verification.js +9 -57
  64. package/src/session-supervision/recovery-frontier.js +55 -0
  65. package/src/session-supervision/stop-notifications.js +8 -8
  66. package/src/sqm/agent-skill.js +1 -1
  67. package/src/sqm/checkers.js +12 -6
  68. package/src/sqm/claude-plugin-update.js +22 -8
  69. package/src/sqm/claude-plugin.js +4 -0
  70. package/src/sqm/local-tests.js +2 -2
  71. package/src/sqm/plugin-cache.js +67 -0
  72. package/src/sqm/proof.js +25 -0
  73. package/src/sqm/report.js +1 -0
  74. package/src/sqm/workflow-onboarding.js +3 -39
  75. package/src/sqm/workflow-update.js +17 -6
  76. package/src/team-status-fence-store.js +32 -0
  77. package/src/team-threads.js +457 -0
package/README.md CHANGED
@@ -25,6 +25,15 @@ for configuration, tests and remaining host integration requirements.
25
25
 
26
26
  Local execution agent for SIMY Local Tasks and Agentic Loop runs.
27
27
 
28
+ To supervise an explicitly requested current goal after a finished Codex run, use
29
+ `simy delivery followup --provider codex --session SESSION_ID --previous-run-id RUN_ID --objective "Current goal and expected result"`.
30
+ It queues read-only reconciliation for the existing persistent supervisor. A new
31
+ goal archives the old run's evidence; the same ordinary `reported_done` goal keeps
32
+ its run identity and budgets while adopting managed outcome verification. Explicit
33
+ stops, opt-out, pending approvals and unknown effects remain in force. This command
34
+ does not install or activate a supervisor, and ordinary prompts never replace a
35
+ finished run automatically. See [runtime operation](docs/local-delivery-runtime.md).
36
+
28
37
  Action candidate search runs once for each authenticated live conversation context,
29
38
  and again when that context changes or **Refresh** is selected. Background binding
30
39
  renewal uses the lightweight binding endpoint instead of repeating candidate search.
@@ -125,9 +134,21 @@ For interrupted Agentic work, `GET /v1/agentic-loop/:id/recovery` provides a
125
134
  read-only objective and checkout fingerprint. Reconcile an uncertain side effect
126
135
  with `POST` to that endpoint (`action: reconcile`, authoritative
127
136
  `operation_status`, `observation`, `evidence_refs`) before resuming. A paused
128
- run retains the legacy metadata endpoint
129
- `POST /v1/agentic-loop/:id/continuation-ceilings` for compatibility. These fields
130
- no longer impose local limits and changing them never resets usage or resumes work. Both routes require the original authorized
137
+ Agentic run retains `POST /v1/agentic-loop/:id/continuation-ceilings` for an
138
+ explicit safety-ceiling extension under the progress policy. Token and retry
139
+ budgets are notification thresholds: they do not interrupt or stop useful work.
140
+ Agentic Loop checks a separate safety ceiling before each new provider call:
141
+ 15,000,000 weighted Provider tokens and 50 executor attempts by default, using
142
+ existing `provider_token_ceiling` and `attempt_ceiling` settings when present.
143
+ The final attempt may still be reviewed if token allowance remains. Active
144
+ processes finish and their results are retained even if usage crosses the ceiling.
145
+ A safety stop preserves run identity, output, usage and approval gates. Review
146
+ remaining work, explicitly extend the relevant ceiling within the existing
147
+ 15M-token/100-attempt validation bounds, then request continuation of the same
148
+ run. A notification-threshold increase alone cannot clear a safety stop; at the
149
+ maximum ceiling, further automatic calls remain blocked. Extension itself never
150
+ resets usage or resumes work. Local Task and managed Delivery behavior is not
151
+ changed by this Agentic safety gate. Both routes require the original authorized
131
152
  account, organization and device throughout the request.
132
153
 
133
154
  ## Provider reconnection and saved consent
@@ -160,6 +181,10 @@ login status, tasks, and updates separately. A Codex login check does not prove
160
181
  that a task can execute successfully; permissions and service availability are
161
182
  checked when work starts.
162
183
 
184
+ When SIMY cannot verify a provider connection or plugin status, the connection
185
+ panel shows **Checking connection status** and **Check again**. Check again
186
+ refreshes the status without changing the saved connection setting.
187
+
163
188
  The sidebar's **Organization** selector is global across Sessions, Tasks and SQM
164
189
  and shows active memberships of the connected SIMY account. Switching is available
165
190
  while work is running and opens browser authorization for the selected organization.
@@ -248,11 +273,13 @@ npm install -g @awak-app/simy-cli
248
273
  simy --daemon
249
274
  ```
250
275
 
251
- When an interactive `simy --daemon` starts without managed SIMY workflows,
252
- the CLI asks once before installing the Codex and Claude Code plugin
253
- sources and Codex/Claude opt-in SQM skills. Existing unrelated plugins
254
- and hooks are preserved. Use `--install-workflows` for explicit non-interactive
255
- installation or `--no-workflow-prompt` to start the daemon without asking.
276
+ When `simy --daemon` starts without managed SIMY workflows, the CLI automatically
277
+ prepares the Codex and Claude Code plugin sources, including in non-interactive
278
+ services. Background maintenance also prepares missing sources and refreshes
279
+ existing enabled plugins. Unrelated plugins and hooks are preserved.
280
+ `--install-workflows` remains available for explicit repair;
281
+ `--no-workflow-prompt` skips initial preparation and background installation of
282
+ missing sources. Provider plugin enablement and hook trust remain provider-owned.
256
283
 
257
284
  SQM checks apply when you explicitly select `@simy`, invoke the SQM skill, or ask to use SQM for the current task. Ordinary PR creation and `git push` do not opt in. Upgrading and restarting the daemon migrates the legacy global PR gate and shipped personal skills; unrelated hooks are preserved. Start a new coding session to reload skill instructions.
258
285
 
@@ -369,27 +396,23 @@ stops, failures and repeated attempts without new verified outcomes, with task
369
396
  identity, agent, reasons and outcome timestamps. Missing evidence is shown as
370
397
  unknown; open time is never counted as productive time. See [work activity](docs/work-activity.md).
371
398
 
372
- Codex work proceeds directly in the current conversation by default. The local
373
- `delivery-loop` and `agentic-delivery-loop` Codex skills are retired and are not
374
- redistributed by workflow updates. If the user explicitly requests SIMY Delivery
375
- Loop, use the CLI Agentic Loop rather than the retired local skill. Installation,
376
- hook trust and ordinary PR creation never authorize starting a loop or SQM.
377
- Internal lifecycle and workspace-lease libraries remain available for existing
378
- explicitly opted-in managed tasks; this packaging change does not start, trust,
379
- resume or reset any task. Quality and PR review skills remain available.
380
- Already open Codex sessions may cache the old skill until a new session starts.
381
-
382
- Claude Code retains its opt-in, hook-based Delivery Loop from an independent
383
- workflow source. Install the plugin, review and enable SIMY hooks with `/hooks`,
384
- then request `/simy:delivery-loop` for the task. Installing or updating the plugin
385
- never approves hook trust. See [hook delivery behavior and limits](docs/delivery-hooks.md).
399
+ The CLI prepares and maintains provider plugin sources automatically. Once the
400
+ user enables the SIMY plugin and permits its hooks, a nonempty prompt starts
401
+ checkpoint tracking in an eligible Codex or Claude conversation. The local Codex
402
+ `delivery-loop` and `agentic-delivery-loop` skills remain retired; prompt hook
403
+ context supplies checkpoint instructions without reinstalling those skills.
404
+ Existing tasks, stops, permission waits and provider/session opt-outs are preserved.
405
+ Use `simy delivery auto off --provider codex` (or `claude`) for a project override.
406
+ Completion in this standard flow is provider-reported, not independently verified.
407
+ See [hook delivery behavior and limits](docs/delivery-hooks.md).
386
408
  The Connection tab checks each provider's actual SIMY plugin inventory and
387
409
  shows installed/enabled, installed/disabled, missing, or unavailable separately
388
410
  from app sign-in. Claude enablement is checked from the home folder; project
389
411
  settings can differ, and project-scoped installations are labeled explicitly.
390
412
 
391
413
  `--no-auto-update` or `SIMY_AUTO_UPDATE=0` disables these background updates too;
392
- `--no-workflow-prompt` only suppresses the initial installation prompt.
414
+ `--no-workflow-prompt` skips initial preparation and background installation of
415
+ missing sources; existing sources still receive updates.
393
416
 
394
417
  This is the recommended installation because the CLI can safely install patch
395
418
  and minor releases, and the daemon restarts only when no Agentic Loop task is active. To run
@@ -1061,8 +1084,9 @@ that explicitly activated candidate when no live desktop owns the state director
1061
1084
  A live unknown/mismatched owner is preserved and reported. `runtime doctor` reports
1062
1085
  expected versus observed host identity, the all-state consistency monitor, and
1063
1086
  remaining task findings. `runtime legacy-hooks` routes only the exact shipped
1064
- 0.6.23/0.6.24 Codex hook entrypoints to the verified pin, retaining originals;
1065
- customized hooks and trust settings remain unchanged. `runtime workflows` refreshes
1087
+ 0.6.23/0.6.24 and commit 2052e787 Codex hook entrypoints to the verified pin.
1088
+ All exported helpers use that pin too; saved originals are never imported as
1089
+ a fallback. Customized hooks are reported as `customized_retained`. `runtime workflows` refreshes
1066
1090
  existing provider workflows without enabling them.
1067
1091
 
1068
1092
  `runtime doctor` also returns `taskReadiness` for every existing task: its original
@@ -1141,7 +1165,11 @@ unverified original model nor a different provider is substituted automatically.
1141
1165
 
1142
1166
  ### Manually consult SIMY about a stopped task
1143
1167
 
1144
- Ask “SIMY CLIに相談して” in Codex to use the `simy:diagnose` skill. Supply the original session/task ID when asking from another chat. A terminal can use the same entry even when the original conversation is unresponsive:
1168
+ Ask “SIMY CLIに相談して” or “原因を調べて” in Codex to use the `simy:diagnose` skill for diagnosis only. “直して続きを進めて” explicitly requests repair and continuation within existing permissions. The skill resolves the current conversation or an existing selected/original job reference; from another chat, select the original job or refer to its conversation. Only an ambiguous target requires clarification, and users normally do not need to type internal IDs.
1169
+
1170
+ The skill calls `delivery consult --scope diagnose` or `delivery consult --scope repair_continue --request-id USER_TURN_ID`. It preserves the chosen `--host`. Explicit references override existing selection, which overrides the observed current conversation; a missing or mismatched reference never redirects to another job. Repair delegates to the installed repair adapter, preserving stop, permission, budget and duplicate-work protections. If that adapter is absent, `recovery_unavailable` means no recovery was executed. Repair success is not original-task completion. See [the shared contract](docs/conversation-recovery-contract.md).
1171
+
1172
+ For terminal use and older CLIs, the read-only diagnosis command remains available even when the original conversation is unresponsive:
1145
1173
 
1146
1174
  ```sh
1147
1175
  ~/.simy/bin/simy delivery diagnose --provider codex --session ORIGINAL_SESSION --task-id ORIGINAL_TASK
@@ -1159,6 +1187,103 @@ Default issue selection is the first eight original acceptance criteria (or `obj
1159
1187
 
1160
1188
  Manual diagnosis and delivery reasoning share the scoped SQM knowledge loader. Partial environment overrides cannot mix a token, account, organization or endpoint with saved credentials; unavailable knowledge is reported without blocking the diagnosis. The brief response retains `knowledge.status`, `reason`, `suppliedCount`, and `bundleDigest`, separately from `knowledgeReferenceCount` and test execution (always zero for diagnosis). Truncated evidence is disclosed to both the analyst and the caller.
1161
1189
 
1190
+ ### Local Task delivery to an authorized device folder
1191
+
1192
+ A single-invocation, repository-write durable Local Task can include
1193
+ `execution_spec.local_destination`:
1194
+
1195
+ ```json
1196
+ {
1197
+ "device_id": "paired-device-id",
1198
+ "repository": "Organization/repository",
1199
+ "relative_directory": "deliverables"
1200
+ }
1201
+ ```
1202
+
1203
+ The repository must match the execution workspace and resolve through the CLI's
1204
+ existing authorized repository inventory. Alternatively, an explicitly delegated
1205
+ native Codex task may specify `authorized_root` instead of `repository`. That
1206
+ absolute directory must be within the existing native session's verified cwd or
1207
+ `sandbox.writableRoots`; the native adapter reports these roots after verifying
1208
+ current account, model, workspace and existing permissions. No new grant is made.
1209
+ The directory must already exist. Read-only tasks and stepped execution do not
1210
+ support delivery. A folder outside existing native roots must be selected through
1211
+ the original provider's workspace permission flow; a repository scan alone does
1212
+ not authorize arbitrary folder writes.
1213
+
1214
+ The worker verifies current output bytes, publishes each file without replacing
1215
+ existing files, and checks size, SHA-256 and path identity through local readback.
1216
+ An identical existing file is a safe replay; a different revision is preserved and
1217
+ delivery fails. Only verified local receipts are persisted in the private task
1218
+ snapshot; the original turn's result summary includes confirmed paths. A folder
1219
+ save is not a claim that an office application rendered or opened the file.
1220
+
1221
+ Offline claims are left to the existing durable lease/reconnect path; no local
1222
+ success or alternate device fallback is synthesized. Stop/lease checks also apply
1223
+ before file publication. Multiple file delivery may be partial if a later file
1224
+ fails; the turn does not report success in that case. Existing local outputs are
1225
+ kept for reconciliation without rerunning an uncertain provider operation.
1226
+
1227
+ Cloud inputs continue through the existing verified attachment store and outputs
1228
+ through the existing artifact contract. This contract does not advertise native
1229
+ browser/Photos automation: `browser_capability` remains unknown until an actual
1230
+ adapter is observed. It does not substitute a server browser for the owner's
1231
+ logged-in browser or grant authority to submit payment/card forms.
1232
+
1233
+ The same destination is accepted on the existing native
1234
+ `POST /v1/local-tasks/start` body as `local_destination`. It is included in the
1235
+ request fingerprint, checked before the native turn, and saved before terminal
1236
+ success. Reconcile returns the same Local Task, original conversation identity
1237
+ and verified destination receipts. A changed destination under the same request
1238
+ key conflicts instead of running another native turn. Direct repository tasks
1239
+ use the same private, ignored output folder layout as durable repository tasks,
1240
+ so outputs stay inside existing native workspace permissions.
1241
+
1242
+ Optional `local_photo_source: {device_id, asset_ids}` is a bounded PhotoKit
1243
+ metadata source for 1–20 explicitly selected asset identifiers. The local adapter
1244
+ requires existing Swift developer tooling (and never launches its installer),
1245
+ checks current Photos authorization, never requests permission or enumerates
1246
+ the library, and returns only selected IDs, dimensions, date and location. It
1247
+ does not export images or claim visual photo recognition. Missing authorization
1248
+ or unavailable selected IDs holds the work before provider invocation; users can
1249
+ supply the photo through the existing attachment flow. No native Photos or
1250
+ browser capability is inferred for a generic model. PhotoKit source reference:
1251
+ [PHPhotoLibrary](https://developer.apple.com/documentation/photos/phphotolibrary).
1252
+
1253
+ Owner-browser execution uses Crew's existing `browser_action` capability and
1254
+ paired Chrome extension queue, independently of CLI/native model execution.
1255
+ P10's companion browser change adds explicit `device_id` and an optional
1256
+ `interaction_scope: "observe_only"` that permits only observation primitives in
1257
+ an existing tab. It cannot type into card fields, click or submit. Its original
1258
+ queue task/device/deadline and result are retained across offline re-entry.
1259
+
1260
+ When the result gateway is offline after file delivery, the existing private task
1261
+ snapshot retains the exact terminal receipt. The same paired device retries only
1262
+ that receipt on reconnect/restart, preserving the original turn and files. It
1263
+ does not claim a new task or re-invoke the provider. A fenced lease remains
1264
+ unconfirmed and never becomes permission to replay the work.
1265
+
1266
+
1267
+ For a paired native Codex write, `workspace_mode: task_sandbox` remains artifact
1268
+ management metadata. It does not move the selected native conversation. After
1269
+ current account/model/cwd/sandbox and lease checks, the CLI places the execution's
1270
+ private output directory under that verified native cwd at
1271
+ `.simy/local-tasks/<execution-id>/outputs` and rebuilds the provider instruction.
1272
+ Both generation and final destination therefore stay inside existing native
1273
+ write permissions. No new sandbox root or cwd override is sent to the provider.
1274
+ Readonly native tasks and Claude's ordinary permission flow do not acquire this
1275
+ Codex-specific writable-root capability.
1276
+
1277
+ Web/Backend admission must preserve the optional destination and selected Photos
1278
+ request in the existing canonical native execution spec and request hash, pinned
1279
+ to the same selected paired device. These fields express intent; no `verified`
1280
+ flag, path in model context, previous receipt or old native root is a grant.
1281
+ Every new execution rechecks current device/account/link/session/lease and actual
1282
+ native roots or PhotoKit authorization. Follow-up preserves the chosen profile;
1283
+ a changed destination requires a fresh explicit admission, never a rewrite of an
1284
+ already-used request key. Revisions use the explicitly requested new artifact
1285
+ name; another revision already present at the same path is preserved and held.
1286
+
1162
1287
  Provider diagnostics distinguish `authentication`, `connection`, and `connection_ready`.
1163
1288
  If both login and SIMY connection are missing, `actions` lists both remedies in one
1164
1289
  report. Connection readiness does not certify model access or task completion.
@@ -1192,3 +1317,32 @@ Search covers the current recent window (up to 200 active conversations and,
1192
1317
  with `--include-archived`, up to 200 archived conversations); older entries outside
1193
1318
  that window are not searched. This view respects the saved
1194
1319
  Codex provider connection setting and never enables automatic continuation.
1320
+
1321
+ The existing per-provider heartbeat now includes `device_io` with
1322
+ `schema_version: local_device_io.v1`. It distinguishes Codex verified native-cwd
1323
+ staging/root delivery, Claude's provider permission flow, and selected Photos
1324
+ metadata requiring existing macOS authorization. It contains no paths or grants;
1325
+ normal provider/device readiness and runtime reauthorization still apply.
1326
+ The ordinary worker also sends `X-SIMY-Device-IO: local_device_io.v1` with its
1327
+ existing native protocol header. Web/gateway should preserve this marker and
1328
+ Backend should require it at claim/invocation when a destination/Photos profile
1329
+ is requested, so an old worker cannot silently ignore those required outputs.
1330
+
1331
+ ### Delivery entry routes
1332
+
1333
+ Crew owns `sessionLoopOptions` in the distributed loop kernel. Codex enrollment,
1334
+ Continue, follow-up, desktop controls and automatic continuation use purpose v2
1335
+ by default; old false/v1 enrollment flags cannot select the retired mode.
1336
+ Explicit strict/balanced policies and human acceptance criteria remain binding.
1337
+ Compatible saved tasks migrate without replacing identity, history or budget.
1338
+
1339
+ `~/.simy/bin/simy` is the installed CLI entry. Point any earlier shell alias or
1340
+ PATH symlink to it. `runtime legacy-hooks` switches known installed plugin caches
1341
+ to the same pin; check its output for customized hooks that still need review.
1342
+ `runtime supervisor-watchdog-install` registers the stable Node entry
1343
+ `~/.simy/bin/simy-runtime.mjs`, which resolves the verified runtime pin on each
1344
+ process start, instead of retaining a release-specific service path.
1345
+ When supervision is already live, install the candidate, request
1346
+ `runtime supervisor-handover --release ABSOLUTE_RELEASE`, and wait for activation.
1347
+ The watcher preserves the live supervisor while service registration is updated.
1348
+ No command here enrolls an unrelated conversation or renews an existing budget.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.7.16",
3
+ "version": "0.7.17",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,7 +43,7 @@
43
43
  "check:auto-update": "node --check ./src/auto-update.js && node --check ./scripts/auto-update-e2e.js && npm run check:sqm",
44
44
  "check:workflow-observation-sender": "node --check ./src/workflow-observation-sender.js",
45
45
  "check:sqm": "node --check ./src/sqm/advisory-guidance.js && node --check ./src/sqm/canonical.js && node --check ./src/sqm/validation.js && node --check ./src/sqm/reliability.js && node --check ./src/sqm/repository.js && node --check ./src/sqm/checkers.js && node --check ./src/sqm/bundle-store.js && node --check ./src/sqm/proof.js && node --check ./src/sqm/evidence-client.js && node --check ./src/sqm/session.js && node --check ./src/sqm/index.js && node --check ./src/sqm/command.js && node --check ./src/sqm/agent-skill.js && node --check ./src/sqm/workflow-onboarding.js && node --check ./src/sqm/workflow-update.js && node --check ./src/sqm/history-store.js && node --check ./src/sqm/review-worker.js && node --check ./scripts/sqm-complete-loop-e2e.js && node --check ./scripts/sqm-local-review-e2e.js",
46
- "check:real-provider-syntax": "node --check ./scripts/real-codex-probe.js && node --check ./scripts/real-claude-probe.js && node --check ./scripts/real-provider-console-fixture.js && node --check ./scripts/real-provider-console-e2e.js && node --check ./scripts/claude-parity-runtime-e2e.js && node --check ./scripts/claude-plugin-runtime-e2e.js",
46
+ "check:real-provider-syntax": "node --check ./scripts/real-codex-probe.js && node --check ./scripts/real-claude-probe.js && node --check ./scripts/real-provider-console-fixture.js && node --check ./scripts/real-provider-console-e2e.js && node --check ./scripts/claude-parity-runtime-e2e.js && node --check ./scripts/claude-plugin-runtime-e2e.js && node --check ./scripts/codex-plugin-runtime-e2e.js",
47
47
  "check:syntax": "node --check ./src/purpose-local-proof.js && node --check ./src/device-signing-identity.js && node --check ./src/index.js && node --check ./src/install-update.js && node --check ./src/agent.js && node --check ./src/connection-discovery.js && node --check ./src/connection-diagnostics.js && node --check ./src/launch-authorization.js && node --check ./src/codex-plugins/simy/hooks/connection-reminder.mjs && node --check ./src/codex-plugins/simy/hooks/dev-work.mjs && node --check ./src/dev-work-events.js && node --check ./src/browser.js && node --check ./src/web-api.js && node --check ./src/durable-local-task-worker.js && node --check ./src/durable-local-task-steps-contract.js && node --check ./src/bounded-local-task-subtasks-contract.js && node --check ./src/bounded-local-task-subtask-pool.js && node --check ./src/local-task-artifact-contract.js && node --check ./src/execution-capability-contract.js && node --check ./src/shutdown.js && node --check ./src/run-registry.js && node --check ./src/direct-executor.js && node --check ./src/execution-guardrail.js && node --check ./src/backend-executable.js && node --check ./src/login-shell-path.js && node --check ./src/hook-runtime.js && node --check ./src/provider-stream.js && node --check ./src/console/index.js && node --check ./src/console/app.js && node --check ./src/console/commands.js && node --check ./src/local-attachments.js && node --check ./src/repository-inventory.js && node --check ./src/session-store.js && node --check ./src/web-origin.js && node --check ./src/workspace-context.js && node --check ./src/orchestrator/index.js && node --check ./src/orchestrator/shared.js && node --check ./src/orchestrator/recovery.js && node --check ./src/orchestrator/risk.js && node --check ./src/orchestrator/contract.js && node --check ./src/orchestrator/completion-contract.js && node --check ./src/orchestrator/pr-preparation.js && node --check ./src/orchestrator/minimum-completion.js && node --check ./src/orchestrator/delivery/scripts/check-minimum-completion.mjs && node --check ./src/orchestrator/instruction.js && node --check ./src/orchestrator/problem-solving.js && node --check ./src/orchestrator/budget.js && node --check ./src/orchestrator/execution-io.js && node --check ./src/orchestrator/presentation.js && node --check ./src/orchestrator/result.js && node --check ./src/orchestrator/retry.js && node --check ./src/orchestrator/evidence.js && node --check ./src/orchestrator/independent-audit.js && node --check ./src/orchestrator/audit.js && node --check ./src/orchestrator/loop.js && node --check ./src/runner.js && node --check ./src/coding-run-store.js && node --check ./scripts/fixtures/completion-contract-fixture.js && node --check ./scripts/fixtures/fake-coding-backend.js && node --check ./scripts/e2e-terminal-evidence.js && node --check ./scripts/audit-gate-e2e.js && node --check ./scripts/completion-contract-e2e.js && node --check ./scripts/retry-circuit-e2e.js && node --check ./scripts/follow-up-charter-e2e.js && node --check ./scripts/task-routing-e2e.js && node --check ./scripts/executor-version-preflight-e2e.js && node --check ./scripts/blocked-recovery-e2e.js && node --check ./scripts/process-localization-e2e.js && node --check ./scripts/hypothesis-retries-e2e.js && node --check ./scripts/budget-enforcement-e2e.js && node --check ./scripts/local-task-default-workspace-e2e.js && node --check ./scripts/local-task-idempotency-e2e.js && node --check ./scripts/console-e2e-fixture.js && node --check ./scripts/console-e2e.js && node --check ./scripts/console-recovery-e2e-fixture.js && node --check ./scripts/console-recovery-e2e.js && node --check ./scripts/real-provider-console-fixture.js && node --check ./scripts/real-provider-console-e2e.js && node --check ./scripts/fullstack-cli-smoke.js && node --check ./scripts/check-package-contents.js && node --check ./src/codex-plugins/simy/hooks/delivery-loop.mjs && node --check ./src/delivery-hooks/command.js && node --check ./src/delivery-hooks/desktop.js && node --check ./src/delivery-hooks/supervisor.js && node --check ./src/delivery-hooks/recovery.js && node --check ./src/delivery-hooks/supervisor-owner.js && node --check ./src/delivery-hooks/supervisor-watchdog.js && node --check ./src/delivery-hooks/supervisor-service.js && node --check ./scripts/supervisor-watchdog-live-e2e.js && node --check ./src/codex-plugins/simy/hooks/recovery-incident.mjs && node --check ./src/session-supervision/automatic-continuation.js && node --check ./src/session-supervision/stop-notifications.js",
48
48
  "check:package": "node ./scripts/check-package-contents.js",
49
49
  "build:macos-installer:dev": "bash packaging/build-macos-arm64.sh --unsigned-dev",
@@ -52,6 +52,7 @@
52
52
  "build:macos-installer:production": "bash packaging/build-macos-arm64.sh --production",
53
53
  "test:e2e:claude-parity": "node ./scripts/claude-parity-runtime-e2e.js",
54
54
  "test:e2e:claude-plugin": "node ./scripts/claude-plugin-runtime-e2e.js",
55
+ "test:e2e:codex-plugin": "node ./scripts/codex-plugin-runtime-e2e.js",
55
56
  "test:e2e:flexible-delivery": "node --test --test-name-pattern=\"flexible prototype\" test/purpose-search.test.js",
56
57
  "test:e2e:sqm-faults": "node ./scripts/sqm-complete-loop-e2e.js"
57
58
  },
package/src/agent.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { readLocalPhotoSource } from "./local-photos.js";
2
+ import { resolveLocalTaskDestination, saveLocalTaskDestination } from "./local-task-destination.js";
3
+ import { observePairedNativeIdentities } from './native-provider-identity.js';
4
+ import { providerDelegationFromRequest } from './provider-delegation.js';
1
5
  import { createLedgerExecutionWorker, discoverLedgerWorkers } from './update-detachment/worker-ledger.js';
2
6
  import { normalizeProviderModelSelection } from "./provider-model-selection.js";
3
7
  import { assertConnectionScope, connectionScope, connectionDiagnostic } from "./connection-diagnostics.js";
@@ -147,6 +151,7 @@ import {
147
151
  import {
148
152
  createDurableLocalTaskApi,
149
153
  createDurableLocalTaskWorker,
154
+ prepareRepositoryWorkspace,
150
155
  } from "./durable-local-task-worker.js";
151
156
  import { createLocalTaskAttachmentStore } from "./local-task-attachment-store.js";
152
157
  import {
@@ -297,12 +302,20 @@ export async function startAgent({
297
302
  if (!link?.connected || expected && link.epoch !== expected.epoch) throw Object.assign(new Error("Reconnect this provider to SIMY before using it."), { code: "provider_link_disconnected" });
298
303
  return link;
299
304
  };
305
+ let nativeIdentityCapability = null, nativeIdentityActor = null, nativeIdentityObservedAt = 0, nativeIdentityReadInFlight = null;
306
+ const nativeActor = () => JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id]);
300
307
  let providerStatuses = { codex: { state: "unknown" }, claude: { state: "unknown" } };
301
308
  const providerRecoveryRevisions = { codex: 0, claude: 0 };
302
309
  const refreshAvailableCapabilities = () => {
303
310
  const features = contractSourceCapabilities.features;
304
311
  availableCapabilities = withCliContract({
305
312
  ...contractSourceCapabilities,
313
+ ...(features?.native_delegation === true || dependencies.observePairedNativeIdentities ? {
314
+ native_delegation: nativeIdentityActor === nativeActor() && isSessionValid(session, Date.now(), apiOrigin)
315
+ ? { ...nativeIdentityCapability, identities: (nativeIdentityCapability?.identities || []).filter(identity =>
316
+ providerLinks[identity.provider]?.connected && (providerLinks[identity.provider]?.epoch ?? null) === identity.connection_epoch) }
317
+ : { schema_version: 'native_delegation_capability.v1', device_id: session?.device_id || null, identities: [] },
318
+ } : {}),
306
319
  ...(purposeSigner ? { purpose_verification: purposeSigner } : {}),
307
320
  ...(features ? { features: { ...features, purpose_host_verification: Boolean(purposeSigner) && isSessionValid(session, Date.now(), apiOrigin), ...(Object.hasOwn(features, "sqm_local_review")
308
321
  ? { sqm_local_review: features.sqm_local_review === true && sqmEnabled && !pendingOrganization && desktopWorkAllowed() } : {}) } } : {}),
@@ -489,6 +502,24 @@ export async function startAgent({
489
502
  notifyDesktopStatusChange();
490
503
  }
491
504
  })]);
505
+ if ((contractSourceCapabilities.features?.native_delegation === true || dependencies.observePairedNativeIdentities) && session?.device_id && !nativeIdentityReadInFlight && (force || nativeIdentityActor !== nativeActor() || Date.now() - nativeIdentityObservedAt > 60000)) {
506
+ const actor = nativeActor();
507
+ const observation = (async () => {
508
+ const observed = await (dependencies.observePairedNativeIdentities || observePairedNativeIdentities)({
509
+ deviceId: session.device_id, providerStatuses, providerLinks });
510
+ if (!shuttingDown && actor === nativeActor()) {
511
+ nativeIdentityCapability = observed; nativeIdentityActor = actor; nativeIdentityObservedAt = Date.now();
512
+ refreshAvailableCapabilities();
513
+ notifyDesktopStatusChange();
514
+ // Official account observation must not delay the existing desktop
515
+ // startup/status path. Publish its catalog when it is actually ready.
516
+ if (deferStartupSynchronization) void heartbeat().catch(error => reportHeartbeatError(error, quiet));
517
+ }
518
+ })();
519
+ nativeIdentityReadInFlight = observation;
520
+ void observation.finally(() => { if (nativeIdentityReadInFlight === observation) nativeIdentityReadInFlight = null; }).catch(() => {});
521
+ if (!deferStartupSynchronization) await observation;
522
+ }
492
523
  const binaryStateChanged = ["codex", "claude"].some((provider) =>
493
524
  previous[provider]?.version !== providerStatuses[provider]?.version ||
494
525
  previous[provider]?.state !== providerStatuses[provider]?.state);
@@ -544,7 +575,21 @@ export async function startAgent({
544
575
  await callback?.(...args);
545
576
  await guard();
546
577
  };
547
- return { ...options, sessionRoot, beforeProviderInvocation: wrap(options.beforeProviderInvocation), beforeSpawn: wrap(options.beforeSpawn) };
578
+ return { ...options, sessionRoot, delegationOptions: { supervisionRoot: join(sessionRoot, "delivery-hooks"), pairedDeviceId: session?.device_id, providerLinkEpoch: link?.epoch ?? null, ...options.delegationOptions,
579
+ onWorkspaceAuthorized: async roots => {
580
+ await guard();
581
+ if (task.local_destination?.authorized_root) {
582
+ await resolveLocalTaskDestination({ local_destination: task.local_destination, operation: task.operation,
583
+ permission_mode: task.permission_mode, workspace_mode: task.workspace_mode, repository: task.repository }, {
584
+ deviceId: session?.device_id, resolveRepository: ensureRepositoryIndexed, nativeWritableRoots: roots,
585
+ });
586
+ }
587
+ task.native_writable_roots = [...roots];
588
+ await options.delegationOptions?.onWorkspaceAuthorized?.(roots);
589
+ await guard();
590
+ } },
591
+ beforeCompletion: options.beforeCompletion ? wrap(options.beforeCompletion) : null,
592
+ beforeProviderInvocation: wrap(options.beforeProviderInvocation), beforeSpawn: wrap(options.beforeSpawn) };
548
593
  };
549
594
  const codingRuntimeOptions = run => {
550
595
  const wrap = (custom, marker) => async context => {
@@ -856,7 +901,7 @@ export async function startAgent({
856
901
  notifyDesktopStatusChange();
857
902
  let deviceHeartbeatSucceeded = false;
858
903
  const requestedSession = session;
859
- const advertisedProviders = JSON.stringify(providerStatuses);
904
+ const advertisedProviders = JSON.stringify([providerStatuses, nativeIdentityCapability]);
860
905
  heartbeatInFlight = Promise.resolve()
861
906
  .then(async () => { if (enforceDesktopAccess) await desktopAccess.refresh(true); })
862
907
  .then(() =>
@@ -878,7 +923,7 @@ export async function startAgent({
878
923
  // browser authorization. Publish verified readiness after their probe.
879
924
  const publishVerifiedProviders = async () => {
880
925
  await readProviderStatuses();
881
- if (!shuttingDown && JSON.stringify(providerStatuses) !== advertisedProviders) {
926
+ if (!shuttingDown && JSON.stringify([providerStatuses, nativeIdentityCapability]) !== advertisedProviders) {
882
927
  await heartbeatDevice({ apiOrigin, apiBaseUrl: session.api_base_url, token: session.token,
883
928
  port, capabilities: refreshAvailableCapabilities(), repoInventory: repositoryInventory });
884
929
  }
@@ -1011,6 +1056,8 @@ export async function startAgent({
1011
1056
  createDurableLocalTaskWorker;
1012
1057
  durableLocalTaskWorker = createWorker({
1013
1058
  api: durableApi,
1059
+ enableBoundedSubtasks:
1060
+ availableCapabilities.features.bounded_local_task_subtasks === true,
1014
1061
  registry: localTaskRegistry,
1015
1062
  storageRoot: taskStorageRoot,
1016
1063
  workspaceRoot: dependencies.localTaskWorkspaceRoot,
@@ -1029,6 +1076,7 @@ export async function startAgent({
1029
1076
  return (dependencies.runLocalTask || startLocalTask)(task, providerRuntimeOptions(task, options, scope, link));
1030
1077
  },
1031
1078
  attachmentStore: localTaskAttachmentStore,
1079
+ readPhotoSource: dependencies.readLocalPhotoSource || readLocalPhotoSource,
1032
1080
  getDeviceId: () => session?.device_id || null,
1033
1081
  beginWork: () => acquireUpdateWork(updateManager),
1034
1082
  onError: (error) => reportDurableLocalTaskError(error, quiet),
@@ -1777,12 +1825,17 @@ export async function startAgent({
1777
1825
  });
1778
1826
  return;
1779
1827
  }
1780
- let requestFingerprint;
1828
+ let requestFingerprint, providerDelegation;
1781
1829
  try {
1830
+ providerDelegation = providerDelegationFromRequest(body, idempotencyKey);
1782
1831
  requestFingerprint = createLocalTaskRequestFingerprint({
1783
1832
  instruction,
1784
1833
  backend: body.backend,
1785
1834
  model: body.model,
1835
+ delegation: providerDelegation,
1836
+ localDestination: body.local_destination,
1837
+ localPhotoSource: body.local_photo_source,
1838
+ nativeIdentity: body.native_identity,
1786
1839
  operation: guardrail.operation,
1787
1840
  permissionMode: guardrail.permission_mode,
1788
1841
  workspaceMode,
@@ -1882,7 +1935,7 @@ export async function startAgent({
1882
1935
  workspace = {
1883
1936
  ...prepared,
1884
1937
  id,
1885
- workspace_path: indexedRepository.local_path,
1938
+ ...(await prepareRepositoryWorkspace({ repositoryPath: indexedRepository.local_path, expectedRepository: repository, localTaskId: id, prepareOutputs: body.permission_mode === "workspace_write" })),
1886
1939
  };
1887
1940
  } else {
1888
1941
  const id = createLocalTaskId();
@@ -1895,6 +1948,19 @@ export async function startAgent({
1895
1948
  })),
1896
1949
  };
1897
1950
  }
1951
+ if (body.local_destination?.authorized_root && providerDelegation?.provider !== "codex") {
1952
+ throw Object.assign(new Error("The selected folder requires an already authorized native Codex workspace."), { code: "local_task_destination_unconfirmed" });
1953
+ }
1954
+ let deliveryTask = null;
1955
+ const destinationSpec = { ...body, workspace_mode: workspaceMode, repository };
1956
+ const resolveDestination = () => resolveLocalTaskDestination(destinationSpec, {
1957
+ deviceId: session?.device_id,
1958
+ resolveRepository: ensureRepositoryIndexed,
1959
+ nativeWritableRoots: deliveryTask?.native_writable_roots,
1960
+ });
1961
+ let destination = body.local_destination && !body.local_destination.authorized_root ? await resolveDestination() : null;
1962
+ const photoContext = body.local_photo_source ? await (dependencies.readLocalPhotoSource || readLocalPhotoSource)(body.local_photo_source, { deviceId: session?.device_id }) : null;
1963
+ await authorizeDesktopWork(requestAccessScope);
1898
1964
  const stagedAttachments = await stagePreparedRunAttachments({
1899
1965
  runId: workspace.id,
1900
1966
  prepared: preparedAttachments,
@@ -1905,6 +1971,10 @@ export async function startAgent({
1905
1971
  instruction,
1906
1972
  backend: body.backend,
1907
1973
  model: body.model,
1974
+ delegation: providerDelegation,
1975
+ localDestination: body.local_destination,
1976
+ localPhotoSource: body.local_photo_source,
1977
+ nativeIdentity: body.native_identity,
1908
1978
  operation: guardrail.operation,
1909
1979
  permissionMode: guardrail.permission_mode,
1910
1980
  workspaceMode,
@@ -1915,6 +1985,7 @@ export async function startAgent({
1915
1985
  outputsRoot: workspace.outputs_root,
1916
1986
  outputsRootExpected: workspace.outputs_root_expected,
1917
1987
  attachments: stagedAttachments,
1988
+ localPhotoContext: photoContext,
1918
1989
  expectedOutputs: body.expected_outputs,
1919
1990
  scenarioPack,
1920
1991
  providerTokenBudget: body.provider_token_budget,
@@ -1927,6 +1998,7 @@ export async function startAgent({
1927
1998
  workflowStartIntent,
1928
1999
  purposeVerification,
1929
2000
  });
2001
+ deliveryTask = task;
1930
2002
  if (purposeVerification) await preparePurposeVerification(task, { session, apiOrigin, clientRequestId: idempotencyKey, keyPath: purposeKeyPath,
1931
2003
  authorize: async () => {
1932
2004
  await authorizeDesktopWork(requestAccessScope);
@@ -1937,7 +2009,27 @@ export async function startAgent({
1937
2009
  requestClaim.complete(task);
1938
2010
  requestClaimCompleted = true;
1939
2011
  const runLocalTask = dependencies.runLocalTask || startLocalTask;
1940
- const runtimeOptions = providerRuntimeOptions(task, { session, apiOrigin, beforeSpawn: async localTask => {
2012
+ const runtimeOptions = providerRuntimeOptions(task, { session, apiOrigin,
2013
+ beforeCompletion: body.local_destination ? async completed => {
2014
+ await authorizeDesktopWork(requestAccessScope);
2015
+ const current = await resolveDestination();
2016
+ if (destination && (current.root !== destination.root || current.directory !== destination.directory)) {
2017
+ throw Object.assign(new Error("The authorized save folder changed."), { code: "local_task_destination_unconfirmed" });
2018
+ }
2019
+ destination ||= current;
2020
+ completed.local_destination_receipts = [];
2021
+ await saveLocalTaskDestination({ task: completed, destination: current,
2022
+ assertActive: async () => {
2023
+ await authorizeDesktopWork(requestAccessScope);
2024
+ if (completed.stop_requested) throw Object.assign(new Error("Local delivery was stopped."), { code: "local_task_destination_unconfirmed" });
2025
+ },
2026
+ onReceipt: async receipt => {
2027
+ completed.local_destination_receipts.push(receipt);
2028
+ await persistLocalTask(completed);
2029
+ },
2030
+ });
2031
+ } : null,
2032
+ beforeSpawn: async localTask => {
1941
2033
  if (purposeWorkspaceBusy(localTask.workspace_path)) throw Object.assign(new Error("Independent verification owns this workspace."), { code: "purpose_verification_workspace_busy" });
1942
2034
  if (purposeVerification) {
1943
2035
  await authorizeDesktopWork(requestAccessScope);
@@ -1999,6 +2091,10 @@ export async function startAgent({
1999
2091
  return;
2000
2092
  }
2001
2093
  const body = await readJson(req);
2094
+ if (body.delegation || body.delegation_target) {
2095
+ json(res, 409, { error: 'Provider delegation requires the existing Local Task admission.', code: 'delegation_local_task_required' });
2096
+ return;
2097
+ }
2002
2098
  const instruction = String(body.instruction || "").trim();
2003
2099
  if (!instruction) {
2004
2100
  json(res, 400, { error: "instruction is required" });
@@ -2705,7 +2801,7 @@ export async function startAgent({
2705
2801
  if (codingRunMonitorTimer) clearInterval(codingRunMonitorTimer);
2706
2802
  updateManager?.stop?.();
2707
2803
  await Promise.all([stopDesktopTasks(), codexWorkPatternSync?.close?.(),
2708
- codexWorkPatternSyncInFlight, codingRunMonitorInFlight]);
2804
+ codexWorkPatternSyncInFlight, codingRunMonitorInFlight, nativeIdentityReadInFlight?.catch(() => {})]);
2709
2805
  try {
2710
2806
  await (dependencies.removeConnectionDiscovery || removeConnectionDiscovery)(
2711
2807
  connectionDiscovery,
@@ -2830,6 +2926,29 @@ export async function startAgent({
2830
2926
  sqmLocalReviewWorker?.retry?.(); notifyDesktopStatusChange();
2831
2927
  },
2832
2928
  async retrySqmEnvironment() { await authorizeDesktopWork(); sqmLocalReviewWorker?.retry?.(); notifyDesktopStatusChange(); },
2929
+ getTeamThreadsScope() {
2930
+ return { key: desktopScopeRevision(),
2931
+ fenceKey: createHash('sha256').update(JSON.stringify([apiOrigin, session?.account_id ?? null, session?.organization_id ?? null, session?.device_id ?? null])).digest('hex'),
2932
+ organizationId: session?.organization_id ?? null,
2933
+ authenticated: !shuttingDown && !pendingOrganization && isSessionValid(session, Date.now(), apiOrigin) && Boolean(session?.device_id),
2934
+ allowed: !shuttingDown && desktopWorkAllowed() && desktopAccessReady(desktopAccess.snapshot()) };
2935
+ },
2936
+ async requestTeamThreads(body, signal) {
2937
+ const selected = session;
2938
+ const scope = desktopScopeRevision();
2939
+ const off = body?.operation === 'consent' && body.enabled === false;
2940
+ if (!off) await authorizeDesktopWork(scope);
2941
+ if (shuttingDown || pendingOrganization || !isSessionValid(selected, Date.now(), apiOrigin) || selected !== session) throw new Error('Team sign-in required');
2942
+ const response = await (dependencies.fetch || globalThis.fetch)(webApiUrl('team-threads', { webOrigin: apiOrigin, apiBaseUrl: selected.api_base_url }), {
2943
+ method: body ? 'POST' : 'GET', headers: webApiHeaders({ token: selected.token }, body ? { 'Content-Type': 'application/json' } : {}),
2944
+ ...(body ? { body: JSON.stringify(body) } : {}), signal: AbortSignal.any([signal, AbortSignal.timeout(10_000)]), cache: 'no-store', redirect: 'error',
2945
+ });
2946
+ if (scope !== desktopScopeRevision() || selected !== session || signal.aborted) throw new Error('Team sign-in changed');
2947
+ if (!response.ok) throw Object.assign(new Error('Shared conversations unavailable'), { status: response.status });
2948
+ const raw = await response.text();
2949
+ if (raw.length > 1_048_576) throw new Error('Team response too large');
2950
+ return JSON.parse(raw);
2951
+ },
2833
2952
  async createDesktopWebSession() {
2834
2953
  const scope = await authorizeDesktopWork();
2835
2954
  const selected = session;
@@ -3298,6 +3417,7 @@ function capabilitiesFromInspections(codex, claude) {
3298
3417
  },
3299
3418
  },
3300
3419
  features: {
3420
+ team_threads: true,
3301
3421
  repository_scan_approval: true,
3302
3422
  executor_version_preflight: true,
3303
3423
  desktop_executor: true,
@@ -3308,6 +3428,7 @@ function capabilitiesFromInspections(codex, claude) {
3308
3428
  local_task_streaming: true,
3309
3429
  local_task_idempotency: true,
3310
3430
  local_task_model: true,
3431
+ native_delegation: true,
3311
3432
  progress_continuation_policy: true,
3312
3433
  local_task_reconciliation: true,
3313
3434
  local_task_control_plane: true,