@awak-app/simy-cli 0.7.35 → 0.7.37

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 (35) hide show
  1. package/README.md +50 -46
  2. package/package.json +1 -1
  3. package/src/agent.js +1 -1
  4. package/src/backend-executable.js +1 -1
  5. package/src/cli-contract.js +1 -1
  6. package/src/cli-observability.js +1 -1
  7. package/src/codex-plugins/simy/.codex-plugin/plugin.json +5 -5
  8. package/src/codex-plugins/simy/hooks/README.md +3 -3
  9. package/src/codex-plugins/simy/hooks/delivery-loop.mjs +2 -2
  10. package/src/codex-plugins/simy/skills/continue/SKILL.md +4 -4
  11. package/src/codex-plugins/simy/skills/continue/agents/openai.yaml +3 -3
  12. package/src/codex-plugins/simy/skills/diagnose/SKILL.md +2 -2
  13. package/src/codex-plugins/simy/skills/sqm-loop/SKILL.md +3 -3
  14. package/src/codex-plugins/simy/skills/sqm-loop/agents/openai.yaml +2 -2
  15. package/src/console/app.js +7 -7
  16. package/src/delivery-hooks/claude-workflow.md +1 -1
  17. package/src/delivery-hooks/command.js +5 -5
  18. package/src/desktop-host.js +58 -11
  19. package/src/index.js +1 -1
  20. package/src/local-task.js +1 -1
  21. package/src/orchestrator/presentation.js +1 -1
  22. package/src/runner.js +2 -2
  23. package/src/session-supervision/automatic-continuation.js +11 -11
  24. package/src/session-supervision/continuation-analysis.js +1 -1
  25. package/src/session-supervision/continuation-command.js +3 -3
  26. package/src/session-supervision/continuation-readiness.js +1 -1
  27. package/src/session-supervision/conversation-presentation.js +3 -3
  28. package/src/session-supervision/decision-presentation.js +8 -8
  29. package/src/session-supervision/index.js +13 -13
  30. package/src/session-supervision/owner-transport.js +53 -11
  31. package/src/session-supervision/remote-errors.js +2 -2
  32. package/src/session-supervision/remote-hosts.js +14 -3
  33. package/src/session-supervision/work-state.js +3 -3
  34. package/src/sqm/agent-skill.js +3 -3
  35. package/src/sqm/claude-plugin.js +1 -1
@@ -44,7 +44,7 @@ resume or reset a task. Prompt hooks enroll eligible conversations under CLI aut
44
44
  Use when the user requests this skill or SIMY Delivery Loop, or current hook context confirms automatic enrollment. Preserve that existing loop and proceed to checkpoints without starting again. Installing this plugin does not opt a task in. Do not run SQM unless separately requested.
45
45
 
46
46
  1. Resolve the exact original provider and session ID from the SIMY hook context or verified existing session admission. If no valid connection exists, report the observed missing capability and the smallest confirmed connection step. Never invent a session ID, change hook trust, bypass permissions, or replace the conversation. Codex settings can be opened at `codex://settings`; a hooks subpage deep link is not guaranteed.
47
- 2. Only start or resume a Delivery Loop when the user requests it. For a new regular Loop, use `simy delivery start --provider <provider> --session <exact-session-id> --objective '<user outcome and acceptance criteria>' --next-step '<concrete next action>'`. For a Loop that must track acceptance, use the existing managed specification with `--managed-file <task.json>` as described below: freeze the original user outcome, selected delivery level when one exists, and necessary applicable acceptance criteria before work. A regular start can only end as `reported_done`; do not present it as a fully verified Agentic Loop. A verified original `codex_app_server` admission and existing user continuation delegation can use `simy delivery continue --provider codex --session <exact-original-session-id>` without SessionStart when the user requests continuation. Preserve the original objective and acceptance. Never start another loop to reset limits or resume user-stopped work without a user request. Authorized automatic recovery from ordinary blockers is distinct from resuming user-stopped work.
47
+ 2. Only start or resume a Delivery Loop when the user requests it. For a new regular Loop, use `simy delivery start --provider <provider> --session <exact-session-id> --objective '<user outcome and acceptance criteria>' --next-step '<concrete next action>'`. For a Loop that must track acceptance, use the existing managed specification with `--managed-file <task.json>` as described below: freeze the original user outcome, selected delivery level when one exists, and necessary applicable acceptance criteria before work. A regular start can only end as `reported_done`; do not present it as a fully verified managed Delivery Loop. A verified original `codex_app_server` admission and existing user continuation delegation can use `simy delivery continue --provider codex --session <exact-original-session-id>` without SessionStart when the user requests continuation. Preserve the original objective and acceptance. Never start another loop to reset limits or resume user-stopped work without a user request. Authorized automatic recovery from ordinary blockers is distinct from resuming user-stopped work.
48
48
 
49
49
  3. Work in the current conversation with its normal permissions. Before ending each turn, submit a JSON checkpoint through standard input with `simy delivery checkpoint --provider <provider> --session <id> --file -` (for example, a shell heredoc). This updates SIMY state; it does not require writing a checkpoint file to the user's workspace. A file path remains supported when authorized.
50
50
 
