@awak-app/simy-cli 0.5.34 → 0.5.35

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.
package/README.md CHANGED
@@ -76,6 +76,13 @@ marketplace still points to SIMY’s generated local source. Plugin versions inc
76
76
  receive a fresh cache. Use a new Codex task to pick up updated skills; running
77
77
  tasks are not restarted. Missing or disabled plugins are not enabled automatically.
78
78
  Failed refreshes retry on the next check without blocking daemon startup.
79
+ The desktop Sessions page discovers existing provider conversations and lets users
80
+ choose monitoring or supported session supervision with a goal, acceptance
81
+ criteria, an autonomy policy, and finite budgets. Codex enrollment uses an exact
82
+ app-server session and checks writer ownership before each continuation. Claude
83
+ can continue through a verified live SIMY hook in its original process; sessions
84
+ without that hook support monitoring only, and cold takeover remains unavailable. See [session supervision](docs/session-supervision.md).
85
+
79
86
  The plugin also provides an opt-in, hook-based Delivery Loop in the user's existing
80
87
  Codex or Claude Code conversation. Install the plugin, review and enable SIMY hooks
81
88
  with `/hooks`, then request `/simy:delivery-loop` for the task. Installing or updating
@@ -104,6 +111,40 @@ with an absolute SIMY Web origin for another deployment. Non-loopback hosts
104
111
  must use HTTPS. `SIMY_WEB_ORIGIN` and the legacy `SIMY_API_ORIGIN` remain
105
112
  available for automation, but the command-line flag takes precedence.
106
113
 
114
+ Work Pattern discovery checks the connected account's server-side personal
115
+ setting before reading local Codex or Claude Code history. An explicit opt-out
116
+ prevents both local inspection and upload. The daemon reads bounded user and
117
+ assistant conversation chunks from active and archived Codex sessions and
118
+ Claude Code projects, excluding delegated agents, tool output, reasoning, and
119
+ injected runtime context. Dated messages outside the configured lookback are
120
+ excluded even when their conversation continues today. Legacy transcripts with
121
+ no message timestamps use the file timestamp as a fallback. The local cursor stores only opaque hashes and
122
+ timestamps scoped to the connection, account, and device. Uploads require the
123
+ `user_conversation.v2` Web, Crew, and Backend contract and exact per-chunk
124
+ receipts before the cursor advances. Deploy those companion changes before
125
+ enabling this CLI version; earlier receivers cannot acknowledge v2 history.
126
+
127
+ History sync runs only in daemon mode, at startup and then at most every 15
128
+ minutes, and only while a Codex or Claude Code executable passes the version
129
+ check. The endpoint is the session's `api_base_url` (for app-dev,
130
+ `https://app-dev.simy.one/api/local-cli/`). The history roots are
131
+ `$CODEX_HOME/sessions`, `$CODEX_HOME/archived_sessions`, and
132
+ `$CLAUDE_CONFIG_DIR/projects`, which default to `~/.codex` and `~/.claude`. When
133
+ the daemon skips a sync, it logs the reason once to stderr. Reasons include no
134
+ daemon mode, no valid session, and no compatible provider with each provider's
135
+ status. To test with synthetic history only, point every root at temp
136
+ directories and run the daemon in the foreground so its log stays visible.
137
+ Stop it with Ctrl-C after the `uploaded` line:
138
+
139
+ ```sh
140
+ export SIMY_HOME="$(mktemp -d)" CODEX_HOME="$(mktemp -d)" CLAUDE_CONFIG_DIR="$(mktemp -d)"
141
+ # write synthetic *.jsonl files under $CODEX_HOME/sessions and $CLAUDE_CONFIG_DIR/projects/<project>/
142
+ SIMY_DAEMON_CHILD=1 simy --daemon --host https://app-dev.simy.one --no-auto-update --no-workflow-prompt
143
+ ```
144
+
145
+ `SIMY_DAEMON_CHILD=1` keeps the daemon attached to the terminal. The sync
146
+ cursor is written under `$SIMY_HOME/work-pattern-history-sync/`.
147
+
107
148
  The CLI starts a loopback HTTP agent on a random available port, keeps a
108
149
  48-hour, origin-scoped local session, and opens a native coding chat. The first
109
150
  chat message creates the remote audit ledger and starts the local Agentic Loop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.34",
3
+ "version": "0.5.35",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,12 +36,13 @@
36
36
  "test:e2e:completion-contract": "node ./scripts/completion-contract-e2e.js",
37
37
  "test:e2e:console:real-provider": "node ./scripts/real-provider-console-e2e.js",
38
38
  "test:e2e:console:fullstack": "node ./scripts/fullstack-cli-smoke.js",
39
- "check": "npm run check:auto-update && node --check ./src/desktop-executor.js && node --check ./src/local-task.js && node --check ./scripts/desktop-executor-e2e.js && node --check ./scripts/pre-guardrail-e2e.js && node --check ./scripts/fixtures/fake-desktop-provider.js && npm run check:syntax && npm run check:real-provider-syntax && npm test && npm run check:package",
39
+ "check": "npm run check:auto-update && node --check ./src/desktop-executor.js && node --check ./src/local-task.js && node --check ./scripts/desktop-executor-e2e.js && node --check ./scripts/pre-guardrail-e2e.js && node --check ./scripts/fixtures/fake-desktop-provider.js && npm run check:syntax && npm run check:session-supervision-syntax && npm run check:real-provider-syntax && npm test && npm run check:package",
40
+ "check:session-supervision-syntax": "node --check ./src/session-supervision/index.js && node --check ./src/session-supervision/codex-app-server.js && node --check ./src/session-supervision/claude-sessions.js && node --check ./src/session-supervision/original-session-target.js",
40
41
  "check:auto-update": "node --check ./src/auto-update.js && node --check ./scripts/auto-update-e2e.js && npm run check:sqm",
41
42
  "check:workflow-observation-sender": "node --check ./src/workflow-observation-sender.js",
42
43
  "check:sqm": "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/review-worker.js && node --check ./scripts/sqm-complete-loop-e2e.js && node --check ./scripts/sqm-local-review-e2e.js",
43
44
  "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",
44
- "check:syntax": "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/codex-plugins/simy/hooks/connection-reminder.mjs && 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/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",
45
+ "check:syntax": "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/codex-plugins/simy/hooks/connection-reminder.mjs && 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/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",
45
46
  "check:package": "node ./scripts/check-package-contents.js",
46
47
  "build:macos-installer:dev": "bash packaging/build-macos-arm64.sh --unsigned-dev",
47
48
  "build:macos-installer": "bash packaging/build-macos-arm64.sh --production",
package/src/agent.js CHANGED
@@ -95,7 +95,10 @@ import {
95
95
  classifyExecutionRequirement,
96
96
  EXECUTION_GUARDRAIL_POLICY_VERSION,
97
97
  } from "./execution-guardrail.js";