@@ -225,18 +225,18 @@ export async function runDeliveryCommand(argv, { root = rootDirectory(), cwd = p
225
225
  }
226
226
  if (action === 'activity') return runActivityCommand(args, { root, output });
227
227
  if (!action || action === '--help') {
228
- output('simy delivery auto-continue on|off|status --provider codex|claude --session UUID — use the running app’s Auto Continue control and read its actual work state.');
229
- output('simy delivery observability status — show the state-only telemetry status for this device. The single default-OFF consent checkbox is in SIMY settings; disabling it stops uploads and discards pending telemetry. It never enables Auto-continue or starts a task.');
228
+ output('simy delivery auto-continue on|off|status --provider codex|claude --session UUID — Continue this conversation automatically: turns Delivery Loop on or off for one exact conversation through the running app’s existing control, and reads its actual work state.');
229
+ output('simy delivery observability status — show the state-only telemetry status for this device. The single default-OFF consent checkbox is in SIMY settings; disabling it stops uploads and discards pending telemetry. It never turns on Delivery Loop or starts a task.');
230
230
  output('simy delivery followup --provider codex --session ID --previous-run-id ID --objective TEXT — explicitly supervise the current goal after an exact finished run. A new goal archives the prior run; the same ordinary reported_done goal retains its identity and budgets for read-only reconciliation. Uses the existing supervisor; never reopens stopped, failed or budget-exhausted work.');
231
- output('simy delivery auto on|off|status --provider codex|claude — inspect or override CLI automatic start for this project directory (on by default). The next nonempty prompt in a conversation without a loop starts checkpoint tracking. Existing loops are preserved; provider hooks must already be enabled.');
231
+ output('simy delivery auto on|off|status --provider codex|claude — Start Delivery Loop automatically in this workspace: inspect or override automatic start for future conversations in this project directory (on by default). The next nonempty prompt in a conversation without a Delivery Loop starts checkpoint tracking. Existing loops are preserved; provider hooks must already be enabled.');
232
232
  output('simy delivery redelegate-native --provider codex --session ID --task-id ID --consent-ref REF — explicitly delegate current native local edit/test permissions to an exact legacy task. Preserves enrollment, history, budgets and stops; does not resume or grant external actions.');
233
233
  output('simy delivery runtime install|status|workflows — install an immutable local candidate, inspect its version/digest, or refresh existing plugin sources/cache while preserving enabled state and hook trust. Never enables a task.');
234
234
  output('simy delivery runtime doctor|desktop-install|desktop-activate|watch — audit live host/state/monitor alignment, or prepare a separate local desktop using the verified fixed runtime. Does not start or resume tasks.');
235
235
  output('simy delivery runtime policy-adopt [--task-id ID [--criterion-id ID]] — explicitly delegate decisions within existing permissions. Manual acceptance stays manual unless the user names the exact task and criterion to delegate its verifier.');
236
236
  output('simy delivery link_parent --provider codex|claude --session CHILD --parent-provider codex|claude --parent-session PARENT — record an explicit observed navigation relationship without starting work.');
237
237
  output('simy delivery supervise [--once] — service already opted-in tasks with this runtime; refuses an active desktop host.');
238
- output('simy delivery consult [--scope diagnose|repair_continue] [--request-id USER_TURN_ID] [--context-file context.json] [--provider codex|claude --session ID | --task-id ID] — use observed current conversation or selected original work; repair scope never implies completion.');
239
- output('simy delivery diagnose --provider codex|claude --session ID [--task-id ID] [--mode analyze|evidence] [--issues-file issues.json] [--detail brief|full] — advisory diagnosis in a separate process; never resumes the target. Exact --task-id alone is also supported.');
238
+ output('simy delivery consult [--scope diagnose|repair_continue] [--request-id USER_TURN_ID] [--context-file context.json] [--provider codex|claude --session ID | --task-id ID] — diagnose scope: check why it stopped, using the observed current conversation or selected original work; repair_continue scope never implies completion.');
239
+ output('simy delivery diagnose --provider codex|claude --session ID [--task-id ID] [--mode analyze|evidence] [--issues-file issues.json] [--detail brief|full] — advisory check of why it stopped, in a separate process; never resumes the target. Exact --task-id alone is also supported.');
240
240
  output('simy delivery evidence --provider codex|claude --session ID — read-only execution and acceptance receipts.');
241
241
  output('simy delivery activity [--watch] [--json] [--provider codex|claude] [--session ID] — local observations every 60 seconds; not productive time.');
242
242
  output('A task stops after repeated failures, and pauses for review when no progress is confirmed. Resume starts a new progress window.');
@@ -35,6 +35,7 @@ import { repairProviderPlugin } from './sqm/provider-plugin-repair.js';
35
35
  import { openCodexAppServer, verifyCodexArchiveState } from './session-supervision/codex-app-server.js';
36
36
  import { conversationSqmChecks, conversationSqmResult } from './session-supervision/sqm-presentation.js';
37
37
  import { stopOwnedDesktopHost } from "./desktop-force-quit.js";
38
+ import { relayToOwner } from './session-supervision/owner-transport.js';
38
39
  import { stopDesktopWorkForUpdate } from './desktop-update-stop.js';
39
40
  import { desktopAccessReady, desktopActionAllowed, requireDesktopAccess, validDesktopAccessPath } from './desktop-access.js';
40
41
  import { createActionDeadlineService } from './session-supervision/action-deadline.js';
@@ -117,9 +118,9 @@ function desktopActionError(action, error) {
117
118
  const messages = {
118
119
  session_workspace_changed: "The conversation workspace changed. Refresh and select it again.",
119
120
  session_metadata_unavailable: "Conversation details are unavailable. Refresh this provider and select the conversation again.",
120
- session_model_unavailable: "This provider did not expose the conversation model; automatic continuation is unavailable.",
121
+ session_model_unavailable: "This provider did not expose the conversation model; Delivery Loop is unavailable.",
121
122
  existing_loop: "This conversation already has a Delivery Loop. Open its existing supervision instead.",
122
- task_settled: "This task has finished. Start a new conversation before enabling automatic continuation.",
123
+ task_settled: "This task has finished. Start a new conversation before turning on Delivery Loop.",
123
124
  };
124
125
  if (messages[error?.code]) return messages[error.code];
125
126
  }
@@ -159,7 +160,23 @@ export async function startDesktopHost({ input = process.stdin, output = process
159
160
  const frames = createDesktopFrameWriter(output);
160
161
  const pendingRemoteReceipts = new Map();
161
162
  let operationJournal, ownerTransport;
162
- const writeEvent = event => { ownerTransport?.publish(event); frames.write(`${JSON.stringify(event)}\n`, event.type === 'status'); };
163
+ // An explicitly requested window update releases only its viewer. The same
164
+ // authenticated owner, tasks and pending permissions remain alive on IPC.
165
+ let windowUpdatePrepared = false, viewerDetached = false, relayStatus = null;
166
+ const detachViewer = () => { viewerDetached = true; frames.close(); };
167
+ output.on('error', error => {
168
+ if (windowUpdatePrepared && ['EPIPE', 'ERR_STREAM_DESTROYED'].includes(error.code)) detachViewer();
169
+ else if (!closed) void close().then(() => exit(1));
170
+ });
171
+ const writeEvent = event => {
172
+ if (event.type === 'status' && event.status) {
173
+ relayStatus = { value: event.status, at: Date.now() };
174
+ if (ownerTransport) event = { ...event, status: { ...event.status, windowUpdate: { version: 1, preservesWork: Boolean(ownerTransport), prepared: windowUpdatePrepared } } };
175
+ }
176
+ ownerTransport?.publish(event);
177
+ if (event.type === 'status') ownerTransport?.publishDesktop?.(event);
178
+ if (!viewerDetached) frames.write(`${JSON.stringify(event)}\n`, event.type === 'status');
179
+ };
163
180
  const emit = (event) => {
164
181
  const pending = event.type === 'result' ? pendingRemoteReceipts.get(event.id) : null;
165
182
  if (pending) {
@@ -181,6 +198,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
181
198
  let remoteHosts;
182
199
  const emitStatus = status => {
183
200
  status = projectCatalogFrame(status);
201
+ status = { ...status, windowUpdate: { version: 1, preservesWork: Boolean(ownerTransport), prepared: windowUpdatePrepared } };
184
202
  const updateSettings = updater?.snapshot?.() || {};
185
203
  status = { ...status, update: { ...status.update,
186
204
  auto_update_supported: updateSettings.auto_update_supported === true,
@@ -205,13 +223,14 @@ export async function startDesktopHost({ input = process.stdin, output = process
205
223
  void controllerPreferences.change(catalogScope, state => { state.catalog = catalog; }).catch(() => {});
206
224
  }
207
225
  if (updateProgress) status = { ...status, update: { ...status.update, ...updateProgress } };
226
+ ownerTransport?.publishDesktop?.({ type: 'status', status });
208
227
  const { sampledAt, ...content } = status;
209
228
  if (!lastStatus || !isDeepStrictEqual(content, lastStatus)) {
210
229
  encodedStatus = JSON.stringify(content).slice(1, -1);
211
230
  lastStatus = structuredClone(content);
212
231
  }
213
232
  const observation = sampledAt === undefined ? '' : `"sampledAt":${JSON.stringify(sampledAt)}${encodedStatus ? ',' : ''}`;
214
- frames.write(`{"type":"status","status":{${observation}${encodedStatus}}}\n`, true);
233
+ if (!viewerDetached) frames.write(`{"type":"status","status":{${observation}${encodedStatus}}}\n`, true);
215
234
  resolveFirstStatus?.(); resolveFirstStatus = null;
216
235
  };
217
236
  const root = env.SIMY_HOME?.trim() || join(homedir(), ".simy");
@@ -953,6 +972,15 @@ export async function startDesktopHost({ input = process.stdin, output = process
953
972
  scope: accessScope(status.access), controllerScope: controllerScope(status), deviceSession: reportDeviceSession(),
954
973
  }) && nativeDevelopmentReportsScope === accessScope(currentAccess)
955
974
  ? { ...structuredClone(cached?.nativeDevelopmentReports || {}), ...structuredClone(nativeDevelopmentReports) } : { state: 'loading', enrollments: [] };
975
+ // Local consent is durable independently of the optional server read or
976
+ // conversation enrichment. Include it even when that full projection is
977
+ // blocked; otherwise the native confirmation timer reports a false error.
978
+ const preferenceState = preferenceScope && desktopAccessReady(status.access)
979
+ ? controllerPreferences.peek(preferenceScope) : null;
980
+ const emailPreferences = Object.values(preferenceState?.conversations || {})
981
+ .flatMap(row => row.emailPreference ? [{ ...row.emailPreference.request, sync: row.emailPreference.state }] : []);
982
+ if (emailPreferences.length || status.nativeDevelopmentReports.preferences)
983
+ status.nativeDevelopmentReports.preferences = emailPreferences;
956
984
  // Cached enrichment must follow the current connection preference even
957
985
  // while a full projection is waiting on records or remote metadata.
958
986
  status.deliveryHooks.runs = linkedSessions(status.deliveryHooks.runs || []);
@@ -1439,6 +1467,11 @@ export async function startDesktopHost({ input = process.stdin, output = process
1439
1467
  let command; try { command = JSON.parse(line); } catch { return; }
1440
1468
  if (typeof command.id !== "string" || command.id.length > 128) return;
1441
1469
  if (relay) {
1470
+ if (['prepare_window_update', 'cancel_window_update'].includes(command.action)) {
1471
+ const authorized = Boolean(ownerTransport) && relayStatus && Date.now() - relayStatus.at < 10_000 && desktopAccessReady(relayStatus.value.access);
1472
+ if (authorized) windowUpdatePrepared = command.action === 'prepare_window_update';
1473
+ emit({ type: 'result', id: command.id, ok: Boolean(authorized) }); return;
1474
+ }
1442
1475
  if (command.action === 'session_renew_claude_owner') {
1443
1476
  emit({ type: 'result', id: command.id, ok: false, message: 'Use the verified local conversation host for connection recovery.' }); return;
1444
1477
  }
@@ -1479,6 +1512,14 @@ export async function startDesktopHost({ input = process.stdin, output = process
1479
1512
  emit({type:'result',id:command.id,ok:coordinator.snapshot().state==='connected' && (action!=='detach_update' || updater.snapshot().state==='updated')});
1480
1513
  await publish();return;
1481
1514
  }
1515
+ if (action === 'prepare_window_update' || action === 'cancel_window_update') {
1516
+ requireDesktopAccess(await accessSnapshot());
1517
+ if (action === 'prepare_window_update' && (!ownerTransport || closed || stoppingForUpdate))
1518
+ throw new Error('Keep using SIMY; the update will be available when this work finishes.');
1519
+ windowUpdatePrepared = action === 'prepare_window_update';
1520
+ emit({ type: 'result', id: command.id, ok: true });
1521
+ await publish(); return;
1522
+ }
1482
1523
  if (action === "quit") {
1483
1524
  if (command.forUpdate === true) {
1484
1525
  if (command.force !== true) throw new Error('Confirm before stopping unfinished tasks.');
@@ -1970,7 +2011,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
1970
2011
  await authorizeWork(commandScope);
1971
2012
  await actionAssociations.correct(command, analysisManager.project(candidate));
1972
2013
  } else if (action === "session_set_auto_continue") {
1973
- if (typeof command.enabled !== 'boolean') throw new Error('Choose whether automatic continuation is enabled.');
2014
+ if (typeof command.enabled !== 'boolean') throw new Error('Choose whether Delivery Loop is on.');
1974
2015
  let admission, target, providerEpoch;
1975
2016
  if (command.enabled) {
1976
2017
  const candidate = await selectedSession(command, { preferObserved: true });
@@ -2163,7 +2204,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
2163
2204
  if (fresh.cwd !== candidate.cwd) throw new Error('The conversation workspace changed. Refresh and retry.');
2164
2205
  const record = await readRecord(deliveryRoot, 'claude', command.session);
2165
2206
  if (fresh.isRunning || fresh.autoContinue || record?.autoContinue || record?.run && !TERMINAL.has(record.run.state))
2166
- throw Object.assign(new Error('Stop this conversation and turn off automatic continuation before organizing it.'), { code: 'claude_desktop_running' });
2207
+ throw Object.assign(new Error('Stop this conversation and turn off Delivery Loop before organizing it.'), { code: 'claude_desktop_running' });
2167
2208
  };
2168
2209
  await verifySelection();
2169
2210
  const operation = new AbortController();
@@ -2289,7 +2330,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
2289
2330
  }
2290
2331
  }
2291
2332
  };
2292
- if (command.action === "quit" || command.action === "prepare_update_restart" || command.action === "update" && (command.force === true || stoppingForUpdate)) {
2333
+ if (["prepare_window_update", "cancel_window_update"].includes(command.action) || command.action === "quit" || command.action === "prepare_update_restart" || command.action === "update" && (command.force === true || stoppingForUpdate)) {
2293
2334
  void execute();
2294
2335
  } else if (["login", "organization_select", "organization_cancel", "authorization_open", "authorization_copy"].includes(command.action)) {
2295
2336
  authorizationQueue = authorizationQueue.then(execute).catch(() => {});
@@ -2316,9 +2357,9 @@ export async function startDesktopHost({ input = process.stdin, output = process
2316
2357
  }).catch(() => {});
2317
2358
  }
2318
2359
  });
2319
- commands.on("close", () => { if (!closed) void close().then(() => exit(0)); });
2320
- if (lock && !dependencies.startAgent) {
2321
- ownerTransport = await createOwnerTransport({ root, nonce: lock.nonce, allowedActions, allowControllerArchive: env.SIMY_REMOTE_CHILD !== '1', dispatch: command => commands.emit('line', JSON.stringify(command)) });
2360
+ commands.on("close", () => { if (windowUpdatePrepared && ownerTransport && !closed) { detachViewer(); return; } if (!closed) void close().then(() => exit(0)); });
2361
+ if (lock && (!dependencies.startAgent || dependencies.createOwnerTransport)) {
2362
+ ownerTransport = await (dependencies.createOwnerTransport || createOwnerTransport)({ root, nonce: lock.nonce, allowedActions, allowControllerArchive: env.SIMY_REMOTE_CHILD !== '1', allowDesktopClient: env.SIMY_REMOTE_CHILD !== '1', dispatch: command => commands.emit('line', JSON.stringify(command)) });
2322
2363
  await publish();
2323
2364
  }
2324
2365
  // Register controls before awaiting initial presentation. A fast, authorized
@@ -2339,7 +2380,7 @@ function sessionActionError(error, action) {
2339
2380
  if (error instanceof AutoContinueUnavailableError) return error.message.replace(/[\r\n\x00-\x1f]/g, ' ').slice(0, 600);
2340
2381
  const message = String(error?.message || "");
2341
2382
  if (/already has a Delivery Loop|budgets cannot be reset/i.test(message)) return "This conversation already has a task. Open its existing supervision instead.";
2342
- if (/model.*unavailable|did not expose.*model/i.test(message)) return "This provider did not expose the conversation model; automatic continuation is unavailable.";
2383
+ if (/model.*unavailable|did not expose.*model/i.test(message)) return "This provider did not expose the conversation model; Delivery Loop is unavailable.";
2343
2384
  if (/workspace changed|metadata.*unavailable|session.*changed/i.test(message)) return "The conversation identity or workspace changed. Refresh and select it again.";
2344
2385
  if (/objective|acceptance|autonomy policy|budget/i.test(message)) return message.replace(/[\r\n\x00-\x1f]/g, " ").slice(0, 240);
2345
2386
  if (/question changed|answer is empty|session understanding|context/i.test(message)) return message.replace(/[\r\n\x00-\x1f]/g, " ").slice(0, 240);
@@ -2407,6 +2448,10 @@ export function isDesktopHostEntry(entry = process.argv[1]) {
2407
2448
 
2408
2449
  function desktopStartupError(error, fallbackMessage = "SIMY could not start. Check whether another SIMY window is already running, then reconnect.") {
2409
2450
  const conflict = error?.code === PROCESS_CONFLICT_CODE;
2451
+ if (error?.code === 'desktop_owner_update_required') return {
2452
+ type: 'error', code: 'desktop_owner_update_required',
2453
+ message: 'The existing SIMY service cannot hand off its window yet. Keep using the original window. After its work finishes, quit SIMY there and reopen it to update; the running work was left untouched.',
2454
+ };
2410
2455
  return {
2411
2456
  type: "error",
2412
2457
  ...(conflict ? { code: PROCESS_CONFLICT_CODE, conflicts: error.conflicts || [] } : {}),
@@ -2425,6 +2470,7 @@ export function startDesktopEntry(options = {}) {
2425
2470
  const diagnostics = createRuntimeDiagnostics(process.env.SIMY_HOME?.trim() || join(homedir(), ".simy"), { name: "desktop-host" });
2426
2471
  // stdout is the native JSONL channel; stderr is the only unstructured output
2427
2472
  // and the native window does not display it, so keep it for support.
2473
+ process.stderr.on('error', error => { if (error.code !== 'EPIPE') diagnostics.write('stderr closed: ' + error.code); });
2428
2474
  captureRuntimeOutput(diagnostics, { stderr: process.stderr });
2429
2475
  applyLoginShellPath({ diagnostics }).catch(() => {}).then(() => startDesktopHost(options)).then((value) => { host = value; }).catch((error) => {
2430
2476
  diagnostics.write(`startup failed: ${error?.stack || error}`);
@@ -2436,6 +2482,7 @@ export function startDesktopEntry(options = {}) {
2436
2482
  if (isDesktopHostEntry()) {
2437
2483
  try {
2438
2484
  const root = process.env.SIMY_HOME?.trim() || join(homedir(), '.simy');
2485
+ if (process.env.SIMY_DESKTOP_ATTACH_OWNER === '1' && await relayToOwner({ root, webOrigin: resolveWebOrigin(null, process.env), desktopClient: true })) process.exit(0);
2439
2486
  try { await adoptInstalledRuntime({ root }); }
2440
2487
  catch (error) {
2441
2488
  if (error.code === PROCESS_CONFLICT_CODE) throw error;
package/src/index.js CHANGED
@@ -161,7 +161,7 @@ async function runCommand(installMode) {
161
161
  Options:
162
162
  --version, -v Print the installed CLI version and exit
163
163
  --daemon, --deamon Run detached in the background
164
- --no-tui Disable the interactive Agentic Loop console
164
+ --no-tui Disable the interactive Delivery Loop console
165
165
  --no-open Do not open the browser when authorization is required
166
166
  --port <port> Bind a specific localhost port
167
167
  --host <url> Connect to a SIMY Web origin (default: ${DEFAULT_WEB_ORIGIN})
package/src/local-task.js CHANGED
@@ -1257,7 +1257,7 @@ function localTaskProviderInstruction(task) {
1257
1257
  ? "The managed output directory is reserved for final user-requested deliverables. Keep helper scripts, temporary files, caches, and intermediate files outside it (use the task workspace or an OS temporary directory), and remove those helper files before completion."
1258
1258
  : null;
1259
1259
  return [
1260
- "You are the local executor for a SIMY Local Task, not an Agentic Loop.",
1260
+ "You are the local executor for a SIMY Local Task, not a Delivery Loop.",
1261
1261
  buildWorkflowReportingInstruction(),
1262
1262
  "Complete this bounded task once. Do not create Git branches, commits, pull requests, audits, or automatic retries.",
1263
1263
  `Permission mode: ${task.permission_mode}.`,
@@ -117,7 +117,7 @@ export function summarizeCodingLoopEvent(snapshot, event) {
117
117
  };
118
118
  case "stopped":
119
119
  return {
120
- summary: "The local human operator stopped this Agentic Loop run.",
120
+ summary: "The local human operator stopped this Delivery Loop run.",
121
121
  result: "No further executor handoff will occur for this run.",
122
122
  next: "Start a new run or explicitly continue this one when ready.",
123
123
  };
package/src/runner.js CHANGED
@@ -452,7 +452,7 @@ export async function recoverLocalCodingRun(run, dependencies = {}, now = Date.n
452
452
  }
453
453
 
454
454
  function assertRunCanContinue(run) {
455
- if (run.operation || run.child) throw new Error("The selected Agentic Loop run is still active.");
455
+ if (run.operation || run.child) throw new Error("The selected Delivery Loop run is still active.");
456
456
  if (!RESUMABLE_STATES.has(run.status)) {
457
457
  throw new Error("Human guidance is available only when the selected run is waiting.");
458
458
  }
@@ -542,7 +542,7 @@ export async function continueLocalCodingRunAfterRepositoryApproval(
542
542
  if (!approvedRepository || !approvedPath) {
543
543
  throw new Error("An approved local repository and path are required.");
544
544
  }
545
- if (run.operation || run.child) throw new Error("The selected Agentic Loop run is still active.");
545
+ if (run.operation || run.child) throw new Error("The selected Delivery Loop run is still active.");
546
546
  if (run.status !== "waiting_human") {
547
547
  throw new Error("Repository approval is available only while the run is waiting.");
548
548
  }
@@ -6,15 +6,15 @@ function continuationFailureReason(error) {
6
6
  // Provider exceptions can contain private paths/output. Only known codes
7
7
  // select actionable UI copy; unrecognized failures retain a safe fallback.
8
8
  const reasons = {
9
- desktop_access_required: 'Sign in to an organization with SQM and Developer access before auto-continue can run.',
10
- provider_sign_in_required: 'Sign in to the original AI provider, then retry auto-continue.',
11
- unavailable: 'The local provider is unavailable. Open or configure it, then retry auto-continue.',
12
- unsupported: 'Update the original AI provider to a compatible version, then retry auto-continue.',
9
+ desktop_access_required: 'Sign in to an organization with SQM and Developer access before Delivery Loop can run.',
10
+ provider_sign_in_required: 'Sign in to the original AI provider, then retry Delivery Loop.',
11
+ unavailable: 'The local provider is unavailable. Open or configure it, then retry Delivery Loop.',
12
+ unsupported: 'Update the original AI provider to a compatible version, then retry Delivery Loop.',
13
13
  process_exited: 'The original Claude process has exited. Return to the original conversation, then refresh.',
14
14
  session_ended: 'This Claude session has ended. Return to the original conversation, then refresh.',
15
15
  hook_missing: 'Waiting for the original Claude hook. SIMY recovers automatically when the next verified prompt or response-end hook arrives. If no notifications arrive, check the SIMY plugin in the original Claude window.',
16
16
  };
17
- return Object.hasOwn(reasons, error?.code) ? reasons[error.code] : 'Could not start auto-continue. Check the original conversation and retry.';
17
+ return Object.hasOwn(reasons, error?.code) ? reasons[error.code] : 'Could not start Delivery Loop. Check the original conversation and retry.';
18
18
  }
19
19
 
20
20
  // Shared by explicit Continue and automatic enrollment. Analysis alone never
@@ -24,7 +24,7 @@ export async function continueAnalyzedSession({ candidate, manager, root, settin
24
24
  const link = await requireProviderLink({ root, provider: candidate.provider, expectedEpoch: settings.expectedProviderEpoch });
25
25
  const recommendationOptions = { allowDiagnosis: settings.purposeDriven === true && candidate.autoContinue === true };
26
26
  const recommendation = manager.requireRecommendation(candidate, recommendationOptions);
27
- if (candidate.isRunning || candidate.capabilities?.canContinue !== true) throw new Error('Automatic continuation is unavailable for this conversation.');
27
+ if (candidate.isRunning || candidate.capabilities?.canContinue !== true) throw new Error('Delivery Loop is unavailable for this conversation.');
28
28
  const context = await readContext(candidate, { signal });
29
29
  if (context.awaitingTurn || context.incomplete || !recommendation.contextDigest || context.contextDigest !== recommendation.contextDigest) {
30
30
  await manager.invalidate(candidate);
@@ -34,7 +34,7 @@ export async function continueAnalyzedSession({ candidate, manager, root, settin
34
34
  await beforeEnroll(candidate);
35
35
  await requireProviderLink({ root, provider: candidate.provider, expectedEpoch: link.epoch });
36
36
  manager.requireRecommendation(candidate, recommendationOptions);
37
- if (signal?.aborted) throw new Error('Automatic continuation was cancelled before enrollment.');
37
+ if (signal?.aborted) throw new Error('Delivery Loop was cancelled before enrollment.');
38
38
  return enroll({ root, provider: candidate.provider, session: candidate.session, ...settings, expectedProviderEpoch: link.epoch });
39
39
  }
40
40
 
@@ -46,7 +46,7 @@ function startingPresentation(candidate, status, reason) {
46
46
  const canPresent = work && !work.needsAttention && !['running', 'stopped', 'archived', 'approval', 'question'].includes(work.state);
47
47
  return { ...candidate, autoContinueStatus: status, autoContinueReason: reason,
48
48
  ...(canPresent ? { workState: { ...work, state: status,
49
- label: status === 'starting' ? '自動継続を準備中' : '自動復旧中', reason,
49
+ label: status === 'starting' ? 'Delivery Loop を準備中' : '自動復旧中', reason,
50
50
  nextAction: null, needsAttention: false } } : {}) };
51
51
  }
52
52
 
@@ -133,7 +133,7 @@ export function createAutomaticContinuation({ root, getManager, selectCandidate,
133
133
  retries.set(key, { attempt, count, at: now() + Math.min(60_000, 1000 * 2 ** Math.min(count - 1, 6)) });
134
134
  } else retries.delete(key);
135
135
  errors.set(key, { epoch: candidate.autoContinueEpoch, recoverable,
136
- reason: recoverable ? 'The AI connection is temporarily unavailable. Automatic continuation will retry when the connection is available.' : continuationFailureReason(error) });
136
+ reason: recoverable ? 'The AI connection is temporarily unavailable. Delivery Loop will retry when the connection is available.' : continuationFailureReason(error) });
137
137
  }
138
138
  }).finally(() => {
139
139
  if (controller.signal.aborted) { attempted.delete(key); retries.delete(key); }
@@ -152,14 +152,14 @@ export function createAutomaticContinuation({ root, getManager, selectCandidate,
152
152
  if (candidate.autoContinue === true && candidate.capabilities?.canAutoContinue === true &&
153
153
  !['managed', 'pending_takeover'].includes(candidate.binding?.mode)) {
154
154
  if (pending.has(key)) return startingPresentation(candidate, 'starting',
155
- 'Auto-continue is saved. Preparing to continue this conversation in the background.');
155
+ 'Delivery Loop is on. Preparing Delivery Loop for this conversation in the background.');
156
156
  const failure = errors.get(key);
157
157
  if (failure?.recoverable && failure.epoch === candidate.autoContinueEpoch)
158
158
  return startingPresentation(candidate, 'recovering', failure.reason);
159
159
  }
160
160
  if (candidate.autoContinue === true && candidate.isRunning !== true &&
161
161
  ['unmanaged', 'monitor'].includes(candidate.binding?.mode || 'unmanaged') && candidate.analysis?.state === 'needs_user') return {
162
- ...candidate, autoContinueStatus: 'needs_user', autoContinueReason: candidate.analysis.reason || 'Your decision is needed before automatic continuation can proceed.',
162
+ ...candidate, autoContinueStatus: 'needs_user', autoContinueReason: candidate.analysis.reason || 'Your decision is needed before Delivery Loop can proceed.',
163
163
  };
164
164
  const error = errors.get(`${candidate.provider}:${candidate.session}`);
165
165
  if (error && candidate.autoContinue === true && candidate.autoContinueEpoch === error.epoch &&
@@ -83,7 +83,7 @@ async function unavailableAnalysis(candidate, error, inspectProvider, signal) {
83
83
  if (signal?.aborted) return uncertain('Conversation analysis was cancelled.', 'cancelled');
84
84
  if (error?.code === 'provider_sign_in_required' || status?.state === 'signed_out') {
85
85
  const label = candidate.provider === 'claude' ? 'Claude Code' : 'Codex';
86
- return uncertain(`Sign in to ${label}. SIMY rechecks this conversation automatically when sign-in is detected. You can also retry manually. Unfinished work can continue automatically only when Auto Continue is on.`, 'provider_sign_in_required');
86
+ return uncertain(`Sign in to ${label}. SIMY rechecks this conversation automatically when sign-in is detected. You can also retry manually. Unfinished work continues only when "Continue automatically" is on.`, 'provider_sign_in_required');
87
87
  }
88
88
  return uncertain('The local AI app could not check this turn. Check its connection, then retry.', 'analysis_unavailable');
89
89
  }
@@ -8,7 +8,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
8
  export async function runContinuationCommand(args, { root, output, signal,
9
9
  request = requestOwnerContinuation } = {}) {
10
10
  if (args.includes('--help') || args.includes('-h')) {
11
- output('simy delivery auto-continue on|off|status --provider codex|claude --session UUID\nUses the running local SIMY app and its existing controls. ON saves delegation; only workState describes execution. Explicit stops, approvals, original goals and history remain in force.');
11
+ output('simy delivery auto-continue on|off|status --provider codex|claude --session UUID\nContinue this conversation automatically: turns Delivery Loop on or off for one exact conversation. Uses the running local SIMY app and its existing controls. ON saves delegation; only workState describes execution. Explicit stops, approvals, original goals and history remain in force.');
12
12
  return 0;
13
13
  }
14
14
  const [operation, ...flags] = args;
@@ -34,12 +34,12 @@ export async function runContinuationCommand(args, { root, output, signal,
34
34
  try {
35
35
  const receipt = await request({ root, signal, command: command('session_get_status') });
36
36
  output(JSON.stringify({ ...receipt, provider, session, accepted,
37
- message: receipt?.message || (operation === 'on' && accepted ? 'Auto Continue delegation saved. Read workState and its nextAction for actual progress; this acknowledgement does not prove a provider turn executed.' : undefined) }));
37
+ message: receipt?.message || (operation === 'on' && accepted ? 'Delivery Loop delegation saved. Read workState and its nextAction for actual progress; this acknowledgement does not prove a provider turn executed.' : undefined) }));
38
38
  return receipt?.ok ? 0 : 2;
39
39
  } catch (error) {
40
40
  if (!accepted) throw error;
41
41
  output(JSON.stringify({ ok: false, provider, session, accepted, code: error.code || 'status_unavailable',
42
- message: 'The setting was saved, but execution status could not be read. Check auto-continue status; do not repeat ON to infer execution.' }));
42
+ message: 'The setting was saved, but execution status could not be read. Check `simy delivery auto-continue status`; do not repeat ON to infer execution.' }));
43
43
  return 2;
44
44
  }
45
45
  }
@@ -54,5 +54,5 @@ export function projectContinuationWait(candidate, wait, now, noticeAfterMs = CO
54
54
  const needsAttention = wait.reasonCode === 'analysis_retry_exhausted' || now - wait.since >= noticeAfterMs;
55
55
  return { ...candidate, continuationWait: { id: wait.id, since: wait.since, reasonCode: wait.reasonCode, needsAttention },
56
56
  ...(needsAttention ? { autoContinueStatus: 'needs_user',
57
- autoContinueReason: 'Auto Continue is saved, but work has been waiting for execution conditions. Check the original conversation or refresh its connection.' } : {}) };
57
+ autoContinueReason: 'Delivery Loop is on, but work has been waiting for execution conditions. Check the original conversation or refresh its connection.' } : {}) };
58
58
  }
@@ -82,7 +82,7 @@ export function conversationPresentation(candidate = {}, projectedRun, { rawReco
82
82
  const pendingDecision = ['approval', 'question'].includes(work.state) || field('pendingApproval') && !field('pendingApproval').denied ||
83
83
  field('understandingQuestion') || managed.userQuestion;
84
84
  if (candidate.autoContinueStatus === 'save_failed') {
85
- ready = readiness('save_failed', 'danger', reason?.text || 'Could not save Auto Continue. Retry.');
85
+ ready = readiness('save_failed', 'danger', reason?.text || 'Could not save Delivery Loop. Retry.');
86
86
  } else if (candidate.autoContinueStatus === 'saving') {
87
87
  ready = readiness('saving', 'accent', 'Saving On…');
88
88
  } else if (candidate.autoContinue !== true) {
@@ -116,13 +116,13 @@ export function conversationPresentation(candidate = {}, projectedRun, { rawReco
116
116
  } else if (terminal) {
117
117
  ready = readiness('finished', 'secondary', state === 'reported_done' ? 'The AI reported completion; verification is pending.' : 'No current work to continue.');
118
118
  } else if (candidate.provider === 'claude' && candidate.isRunning === false && !['starting', 'recovering'].includes(candidate.autoContinueStatus)) {
119
- ready = readiness('original_turn_required', 'attention', 'Send one message in the original conversation. Auto Continue waits for its next verified hook.', action('open_original', 'Open original conversation'));
119
+ ready = readiness('original_turn_required', 'attention', 'Send one message in the original conversation. Delivery Loop waits for its next verified hook.', action('open_original', 'Open original conversation'));
120
120
  } else if (control && candidate.autoContinueStatus === 'supervising' && ['active', 'verification_pending'].includes(state) &&
121
121
  ownership === 'simy_continues' && blocker.state === 'ready' &&
122
122
  !field('executionUncertain') && !field('providerWarning') && !managed.nativeWait && !managed.purpose?.nativeBoundary) {
123
123
  ready = readiness('ready', 'accent', 'Will continue after this turn');
124
124
  } else if (['starting', 'recovering'].includes(candidate.autoContinueStatus)) {
125
- ready = readiness(candidate.autoContinueStatus, 'accent', reason?.text || 'Checking Auto Continue readiness.');
125
+ ready = readiness(candidate.autoContinueStatus, 'accent', reason?.text || 'Checking Delivery Loop readiness.');
126
126
  } else {
127
127
  ready = readiness('waiting', 'secondary', reason?.text || 'Checking whether the next step can be sent.', action('refresh', 'Refresh status'));
128
128
  }
@@ -78,8 +78,8 @@ export function sessionDecisionPresentation(candidate, run) {
78
78
  const acceptancePending = run.state === 'verification_pending';
79
79
  const providerName = candidate.provider === 'claude' ? 'Claude' : candidate.provider === 'codex' ? 'Codex' : 'AI';
80
80
  const reasons = {
81
- session_end: pair(`元の${providerName}会話が終了し、自動継続が止まっています。`, `The original ${providerName} session ended, stopping Auto Continue.`),
82
- process_exit: pair('元のAIが終了したため、自動継続できていません。', 'The original AI process exited. Auto Continue cannot proceed.'),
81
+ session_end: pair(`元の${providerName}会話が終了し、Delivery Loop が止まっています。`, `The original ${providerName} session ended, stopping Delivery Loop.`),
82
+ process_exit: pair('元のAIが終了したため、Delivery Loop を続けられません。', 'The original AI process exited. Delivery Loop cannot proceed.'),
83
83
  provider_error: pair('AIの実行でエラーが発生し、作業が止まっています。', 'An AI execution error stopped the work.'),
84
84
  shutdown_cancelled: pair('SIMYの終了時に作業が中断されました。', 'Work was interrupted when SIMY exited.'),
85
85
  hook_owner_mismatch: pair('元のAI会話の接続が変わりました。', 'The connection to the original AI session changed.'),
@@ -91,9 +91,9 @@ export function sessionDecisionPresentation(candidate, run) {
91
91
  const choices = [];
92
92
  if (run.canReplan && frozen) choices.push(choice(pair('AIに状況確認を任せる', 'Ask AI to investigate'), pair('目的・履歴・制限を維持して原因と安全な次の手順を調べます。操作の許可を追加することはありません。', 'Investigate the cause and next steps while keeping the goal, history and limits. No new permission is granted.'), 'delivery_replan', {}, 'primary'));
93
93
  const enablesAutoContinue = candidate.provider === 'codex' && run.managed !== true && run.state === 'stopped' && candidate.autoContinue !== true;
94
- if (run.canResume && frozen) choices.push(choice(pair(enablesAutoContinue ? '自動継続をオンにして再開' : acceptancePending ? '未完了の作業を続ける' : '再開を依頼する', enablesAutoContinue ? 'Resume with Auto Continue' : acceptancePending ? 'Continue remaining work' : 'Request resume'), pair(enablesAutoContinue ? '自動継続をオンにして元の会話の読み取り専用の状況確認から再開します。元の目的・権限・停止履歴・予算を維持します。' : acceptancePending ? '元の目的・履歴・制限を維持して残作業を確認します。最終確認を済ませたことにはしません。' : '保存した目的と履歴を引き継ぎます。接続や権限が不足する場合は、その内容を案内します。', enablesAutoContinue ? 'Turn on Auto Continue and resume with read-only diagnosis in the original conversation. Preserve its goal, permissions, stop history and budgets.' : acceptancePending ? 'Keep the goal, history and limits while checking what remains. This does not accept the result.' : 'Keep the saved goal and history. Any missing connection or permission will be shown.'), 'delivery_resume', {}, choices.length ? 'secondary' : 'primary'));
94
+ if (run.canResume && frozen) choices.push(choice(pair(enablesAutoContinue ? 'Delivery Loop をオンにして再開' : acceptancePending ? '未完了の作業を続ける' : '再開を依頼する', enablesAutoContinue ? 'Resume with Delivery Loop' : acceptancePending ? 'Continue remaining work' : 'Request resume'), pair(enablesAutoContinue ? 'Delivery Loop をオンにして元の会話の読み取り専用の状況確認から再開します。元の目的・権限・停止履歴・予算を維持します。' : acceptancePending ? '元の目的・履歴・制限を維持して残作業を確認します。最終確認を済ませたことにはしません。' : '保存した目的と履歴を引き継ぎます。接続や権限が不足する場合は、その内容を案内します。', enablesAutoContinue ? 'Turn on Delivery Loop and resume with a read-only status check in the original conversation. Preserve its goal, permissions, stop history and budgets.' : acceptancePending ? 'Keep the goal, history and limits while checking what remains. This does not accept the result.' : 'Keep the saved goal and history. Any missing connection or permission will be shown.'), 'delivery_resume', {}, choices.length ? 'secondary' : 'primary'));
95
95
  choices.push(choice(pair(`元の${providerName}会話を開く`, 'Open original conversation ↗'), pair('接続や権限の確認が必要な場合、元のAI会話で確認できます。会話を開くだけでは作業を再開しません。', 'Check the original conversation. Opening it does not resume work.'), 'open_original', {}, 'link'));
96
- if (candidate.autoContinue === true) choices.push(choice(pair('自動継続をオフにする', 'Turn off Auto Continue'), pair('今後の自動継続をオフにします。作業の履歴は残ります。', 'Turn off future automatic continuation and retain the history.'), 'session_set_auto_continue', { ...identity, enabled: false }));
96
+ if (candidate.autoContinue === true) choices.push(choice(pair('Delivery Loop をオフにする', 'Turn off Delivery Loop'), pair('今後の Delivery Loop をオフにします。作業の履歴は残ります。', 'Turn off Delivery Loop for future work and retain the history.'), 'session_set_auto_continue', { ...identity, enabled: false }));
97
97
  const actionable = frozen && (run.canReplan || run.canResume);
98
98
  return model('recovery', {
99
99
  title: pair(acceptancePending ? '最終確認を待ちながら、残作業を続けられます' : '作業が止まっています。対応を選んでください', acceptancePending ? 'Continue work while acceptance is pending' : 'Work stopped. Choose how to proceed'),
@@ -105,12 +105,12 @@ export function sessionDecisionPresentation(candidate, run) {
105
105
  candidate.capabilities?.canAutoContinue === true && candidate.autoContinueAvailability?.canEnable !== false) return model('continue', {
106
106
  title: pair('続けますか?', 'Continue?'),
107
107
  question: pair(clean(candidate.analysis.reason) || '許可済みの残作業があります。', clean(candidate.analysis.reason) || 'Authorized work remains.'),
108
- context: pair('自動継続はオフです。オンにすると、元の目的と権限の範囲で残作業を確認します。結果を受け入れる操作ではありません。', 'Auto Continue is off. Turning it on checks remaining work within the original goal and permissions. It does not accept the result.'),
109
- }, { choices: [choice(pair('自動継続をオンにする', 'Turn on Auto Continue'), pair('自動継続の設定を保存します。作業の開始や完了を証明するものではありません。', 'Save consent for Auto Continue. This does not confirm that work started or finished.'), 'session_set_auto_continue', { ...identity, enabled: true }, 'primary'),
108
+ context: pair('Delivery Loop はオフです。オンにすると、元の目的と権限の範囲で残作業を確認します。結果を受け入れる操作ではありません。', 'Delivery Loop is off. Turning it on checks remaining work within the original goal and permissions. It does not accept the result.'),
109
+ }, { choices: [choice(pair('Delivery Loop をオンにする', 'Turn on Delivery Loop'), pair('Delivery Loop の設定を保存します。作業の開始や完了を証明するものではありません。', 'Save consent for Delivery Loop. This does not confirm that work started or finished.'), 'session_set_auto_continue', { ...identity, enabled: true }, 'primary'),
110
110
  choice(pair('元の会話を開く', 'Open original conversation ↗'), pair('会話を開くだけでは作業を再開しません。', 'Opening the conversation does not resume work.'), 'open_original', {}, 'link')] });
111
111
  if (candidate.continuationWait?.needsAttention === true && candidate.autoContinue === true) return model('continuation_wait', {
112
- title: pair('自動継続の条件を確認してください', 'Check Auto Continue conditions'),
113
- question: pair('自動継続はオンのまま、実行条件の確認を待っています。元の会話や接続状態を確認してください。', 'Auto Continue is still on, waiting for execution conditions. Check the original conversation or its connection.'),
112
+ title: pair('Delivery Loop の条件を確認してください', 'Check Delivery Loop conditions'),
113
+ question: pair('Delivery Loop はオンのまま、実行条件の確認を待っています。元の会話や接続状態を確認してください。', 'Delivery Loop is still on, waiting for execution conditions. Check the original conversation or its connection.'),
114
114
  context: pair('設定・目的・停止指示・承認待ちは維持しています。この通知で作業を再開したり、権限を追加したりしません。', 'The setting, goal, stops and pending approvals are preserved. This notice does not resume work or grant permission.'),
115
115
  }, { choices: [choice(pair('状況を再確認する', 'Refresh status'), pair('条件を再確認します。', 'Recheck conditions.'), 'refresh', identity),
116
116
  choice(pair('元の会話を開く', 'Open original conversation ↗'), pair('元の会話を確認します。', 'Check the original conversation.'), 'open_original', {}, 'link')] }, 'helper');
@@ -24,7 +24,7 @@ import { readDispatchGate, rootFromDelivery, sessionDispatchReadiness } from '..
24
24
  // error sanitizer; native provider errors never acquire this type.
25
25
  export class AutoContinueUnavailableError extends Error {
26
26
  constructor(availability) {
27
- super(availability.reason || 'Automatic continuation is unavailable for this conversation.');
27
+ super(availability.reason || 'Delivery Loop is unavailable for this conversation.');
28
28
  this.code = availability.reasonCode;
29
29
  }
30
30
  }
@@ -54,8 +54,8 @@ function codexContinuationChannel(source, model, issue = null) {
54
54
  // The saved ON preference is consent, not execution readiness. Each blocker
55
55
  // names its own recovery route instead of reading as provider activity.
56
56
  const DISPATCH_BLOCKERS = {
57
- update_recovery_required: 'Auto-continue is saved, but SIMY is recovering from an interrupted update and cannot send work. Open Settings > App & updates and choose Try again.',
58
- writer_reconnect_required: 'Auto-continue is saved, but this conversation was opened before SIMY recovered from an update. Send one message in the original conversation so SIMY can verify the same process and reconnect it.',
57
+ update_recovery_required: 'Delivery Loop is on, but SIMY is recovering from an interrupted update and cannot send work. Open Settings > App & updates and choose Try again.',
58
+ writer_reconnect_required: 'Delivery Loop is on, but this conversation was opened before SIMY recovered from an update. Send one message in the original conversation so SIMY can verify the same process and reconnect it.',
59
59
  dispatch_history_quarantined: 'Earlier unconfirmed work in this conversation was quarantined during update recovery and will not be resent. Review the original conversation and start the next request yourself.',
60
60
  task_admission_blocked: 'This task needs fresh verified provider permissions before SIMY can send work. Continue it once in the original conversation.',
61
61
  };
@@ -63,7 +63,7 @@ function autoContinueState(record, isRunning, availability, readiness = null) {
63
63
  if (record?.autoContinue !== true) return { autoContinueStatus: 'off', autoContinueReason: null };
64
64
  const state = record.run?.state;
65
65
  if (readiness?.state === 'blocked' && !TERMINAL_RUNS.has(state)) return { autoContinueStatus: 'needs_user',
66
- autoContinueReason: DISPATCH_BLOCKERS[readiness.code] || 'Auto-continue is saved, but SIMY cannot send work for this conversation yet.' };
66
+ autoContinueReason: DISPATCH_BLOCKERS[readiness.code] || 'Delivery Loop is on, but SIMY cannot send work for this conversation yet.' };
67
67
  if (state === 'budget_exhausted') return { autoContinueStatus: 'needs_user', autoContinueReason: 'Local execution limits have been removed. Resume this same conversation to continue.' };
68
68
  if (completionClaimUnbound(record)) return { autoContinueStatus: 'watching', autoContinueReason: 'Verification of the recorded completion remains pending.' };
69
69
  if (state === 'reported_done') return { autoContinueStatus: 'watching', autoContinueReason: 'The provider reported completion; the actual result has not yet been confirmed.' };
@@ -383,7 +383,7 @@ async function discoverSessionCatalog({ root, openClient = openCodexAppServer, l
383
383
  } catch { /* Missing workspaces cannot grant an admission. */ }
384
384
  }
385
385
  const availability = archived ? { canEnable: false, reasonCode: 'session_archived',
386
- reason: 'Restore the original provider conversation before enabling automatic continuation.' }
386
+ reason: 'Restore the original provider conversation before turning on Delivery Loop.' }
387
387
  : autoContinueAvailability(run, claudeChannel || codexChannel);
388
388
  const upgradeable = canUpgradeDelivery(run);
389
389
  const continuationIssue = liveHook && run ? !run.managed && !upgradeable
@@ -469,7 +469,7 @@ function hasAutoContinueAdmission(record, provider, session) {
469
469
  (provider !== 'codex' || typeof admission.model === 'string' && admission.model.trim());
470
470
  }
471
471
  function requireAutoContinueTask(record) {
472
- if (ownerRenewalPending(record)) throw new AutoContinueUnavailableError({ reasonCode: 'owner_renewal_pending', reason: 'Finish the original conversation connection recovery before enabling Auto Continue.' });
472
+ if (ownerRenewalPending(record)) throw new AutoContinueUnavailableError({ reasonCode: 'owner_renewal_pending', reason: 'Finish the original conversation connection recovery before turning on Delivery Loop.' });
473
473
  if (record?.run?.state === 'stopped') throw new AutoContinueUnavailableError({ reasonCode: 'task_stopped', reason: 'This task was stopped. Review the original conversation and explicitly request resumption before continuing.' });
474
474
  if (record?.run && !record.run.managed && !canUpgradeDelivery(record.run)) throw Object.assign(new Error('This conversation already has a Delivery Loop. Use its existing controls.'), { code: 'existing_loop' });
475
475
  if (TERMINAL_RUNS.has(record?.run?.state)) throw new AutoContinueUnavailableError({ reason: 'This supervised task has settled. Review its existing status before continuing.', reasonCode: 'task_settled' });
@@ -487,7 +487,7 @@ export async function saveSessionAutoContinuePreference(options) {
487
487
  }
488
488
 
489
489
  export async function setSessionAutoContinue({ root, provider, session, enabled, continuationPolicy, initialContinuationPolicy, ...options }) {
490
- if (!['codex', 'claude'].includes(provider) || typeof enabled !== 'boolean') throw new Error('Unsupported automatic continuation request.');
490
+ if (!['codex', 'claude'].includes(provider) || typeof enabled !== 'boolean') throw new Error('Unsupported Delivery Loop request.');
491
491
  const link = enabled ? await requireProviderLink({ root, provider, expectedEpoch: options.expectedProviderEpoch }) : null;
492
492
  let discoveredAdmission;
493
493
  const preferenceOnly = options.preferenceOnly === true;
@@ -496,7 +496,7 @@ export async function setSessionAutoContinue({ root, provider, session, enabled,
496
496
  const purposeRules = enabled && provider === 'codex' && !preferenceOnly ? await loadPurposeRuleSet() : null;
497
497
  if (enabled && !preferenceOnly) {
498
498
  const record = await readRecord(root, provider, session);
499
- if (record?.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before enabling automatic continuation.'), { code: 'session_archived' });
499
+ if (record?.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before turning on Delivery Loop.'), { code: 'session_archived' });
500
500
  requireAutoContinueTask(record);
501
501
  if (!hasAutoContinueAdmission(record, provider, session)) {
502
502
  // Only the host's verified discovery catalog supplies this option; command
@@ -533,8 +533,8 @@ export async function setSessionAutoContinue({ root, provider, session, enabled,
533
533
  // identity with the earlier catalog snapshot, including a mismatch.
534
534
  previous = previous?.admission ? previous : { ...(previous || { schemaVersion: 1, provider, session, cwd: discoveredAdmission.cwd, run: null }), admission: discoveredAdmission };
535
535
  }
536
- if (!previous) throw new Error('Refresh this conversation before changing automatic continuation.');
537
- if (enabled && previous.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before enabling automatic continuation.'), { code: 'session_archived' });
536
+ if (!previous) throw new Error('Refresh this conversation before changing Delivery Loop.');
537
+ if (enabled && previous.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before turning on Delivery Loop.'), { code: 'session_archived' });
538
538
  if (enabled && !preferenceOnly) {
539
539
  const channel = provider === 'claude' ? await liveClaudeChannel(previous,
540
540
  { session, cwd: previous.cwd, observation: { source: previous.admission?.source } }, options.inspectProcess || processIdentity)
@@ -609,7 +609,7 @@ export async function trackSession({ root, provider, session, tracked = true, op
609
609
  const cwd = await realpath(metadata.cwd);
610
610
  await updateRecord(root, provider, session, async previous => {
611
611
  await requireProviderLink({ root, provider, expectedEpoch: link.epoch });
612
- if (previous?.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before enabling automatic continuation or tracking.'), { code: 'session_archived' });
612
+ if (previous?.providerArchived === true) throw Object.assign(new Error('Restore the original provider conversation before turning on Delivery Loop or tracking.'), { code: 'session_archived' });
613
613
  if (previous && previous.cwd !== cwd) throw new Error('Session workspace changed; refresh its metadata.');
614
614
  return { ...(previous || { schemaVersion: 1, provider, session, cwd, run: null }),
615
615
  ...(link.consentEpoch ? { providerConsentEpoch: link.consentEpoch } : {}), tracked: true, monitor: true,
@@ -663,7 +663,7 @@ export async function enrollSession(options) {
663
663
  await requireClaudeChannel(await readRecord(root, provider, session), metadata, inspectProcess);
664
664
  } else {
665
665
  metadata = await monitorSession({ root, provider, session, monitor: true, openClient, expectedProviderEpoch: link.epoch, now });
666
- if (!metadata.model) throw new Error('Codex did not expose this conversation’s model; automatic continuation is unavailable.');
666
+ if (!metadata.model) throw new Error('Codex did not expose this conversation’s model; Delivery Loop is unavailable.');
667
667
  }
668
668
  const cwd = await realpath(metadata.cwd);
669
669
  const initialPlan = initialDiagnosisPlan(objective.trim(), contextBootstrap);
@@ -700,7 +700,7 @@ export async function continueSession(options) {
700
700
  options = sessionLoopOptions(options);
701
701
  const record = await readRecord(options.root, options.provider, options.session);
702
702
  if (options.upgradeRunId && (record?.run?.id !== options.upgradeRunId || record.run.managed))
703
- throw Object.assign(new Error('The original task changed before automatic continuation started.'), { code: 'session_task_changed' });
703
+ throw Object.assign(new Error('The original task changed before Delivery Loop started.'), { code: 'session_task_changed' });
704
704
  if (record?.run && !record.run.managed) {
705
705
  // The user asked to continue this task. Adopt its goal and failure budget,
706
706
  // rather than enrolling the generic context-bootstrap goal as a new run.