98
- import { normalizeWorkflowObservationBinding } from "./workflow-observation-sender.js";
98
+ import {
99
+ normalizeWorkflowObservationBinding,
100
+ normalizeWorkflowStartIntent,
101
+ } from "./workflow-observation-sender.js";
99
102
  import {
100
103
  createLocalTask,
101
104
  createLocalTaskId,
@@ -132,6 +135,7 @@ import {
132
135
  createSqmLocalReviewApi,
133
136
  createSqmLocalReviewWorker,
134
137
  } from "./sqm/review-worker.js";
138
+ import { createCodexWorkPatternSync, workPatternHistoryHomes } from "./codex-work-pattern-sync.js";
135
139
  import {
136
140
  publishConnectionDiscovery,
137
141
  removeConnectionDiscovery,
@@ -232,6 +236,78 @@ export async function startAgent({
232
236
  };
233
237
  let durableLocalTaskWorker = null;
234
238
  let sqmLocalReviewWorker = null;
239
+ let codexWorkPatternSync = null;
240
+ let codexWorkPatternSyncInFlight = null;
241
+
242
+ let codexWorkPatternSkipReason = null;
243
+ const pollCodexWorkPatterns = () => {
244
+ if (shuttingDown) return;
245
+ const skipReason = codexWorkPatternSkipReasonFor({
246
+ daemon,
247
+ sessionValid: isSessionValid(session, Date.now(), apiOrigin),
248
+ capabilities: availableCapabilities,
249
+ });
250
+ // Report each distinct skip reason once so a skipped sync is never silent
251
+ // without repeating the same line on every heartbeat.
252
+ if (skipReason !== codexWorkPatternSkipReason) {
253
+ codexWorkPatternSkipReason = skipReason;
254
+ if (skipReason) reportCodexWorkPatternSyncStatus(`skipped: ${skipReason}`, quiet);
255
+ }
256
+ if (skipReason) return;
257
+ if (!codexWorkPatternSync) {
258
+ if (dependencies.codexWorkPatternSync) {
259
+ codexWorkPatternSync = dependencies.codexWorkPatternSync;
260
+ } else {
261
+ const homes = workPatternHistoryHomes({
262
+ codexHome: dependencies.codexHome,
263
+ claudeHome: dependencies.claudeHome,
264
+ });
265
+ reportCodexWorkPatternSyncStatus(
266
+ `reading history roots CODEX_HOME=${homes.codexHome} `
267
+ + `(sessions/, archived_sessions/) and CLAUDE_CONFIG_DIR=${homes.claudeHome} (projects/); `
268
+ + `state under ${sessionRoot}.`,
269
+ quiet,
270
+ );
271
+ codexWorkPatternSync = createCodexWorkPatternSync({ sessionRoot, ...homes });
272
+ }
273
+ }
274
+ let syncPromise;
275
+ try {
276
+ syncPromise = codexWorkPatternSync.poll({
277
+ apiOrigin,
278
+ apiBaseUrl: session.api_base_url,
279
+ token: session.token,
280
+ deviceId: session.device_id,
281
+ accountId: session.account_id,
282
+ });
283
+ } catch (error) {
284
+ reportCodexWorkPatternSyncError(error, quiet);
285
+ return;
286
+ }
287
+ const observed = Promise.resolve(syncPromise)
288
+ .then((result) => {
289
+ // `poll` returns undefined while throttled by the 15-minute interval.
290
+ if (!shuttingDown && result?.status) {
291
+ reportCodexWorkPatternSyncStatus(
292
+ `${result.status}; uploaded ${Number(result.uploaded || 0)} observation chunk(s).`,
293
+ quiet,
294
+ );
295
+ }
296
+ if (!shuttingDown && Number(result?.skipped_oversized || 0) > 0) {
297
+ reportCodexWorkPatternSyncError(
298
+ new Error(`${result.skipped_oversized} observation(s) exceeded the gateway limit.`),
299
+ quiet,
300
+ );
301
+ }
302
+ })
303
+ .catch((error) => {
304
+ if (!shuttingDown) reportCodexWorkPatternSyncError(error, quiet);
305
+ });
306
+ codexWorkPatternSyncInFlight = observed;
307
+ void observed.finally(() => {
308
+ if (codexWorkPatternSyncInFlight === observed) codexWorkPatternSyncInFlight = null;
309
+ });
310
+ };
235
311
  let loginUrl = null;
236
312
  const connectionStatus = createConnectionStatusTracker({ now: dependencies.statusNow });
237
313
  const codexStatus = createCodexStatusInspector({
@@ -463,6 +539,7 @@ export async function startAgent({
463
539
  await heartbeatDevice({ apiOrigin, apiBaseUrl: session.api_base_url, token: session.token,
464
540
  port, capabilities: refreshAvailableCapabilities(), repoInventory: repositoryInventory });
465
541
  }
542
+ pollCodexWorkPatterns();
466
543
  };
467
544
  if (deferStartupSynchronization) {
468
545
  void publishVerifiedProviders().catch((error) => reportHeartbeatError(error, quiet));
@@ -1074,11 +1151,19 @@ export async function startAgent({
1074
1151
  throw error;
1075
1152
  }
1076
1153
  let workflowObservationBinding;
1154
+ let workflowStartIntent;
1077
1155
  try {
1078
1156
  workflowObservationBinding = normalizeWorkflowObservationBinding(
1079
1157
  body.workflow_observation_binding,
1080
1158
  { provider: body.backend },
1081
1159
  );
1160
+ workflowStartIntent = normalizeWorkflowStartIntent(
1161
+ body.workflow_start_intent,
1162
+ { provider: body.backend },
1163
+ );
1164
+ if (workflowObservationBinding && workflowStartIntent) {
1165
+ throw new Error("WorkFlow binding and start intent are mutually exclusive.");
1166
+ }
1082
1167
  } catch (error) {
1083
1168
  json(res, 400, {
1084
1169
  error: error instanceof Error ? error.message : "WorkFlow observation binding is invalid.",
@@ -1102,6 +1187,7 @@ export async function startAgent({
1102
1187
  attachmentManifest: preparedAttachments,
1103
1188
  scenarioPack,
1104
1189
  workflowObservationBinding,
1190
+ workflowStartIntent,
1105
1191
  });
1106
1192
  hashLocalTaskIdempotencyKey(idempotencyKey);
1107
1193
  } catch (error) {
@@ -1227,6 +1313,7 @@ export async function startAgent({
1227
1313
  idempotencyKeyHash: hashLocalTaskIdempotencyKey(idempotencyKey),
1228
1314
  requestFingerprint,
1229
1315
  workflowObservationBinding,
1316
+ workflowStartIntent,
1230
1317
  });
1231
1318
  await persistLocalTask(task);
1232
1319
  localTaskRegistry.create(task);
@@ -1463,11 +1550,19 @@ export async function startAgent({
1463
1550
  }
1464
1551
  const indexedRepository = await ensureRepositoryIndexed(body.repository);
1465
1552
  let workflowObservationBinding;
1553
+ let workflowStartIntent;
1466
1554
  try {
1467
1555
  workflowObservationBinding = normalizeWorkflowObservationBinding(
1468
1556
  body.workflow_observation_binding,
1469
1557
  { provider: body.backend },
1470
1558
  );
1559
+ workflowStartIntent = normalizeWorkflowStartIntent(
1560
+ body.workflow_start_intent,
1561
+ { provider: body.backend },
1562
+ );
1563
+ if (workflowObservationBinding && workflowStartIntent) {
1564
+ throw new Error("WorkFlow binding and start intent are mutually exclusive.");
1565
+ }
1471
1566
  } catch (error) {
1472
1567
  json(res, 400, {
1473
1568
  error: error instanceof Error ? error.message : "WorkFlow observation binding is invalid.",
@@ -1517,6 +1612,7 @@ export async function startAgent({
1517
1612
  must_not: Array.isArray(body.must_not) ? body.must_not : [],
1518
1613
  proposal_id: typeof body.proposal_id === "string" ? body.proposal_id : null,
1519
1614
  workflow_observation_binding: workflowObservationBinding,
1615
+ workflow_start_intent: workflowStartIntent,
1520
1616
  charter_context:
1521
1617
  body.charter_context &&
1522
1618
  typeof body.charter_context === "object" &&
@@ -1848,6 +1944,8 @@ export async function startAgent({
1848
1944
  if (codingRunMonitorTimer) clearInterval(codingRunMonitorTimer);
1849
1945
  if (codingRunMonitorInFlight) await codingRunMonitorInFlight;
1850
1946
  updateManager?.stop?.();
1947
+ await codexWorkPatternSync?.close?.();
1948
+ await codexWorkPatternSyncInFlight;
1851
1949
  await sqmLocalReviewWorker?.close?.();
1852
1950
  await durableLocalTaskWorker?.close?.();
1853
1951
  try {
@@ -2142,6 +2240,35 @@ function reportSqmLocalReviewError(error, quiet) {
2142
2240
  );
2143
2241
  }
2144
2242
 
2243
+ export function codexWorkPatternSkipReasonFor({ daemon, sessionValid, capabilities }) {
2244
+ if (!daemon) return "history sync runs only in daemon mode (simy --daemon).";
2245
+ if (!sessionValid) return "no valid SIMY session for this Web origin; sign in first.";
2246
+ if (capabilities?.features?.codex_work_pattern_source === true) return null;
2247
+ const versions = capabilities?.backend_versions || {};
2248
+ const describe = (provider) => {
2249
+ const inspection = versions[provider];
2250
+ if (!inspection) return `${provider}=unknown`;
2251
+ return `${provider}=${inspection.status || "unknown"}`
2252
+ + (inspection.installed_version ? ` ${inspection.installed_version}` : "")
2253
+ + (inspection.minimum_version ? ` (requires ${inspection.minimum_version}+)` : "");
2254
+ };
2255
+ return "no compatible Codex or Claude Code executable was detected "
2256
+ + `(${describe("codex")}, ${describe("claude")}); history roots were not read. `
2257
+ + "Install or update a provider, or set SIMY_CODEX_BIN / SIMY_CLAUDE_BIN.";
2258
+ }
2259
+
2260
+ function reportCodexWorkPatternSyncStatus(message, quiet) {
2261
+ if (quiet) return;
2262
+ console.error(`SIMY work-pattern history sync: ${message}`);
2263
+ }
2264
+
2265
+ function reportCodexWorkPatternSyncError(error, quiet) {
2266
+ if (quiet) return;
2267
+ console.error(
2268
+ `SIMY work-pattern history sync: ${error instanceof Error ? error.message : String(error)}`,
2269
+ );
2270
+ }
2271
+
2145
2272
  function reportStartupConnectionError(error, quiet) {
2146
2273
  if (quiet) return;
2147
2274
  console.error(
@@ -2257,7 +2384,12 @@ function capabilitiesFromInspections(codex, claude) {
2257
2384
  durable_local_task_step_model: true,
2258
2385
  bounded_local_task_subtasks: true,
2259
2386
  sqm_agent_skill_management: true,
2387
+ // Web adds `terminal_contract` to a WorkFlow binding only for CLIs that
2388
+ // advertise this; older CLIs reject a fifth binding key.
2389
+ workflow_terminal_contract: true,
2390
+ workflow_deliverable_upload: true,
2260
2391
  sqm_local_review: codex.compatible || claude.compatible,
2392
+ codex_work_pattern_source: codex.compatible || claude.compatible,
2261
2393
  },
2262
2394
  session_ttl_hours: 48,
2263
2395
  };
@@ -2616,7 +2748,11 @@ function daemonCapabilities(inspectedCapabilities, daemon) {
2616
2748
  inspectedCapabilities?.features || {},
2617
2749
  "sqm_local_review",
2618
2750
  );
2619
- return durableControlPlane || hasSqmLocalReview
2751
+ const hasCodexWorkPatternSource = Object.hasOwn(
2752
+ inspectedCapabilities?.features || {},
2753
+ "codex_work_pattern_source",
2754
+ );
2755
+ return durableControlPlane || hasSqmLocalReview || hasCodexWorkPatternSource
2620
2756
  ? {
2621
2757
  ...inspectedCapabilities,
2622
2758
  features: {
@@ -2645,6 +2781,12 @@ function daemonCapabilities(inspectedCapabilities, daemon) {
2645
2781
  daemon && inspectedCapabilities?.features?.sqm_local_review === true,
2646
2782
  }
2647
2783
  : {}),
2784
+ ...(hasCodexWorkPatternSource
2785
+ ? {
2786
+ codex_work_pattern_source:
2787
+ daemon && inspectedCapabilities?.features?.codex_work_pattern_source === true,
2788
+ }
2789
+ : {}),
2648
2790
  },
2649
2791
  }
2650
2792
  : inspectedCapabilities;
@@ -3,6 +3,7 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  const MACOS_CODEX_BINARY = "/Applications/ChatGPT.app/Contents/Resources/codex";
6
+ const MACOS_CODEX_BUNDLED_BINARY = "/Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex";
6
7
 
7
8
  export const BACKEND_VERSION_POLICIES = Object.freeze({
8
9
  codex: Object.freeze({
@@ -86,7 +87,7 @@ export function backendCandidates(backend, {
86
87
  return [
87
88
  environment.SIMY_CODEX_BIN,
88
89
  "codex",
89
- ...(platform === "darwin" ? [MACOS_CODEX_BINARY] : []),
90
+ ...(platform === "darwin" ? [MACOS_CODEX_BUNDLED_BINARY, MACOS_CODEX_BINARY] : []),
90
91
  ];
91
92
  }
92
93
 
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.5.34";
16
+ export const CLI_VERSION = "0.5.35";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(