@awak-app/simy-cli 0.5.15 → 0.5.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 (45) hide show
  1. package/README.md +78 -15
  2. package/package.json +2 -2
  3. package/src/agent.js +33 -1
  4. package/src/cli-contract.js +1 -1
  5. package/src/codex-plugins/simy/.codex-plugin/plugin.json +5 -6
  6. package/src/codex-plugins/simy/hooks/README.md +17 -0
  7. package/src/codex-plugins/simy/hooks/connection-reminder.mjs +184 -0
  8. package/src/codex-plugins/simy/hooks/hooks.json +27 -0
  9. package/src/connection-discovery.js +49 -0
  10. package/src/console/app.js +2 -2
  11. package/src/orchestrator/audit.js +47 -5
  12. package/src/orchestrator/budget.js +1 -1
  13. package/src/orchestrator/checklist-continuation.js +21 -0
  14. package/src/orchestrator/completion-contract.js +1 -1
  15. package/src/orchestrator/contract.js +2 -0
  16. package/src/{codex-plugins/simy/skills/agentic-delivery-loop/SKILL.md → orchestrator/delivery/WORKFLOW.md} +121 -13
  17. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/acceptance-evidence.md +27 -3
  18. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/change-boundary-matrix.md +1 -1
  19. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/delivery-levels.md +78 -27
  20. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/minimum-completion.md +3 -3
  21. package/src/orchestrator/delivery/references/pr-closure.md +203 -0
  22. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/state-and-receipts.md +4 -1
  23. package/src/orchestrator/delivery/references/ux-specification.md +98 -0
  24. package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/production-contract.mjs +1 -1
  25. package/src/orchestrator/delivery-level.js +19 -0
  26. package/src/orchestrator/evidence.js +64 -5
  27. package/src/orchestrator/independent-audit.js +18 -0
  28. package/src/orchestrator/index.js +7 -0
  29. package/src/orchestrator/instruction.js +32 -3
  30. package/src/orchestrator/loop.js +4 -1
  31. package/src/orchestrator/minimum-completion.js +2 -2
  32. package/src/orchestrator/pr-closure.js +26 -0
  33. package/src/orchestrator/pr-preparation.js +420 -0
  34. package/src/orchestrator/presentation.js +2 -2
  35. package/src/orchestrator/result.js +8 -2
  36. package/src/orchestrator/ux-specification.js +20 -0
  37. package/src/runner.js +104 -1
  38. package/src/sqm/agent-skill.js +39 -7
  39. package/src/codex-plugins/simy/skills/agentic-delivery-loop/agents/openai.yaml +0 -5
  40. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/chrome-user-verification.md +0 -0
  41. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/cross-repository-control.md +0 -0
  42. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/local-sqm-evidence.md +0 -0
  43. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/references/shared-system-specifications.md +0 -0
  44. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/check-acceptance.mjs +0 -0
  45. /package/src/{codex-plugins/simy/skills/agentic-delivery-loop → orchestrator/delivery}/scripts/check-minimum-completion.mjs +0 -0
package/README.md CHANGED
@@ -42,6 +42,13 @@ SQM checks apply when you explicitly select `@simy`, invoke the SQM skill, or as
42
42
  After installation, restart Codex and find and install **SIMY** in **Plugins**
43
43
  before using `@simy` in a new task.
44
44
 
45
+ The plugin also includes a non-blocking connection reminder. At task startup,
46
+ resume, and prompt submission it reads the origin-scoped local discovery record
47
+ and checks the loopback CLI health endpoint. It warns once per disconnected
48
+ period in each Codex session, then rearms after the CLI reconnects. The hook does
49
+ not start or install the CLI, read session tokens, enable SQM, or send email.
50
+ Review and enable the plugin hooks through Codex's `/hooks` command.
51
+
45
52
  After that first installation, the daemon checks installed workflows at startup
46
53
  and every hour, including after a CLI automatic upgrade. It updates only existing
47
54
  platform installations and refreshes an already enabled SIMY Codex plugin through
@@ -49,6 +56,11 @@ platform installations and refreshes an already enabled SIMY Codex plugin throug
49
56
  receive a fresh cache. Use a new Codex task to pick up updated skills; running
50
57
  tasks are not restarted. Missing or disabled plugins are not enabled automatically.
51
58
  Failed refreshes retry on the next check without blocking daemon startup.
59
+ Delivery Loop is a SIMY Web/CLI runtime workflow, not a local Codex skill.
60
+ The plugin ships only SQM and PR review. Updating to this release removes the
61
+ previously bundled Delivery Loop from managed plugin sources and refreshes the
62
+ enabled cache. Older running CLI releases can restore their old bundled skill
63
+ until upgraded; deleting a local skill alone does not change their updater.
52
64
  `--no-auto-update` or `SIMY_AUTO_UPDATE=0` disables these background updates too;
53
65
  `--no-workflow-prompt` only suppresses the initial installation prompt.
54
66
 
@@ -267,7 +279,7 @@ is not copied into the recovery contract.
267
279
  ## Provider token budget
268
280
 
269
281
  Each run accepts a configurable `token_budget` (or the explicit
270
- `provider_token_budget` alias) from 1 to 10,000,000 Provider tokens. The default
282
+ `provider_token_budget` alias) from 1 to 15,000,000 Provider tokens. The default
271
283
  is 250,000. Provider tokens are the Codex or Claude Code usage reported for the
272
284
  run; they are a safety limit and are not billed as SIMY tokens.
273
285
 
@@ -277,6 +289,12 @@ limit. SIMY Web can edit that suggestion and call
277
289
  `provider_token_budget`. The CLI persists the higher limit and resumes the same
278
290
  run. SIMY token consumption remains a separate platform billing record.
279
291
 
292
+ An in-flight provider call can report usage above the previous limit. Recovery
293
+ keeps that full usage history, including reported cached input; it never resets
294
+ the counter or increases the limit automatically. An explicitly approved higher
295
+ limit must exceed usage already recorded and remain within the finite cap.
296
+ Retry limits and all completion gates still apply after a budget increase.
297
+
280
298
  ## SQM local checks
281
299
 
282
300
  SQM deliberately has two identity-bound stages:
@@ -376,8 +394,16 @@ The local PR lifecycle is intentionally separate from deployment:
376
394
  2. Require explicit acceptance criteria plus an approved design summary for high-risk work.
377
395
  3. Run the coding executor and verify its result against local Git evidence.
378
396
  4. Run a separate AI audit session without granting it an implementation role.
379
- 5. Mark the result `pr_ready_for_review` while GitHub checks or peer approval are pending.
380
- 6. Mark it `merge_ready` only after the observed PR head/base, CI checks, merge state,
397
+ 5. Require eight revision-bound PR-preparation rows: latest base/head, current CI and
398
+ disclosed skips, exact companion revisions, E2E outcomes and zero residual fixtures,
399
+ security/privacy, browser/localization when applicable, final diff scope, and honest
400
+ residual/post-merge status. The CLI refreshes the remote base, requires it to be an
401
+ ancestor of the final head, and compares it with the PR base SHA.
402
+ Four additional conditional safeguards cover official-checklist ownership,
403
+ authoritative deployment-target mapping, live-test data safety, and saved-run/
404
+ checkpoint compatibility without forcing irrelevant work to execute them.
405
+ 6. Mark the result `pr_ready_for_review` while GitHub checks or peer approval are pending.
406
+ 7. Mark it `merge_ready` only after the observed PR head/base, CI checks, merge state,
381
407
  and human approval all pass.
382
408
 
383
409
  The Web UI can refresh review and CI evidence with
@@ -428,42 +454,79 @@ organization-wide bundle.
428
454
 
429
455
  Every CLI coding-loop promotion now requires a current two-layer observation
430
456
  matrix: the requested operation and result, plus independently checked final
431
- postcondition and integration. See the [minimum completion contract](src/codex-plugins/simy/skills/agentic-delivery-loop/references/minimum-completion.md)
457
+ postcondition and integration. See the [minimum completion contract](src/orchestrator/delivery/references/minimum-completion.md)
432
458
  for the executable format. Missing mappings, mismatching observations, changed
433
459
  artifacts or stale revisions block promotion even when the executor reports
434
460
  success. Independent review checks original-request coverage and the test oracle.
435
461
 
436
462
  Existing v2 run history is retained; resumed/rechecked runs need the new evidence.
437
- The same checker ships in the standalone Delivery Loop skill, but it does not
438
- control the host's final response or establish deployment completion.
463
+ The same checker ships under `src/orchestrator/delivery/scripts/` in the CLI
464
+ package, independent of Codex plugin installation. It does not establish deployment
465
+ completion or intercept another host application's final response.
466
+
467
+ ### PR closure and user-outcome transfer
468
+
469
+ The Web/CLI executor and independent auditor receive the same
470
+ [PR closure procedure](src/orchestrator/delivery/references/pr-closure.md).
471
+ It requires a revision-bound before/after table, reachable normal-user paths
472
+ (including no-workflow fallback), independent review, explicit authority,
473
+ pre-close freshness checks and post-close remote reconciliation. A file match or
474
+ successor link is not proof of UX inheritance; administrative transfer, approved
475
+ retirement and complete inheritance have distinct decisions. Unresolved yellow/red comparison rows require
476
+ remediation PRs (or exact existing open equivalents), mapped by criterion ID,
477
+ owner and acceptance checks. Uncreated plans or unresolved authority/product
478
+ decisions block transfer completion; creating a PR does not make a row green.
479
+ Closure is not a new delivery completion state. This is runtime instruction and review guidance,
480
+ not a GitHub hook that mechanically intercepts every external close command.
439
481
 
440
482
  ### Submit repaired local delivery evidence to SQM
441
483
 
442
484
  The bundled Delivery Loop maintains an external acceptance checklist with separate
443
485
  user-journey and technical-invariant results. It refreshes the table after each
444
486
  evaluated action and marks evidence pending when the code or runtime context
445
- changes. The [acceptance evidence guide](src/codex-plugins/simy/skills/agentic-delivery-loop/references/acceptance-evidence.md)
446
- documents the offline validator shipped inside the skill. Its result establishes
487
+ changes. A non-green table is a checkpoint, not completion: the executor must
488
+ perform feasible authorized repairs, missing verification and bounded waits,
489
+ then re-observe and update the same criteria until the requested endpoint passes.
490
+ A genuine safety/authority/dependency/budget stop remains explicitly unfinished
491
+ with affected IDs and a concrete resumption condition; no automatic budget increase
492
+ or background monitor is implied. Both executor and independent auditor receive
493
+ this continuation contract. The [acceptance evidence guide](src/orchestrator/delivery/references/acceptance-evidence.md)
494
+ documents the offline validator shipped inside the CLI runtime. Its result establishes
447
495
  record completeness and artifact integrity, not overall delivery or deployment.
448
496
  At the start of each new run, the loop recommends and confirms one of five
449
- [delivery levels](src/codex-plugins/simy/skills/agentic-delivery-loop/references/delivery-levels.md)
450
- in chat. Risk-based ceilings keep sensitive or consequential changes on the
451
- required safety level; the selection adjusts verification depth but grants no
452
- additional authority.
497
+ [delivery levels](src/orchestrator/delivery/references/delivery-levels.md)
498
+ in chat. Level 5 is highest assurance; level 1 is lightest. Every selection or
499
+ recommendation shows all five levels (5 to 1), their content and verification scope,
500
+ task-specific hour estimates, and the recommended level with its rationale.
501
+ Estimates distinguish execution from external waits and state assumptions and
502
+ uncertainty; there are no fixed durations by level. Risk-based floors preserve
503
+ mandatory assurance. Historical selections retain their numbering version and
504
+ meaning. These are executor/auditor instructions, not a new deterministic CLI
505
+ selection validator; selecting a level grants no additional authority.
453
506
 
454
507
  `simy sqm submit-evidence --file delivery.json --host https://app-dev.simy.one`
455
508
  submits an explicit bounded summary through your existing organization-bound device
456
509
  session. `--dry-run` previews the exact document without uploading it. See the
457
- [local evidence contract](src/codex-plugins/simy/skills/agentic-delivery-loop/references/local-sqm-evidence.md).
510
+ [local evidence contract](src/orchestrator/delivery/references/local-sqm-evidence.md).
458
511
  The receipt identifies the source digest and analysis job; it is not a declaration
459
512
  that an incident or blocking rule has been published.
460
513
 
461
514
  ## Delivery Loop shared specification
462
515
 
516
+ UX descriptions throughout the Web/CLI lifecycle follow the
517
+ [behavioral-completeness standard](src/orchestrator/delivery/references/ux-specification.md).
518
+ Observable interaction units explicitly cover intent/context, entry, perception,
519
+ control, response, usable outcome, continuation, alternatives/recovery and evidence.
520
+ Independent reconstruction, source coverage, causal continuity, branch closure,
521
+ observability and traceability determine sufficient detail, not word counts or
522
+ product-specific scenarios. Unknowns remain explicit; compact summaries link to
523
+ the complete canonical specification. The same standard is supplied to the actual
524
+ executor and independent auditor; it is not an additional automatic schema gate.
525
+
463
526
  The [Delivery Loop system specification](docs/systems/delivery-loop/README.md)
464
527
  explains its purpose, As-Is and proposed To-Be through twelve linked views. Keep
465
528
  these as shared team documents; each change updates affected views and checks
466
- cross-view consistency. The skill requires independent design, implementation-to-
529
+ cross-view consistency. The workflow requires independent design, implementation-to-
467
530
  To-Be and PR reviews, plus explicitly opted-in SQM before and after PR creation.
468
- The [workflow reference](src/codex-plugins/simy/skills/agentic-delivery-loop/references/shared-system-specifications.md)
531
+ The [workflow reference](src/orchestrator/delivery/references/shared-system-specifications.md)
469
532
  explains baseline updates, historical regression coverage and evidence limits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,7 +40,7 @@
40
40
  "check:auto-update": "node --check ./src/auto-update.js && node --check ./scripts/auto-update-e2e.js && npm run check:sqm",
41
41
  "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",
42
42
  "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",
43
- "check:syntax": "node --check ./src/index.js && node --check ./src/install-update.js && node --check ./src/agent.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/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/minimum-completion.js && node --check ./src/codex-plugins/simy/skills/agentic-delivery-loop/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 ./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",
43
+ "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 ./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",
44
44
  "check:package": "node ./scripts/check-package-contents.js",
45
45
  "build:macos-installer:dev": "bash packaging/build-macos-arm64.sh --unsigned-dev",
46
46
  "build:macos-installer": "bash packaging/build-macos-arm64.sh --production",
package/src/agent.js CHANGED
@@ -126,6 +126,10 @@ import {
126
126
  createSqmLocalReviewApi,
127
127
  createSqmLocalReviewWorker,
128
128
  } from "./sqm/review-worker.js";
129
+ import {
130
+ publishConnectionDiscovery,
131
+ removeConnectionDiscovery,
132
+ } from "./connection-discovery.js";
129
133
 
130
134
  const DEVICE_HEARTBEAT_INTERVAL_MS = 20_000;
131
135
 
@@ -510,14 +514,20 @@ export async function startAgent({
510
514
  res.once("close", releaseUpdateWork);
511
515
  }
512
516
  if (req.method === "GET" && url.pathname === "/v1/health") {
517
+ const sessionValid = isSessionValid(session, Date.now(), apiOrigin);
518
+ const connection = connectionStatus.snapshot({ authenticated: sessionValid });
513
519
  json(res, 200, {
514
520
  ok: true,
515
521
  cli_version: CLI_VERSION,
516
522
  api_contract_version: CLI_API_CONTRACT_VERSION,
517
523
  daemon,
518
524
  web_origin: apiOrigin,
519
- session_valid: isSessionValid(session, Date.now(), apiOrigin),
525
+ session_valid: sessionValid,
520
526
  session_expires_at: session?.expires_at ?? null,
527
+ connection: {
528
+ state: connection.state,
529
+ lastSuccessAt: connection.lastSuccessAt,
530
+ },
521
531
  cli_update: updateManager?.snapshot?.() ?? null,
522
532
  });
523
533
  return;
@@ -1566,6 +1576,14 @@ export async function startAgent({
1566
1576
  await new Promise((resolve) => server.listen(requestedPort, "127.0.0.1", resolve));
1567
1577
  const address = server.address();
1568
1578
  port = typeof address === "object" && address ? address.port : requestedPort;
1579
+ let connectionDiscovery = null;
1580
+ try {
1581
+ connectionDiscovery = await (
1582
+ dependencies.publishConnectionDiscovery || publishConnectionDiscovery
1583
+ )({ webOrigin: apiOrigin, port, sessionRoot });
1584
+ } catch (error) {
1585
+ reportConnectionDiscoveryError(error, quiet);
1586
+ }
1569
1587
  if (!quiet) {
1570
1588
  console.log(`SIMY local agent listening on http://127.0.0.1:${port}`);
1571
1589
  console.log(`SIMY Web host: ${apiOrigin}`);
@@ -1598,6 +1616,13 @@ export async function startAgent({
1598
1616
  updateManager?.stop?.();
1599
1617
  await sqmLocalReviewWorker?.close?.();
1600
1618
  await durableLocalTaskWorker?.close?.();
1619
+ try {
1620
+ await (dependencies.removeConnectionDiscovery || removeConnectionDiscovery)(
1621
+ connectionDiscovery,
1622
+ );
1623
+ } catch (error) {
1624
+ reportConnectionDiscoveryError(error, quiet);
1625
+ }
1601
1626
  registry.close();
1602
1627
  localTaskRegistry.close();
1603
1628
  })();
@@ -1850,6 +1875,13 @@ function reportHeartbeatError(error, quiet) {
1850
1875
  );
1851
1876
  }
1852
1877
 
1878
+ function reportConnectionDiscoveryError(error, quiet) {
1879
+ if (quiet) return;
1880
+ console.error(
1881
+ `SIMY connection reminder metadata could not be updated: ${error instanceof Error ? error.message : String(error)}`,
1882
+ );
1883
+ }
1884
+
1853
1885
  function reportDurableLocalTaskError(error, quiet) {
1854
1886
  if (quiet) return;
1855
1887
  console.error(
@@ -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.15";
16
+ export const CLI_VERSION = "0.5.17";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(
@@ -1,25 +1,24 @@
1
1
  {
2
2
  "name": "simy",
3
3
  "version": "0.1.0",
4
- "description": "Run SIMY quality, delivery, and pull request review workflows from the current Codex thread.",
4
+ "description": "Run SIMY quality and pull request review workflows from the current Codex thread.",
5
5
  "author": {
6
6
  "name": "Specialist Doctors Inc.",
7
7
  "url": "https://github.com/SpecialistDoctors-Inc"
8
8
  },
9
9
  "repository": "https://github.com/SpecialistDoctors-Inc/simy-cli",
10
10
  "license": "UNLICENSED",
11
- "keywords": ["simy", "sqm", "delivery", "pull-request"],
11
+ "keywords": ["simy", "sqm", "pull-request"],
12
12
  "skills": "./skills/",
13
13
  "interface": {
14
14
  "displayName": "SIMY",
15
- "shortDescription": "Quality, delivery, and PR workflows for SIMY.",
16
- "longDescription": "SIMY turns the current Codex thread into a bounded quality check, a full evidence-driven delivery loop, or a read-only pull request review.",
15
+ "shortDescription": "Quality and PR review workflows for SIMY.",
16
+ "longDescription": "SIMY turns the current Codex thread into a bounded quality check or a read-only pull request review. Delivery Loop runs through SIMY Web and CLI, not a local Codex skill.",
17
17
  "developerName": "Specialist Doctors Inc.",
18
18
  "category": "Developer Tools",
19
- "capabilities": ["Quality", "Delivery", "Review"],
19
+ "capabilities": ["Quality", "Review"],
20
20
  "defaultPrompt": [
21
21
  "Run the SIMY SQM Loop for the current thread.",
22
- "Run the SIMY Delivery Loop for the current thread.",
23
22
  "Review the current pull request with SIMY."
24
23
  ]
25
24
  }
@@ -0,0 +1,17 @@
1
+ # SIMY connection reminder
2
+
3
+ The SIMY Codex plugin checks the origin-scoped local CLI connection when a task
4
+ starts or resumes and when a prompt is submitted. The hook only reads the local
5
+ daemon discovery record and its `/v1/health` endpoint. It never starts, installs,
6
+ signs in to, or changes SIMY CLI, and it never sends email.
7
+
8
+ The reminder is non-blocking. It stays silent while the latest cloud heartbeat is
9
+ fresh, warns once for each disconnected period in a Codex session, and rearms
10
+ after a successful probe. State is stored under `PLUGIN_DATA`; the fallback is
11
+ the SIMY home directory. Installing the plugin does not trust its hooks
12
+ automatically: review and enable them through Codex's `/hooks` command.
13
+
14
+ When `SIMY_WEB_ORIGIN` or `SIMY_API_ORIGIN` is set, only that origin is accepted.
15
+ Otherwise the hook checks at most 16 origin-keyed discovery records and accepts
16
+ the first live, authenticated connection with a fresh cloud heartbeat. It never
17
+ scans ports or reads stored session tokens.
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ const HEARTBEAT_FRESHNESS_MS = 90_000;
10
+ const PROBE_TIMEOUT_MS = 750;
11
+ const MAX_INPUT_BYTES = 64 * 1024;
12
+ const MAX_DISCOVERY_RECORDS = 16;
13
+
14
+ export async function runConnectionReminder({
15
+ env = process.env,
16
+ input = {},
17
+ fetchImpl = globalThis.fetch,
18
+ now = () => Date.now(),
19
+ } = {}) {
20
+ const explicitWebOrigin = resolveExplicitWebOrigin(env);
21
+ const sessionRoot = env.SIMY_HOME?.trim() || join(homedir(), ".simy");
22
+ const pluginData = env.PLUGIN_DATA?.trim() || join(sessionRoot, "plugin-data", "simy");
23
+ const sessionId = String(input.session_id || input.sessionId || env.CODEX_THREAD_ID || "unknown");
24
+ const statePath = reminderStatePath(pluginData, explicitWebOrigin || "any-origin", sessionId);
25
+ const connected = explicitWebOrigin
26
+ ? await probeConnection({ webOrigin: explicitWebOrigin, sessionRoot, fetchImpl, now })
27
+ : await probeAnyConnection({ sessionRoot, fetchImpl, now });
28
+ const previous = await readState(statePath);
29
+ if (connected) {
30
+ if (previous?.disconnected) await writeState(statePath, { disconnected: false }).catch(() => {});
31
+ return { continue: true };
32
+ }
33
+ if (previous?.disconnected) return { continue: true };
34
+ await writeState(statePath, { disconnected: true }).catch(() => {});
35
+ return { continue: true, systemMessage: disconnectedMessage(env) };
36
+ }
37
+
38
+ export async function probeConnection({ webOrigin, sessionRoot, fetchImpl, now }) {
39
+ let discovery;
40
+ try {
41
+ discovery = JSON.parse(await readFile(discoveryPath(webOrigin, sessionRoot), "utf8"));
42
+ } catch {
43
+ return false;
44
+ }
45
+ if (
46
+ discovery?.schema_version !== 1 ||
47
+ discovery?.web_origin !== webOrigin ||
48
+ !Number.isInteger(discovery?.port) ||
49
+ discovery.port < 1 ||
50
+ discovery.port > 65_535
51
+ ) return false;
52
+
53
+ const controller = new AbortController();
54
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
55
+ timeout.unref?.();
56
+ try {
57
+ const response = await fetchImpl(`http://127.0.0.1:${discovery.port}/v1/health`, {
58
+ headers: { Origin: webOrigin },
59
+ redirect: "error",
60
+ signal: controller.signal,
61
+ });
62
+ if (!response.ok) return false;
63
+ const health = await response.json();
64
+ const lastSuccess = Date.parse(health?.connection?.lastSuccessAt || "");
65
+ const sampledAt = typeof now === "function" ? now() : now;
66
+ return Boolean(
67
+ health?.ok === true &&
68
+ health?.web_origin === webOrigin &&
69
+ health?.session_valid === true &&
70
+ health?.connection?.state === "online" &&
71
+ Number.isFinite(lastSuccess) &&
72
+ sampledAt - lastSuccess >= 0 &&
73
+ sampledAt - lastSuccess <= HEARTBEAT_FRESHNESS_MS,
74
+ );
75
+ } catch {
76
+ return false;
77
+ } finally {
78
+ clearTimeout(timeout);
79
+ }
80
+ }
81
+
82
+ export async function probeAnyConnection({ sessionRoot, fetchImpl, now }) {
83
+ let entries;
84
+ try {
85
+ entries = (await readdir(join(sessionRoot, "connections")))
86
+ .filter((entry) => entry.endsWith(".json"))
87
+ .sort()
88
+ .slice(0, MAX_DISCOVERY_RECORDS);
89
+ } catch {
90
+ return false;
91
+ }
92
+ if (!entries.length) return false;
93
+ const probes = entries.map(async (entry) => {
94
+ try {
95
+ const record = JSON.parse(await readFile(join(sessionRoot, "connections", entry), "utf8"));
96
+ const webOrigin = normalizeOrigin(record?.web_origin);
97
+ if (!webOrigin || entry !== `${sha256(webOrigin)}.json`) return false;
98
+ return probeConnection({ webOrigin, sessionRoot, fetchImpl, now });
99
+ } catch {
100
+ return false;
101
+ }
102
+ });
103
+ return (await Promise.all(probes)).some(Boolean);
104
+ }
105
+
106
+ function resolveExplicitWebOrigin(env) {
107
+ const raw = env.SIMY_WEB_ORIGIN || env.SIMY_API_ORIGIN;
108
+ if (!raw) return null;
109
+ return normalizeOrigin(raw) || "invalid-origin";
110
+ }
111
+
112
+ function normalizeOrigin(raw) {
113
+ try {
114
+ const url = new URL(raw);
115
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
116
+ return null;
117
+ }
118
+ if ((url.pathname && url.pathname !== "/") || url.search || url.hash) {
119
+ return null;
120
+ }
121
+ return url.origin;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function discoveryPath(webOrigin, sessionRoot) {
128
+ return join(sessionRoot, "connections", `${sha256(webOrigin)}.json`);
129
+ }
130
+
131
+ function reminderStatePath(pluginData, webOrigin, sessionId) {
132
+ return join(pluginData, "connection-reminders", `${sha256(`${webOrigin}\0${sessionId}`)}.json`);
133
+ }
134
+
135
+ async function readState(target) {
136
+ try {
137
+ return JSON.parse(await readFile(target, "utf8"));
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+
143
+ async function writeState(target, state) {
144
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
145
+ const temporary = `${target}.${process.pid}.tmp`;
146
+ await writeFile(temporary, `${JSON.stringify(state)}\n`, { encoding: "utf8", mode: 0o600 });
147
+ await rename(temporary, target);
148
+ }
149
+
150
+ function disconnectedMessage(env) {
151
+ const locale = String(env.LC_ALL || env.LC_MESSAGES || env.LANG || "").toLowerCase();
152
+ return locale.startsWith("ja")
153
+ ? "SIMY CLI に接続できません。SIMY CLI を起動し、ログイン状態をご確認ください。作業はこのまま続けられます。"
154
+ : "SIMY CLI is not connected. Start SIMY CLI and check that you are signed in. You can keep working in the meantime.";
155
+ }
156
+
157
+ function sha256(value) {
158
+ return createHash("sha256").update(value).digest("hex");
159
+ }
160
+
161
+ async function readHookInput(stream = process.stdin) {
162
+ let raw = "";
163
+ for await (const chunk of stream) {
164
+ raw += String(chunk);
165
+ if (Buffer.byteLength(raw) > MAX_INPUT_BYTES) return {};
166
+ }
167
+ try {
168
+ const parsed = JSON.parse(raw || "{}");
169
+ return parsed && typeof parsed === "object" ? parsed : {};
170
+ } catch {
171
+ return {};
172
+ }
173
+ }
174
+
175
+ export async function main() {
176
+ try {
177
+ const result = await runConnectionReminder({ input: await readHookInput() });
178
+ process.stdout.write(`${JSON.stringify(result)}\n`);
179
+ } catch {
180
+ process.stdout.write(`${JSON.stringify({ continue: true })}\n`);
181
+ }
182
+ }
183
+
184
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main();
@@ -0,0 +1,27 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node --input-type=module -e \"import { pathToFileURL } from 'node:url'; import { join } from 'node:path'; const hook = await import(pathToFileURL(join(process.env.PLUGIN_ROOT, 'hooks', 'connection-reminder.mjs')).href); await hook.main();\"",
10
+ "timeout": 3
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "UserPromptSubmit": [
16
+ {
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node --input-type=module -e \"import { pathToFileURL } from 'node:url'; import { join } from 'node:path'; const hook = await import(pathToFileURL(join(process.env.PLUGIN_ROOT, 'hooks', 'connection-reminder.mjs')).href); await hook.main();\"",
21
+ "timeout": 3
22
+ }
23
+ ]
24
+ }
25
+ ]
26
+ }
27
+ }
@@ -0,0 +1,49 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+
6
+ import { normalizeWebOrigin } from "./web-origin.js";
7
+
8
+ export function connectionDiscoveryPath(webOrigin, sessionRoot) {
9
+ const origin = normalizeWebOrigin(webOrigin);
10
+ const originHash = createHash("sha256").update(origin).digest("hex");
11
+ return join(sessionRoot || defaultSessionRoot(), "connections", `${originHash}.json`);
12
+ }
13
+
14
+ export async function publishConnectionDiscovery({ webOrigin, port, sessionRoot }) {
15
+ const origin = normalizeWebOrigin(webOrigin);
16
+ const target = connectionDiscoveryPath(origin, sessionRoot || defaultSessionRoot());
17
+ const instanceId = randomBytes(16).toString("hex");
18
+ const record = {
19
+ schema_version: 1,
20
+ web_origin: origin,
21
+ port,
22
+ pid: process.pid,
23
+ instance_id: instanceId,
24
+ started_at: new Date().toISOString(),
25
+ };
26
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
27
+ const temporary = `${target}.${process.pid}.${instanceId}.tmp`;
28
+ await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, {
29
+ encoding: "utf8",
30
+ mode: 0o600,
31
+ });
32
+ await rename(temporary, target);
33
+ return { path: target, instanceId };
34
+ }
35
+
36
+ function defaultSessionRoot() {
37
+ return process.env.SIMY_HOME?.trim() || join(homedir(), ".simy");
38
+ }
39
+
40
+ export async function removeConnectionDiscovery(discovery) {
41
+ if (!discovery?.path || !discovery.instanceId) return;
42
+ try {
43
+ const current = JSON.parse(await readFile(discovery.path, "utf8"));
44
+ if (current?.instance_id !== discovery.instanceId) return;
45
+ await rm(discovery.path, { force: true });
46
+ } catch (error) {
47
+ if (error?.code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
48
+ }
49
+ }
@@ -96,7 +96,7 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
96
96
 
97
97
  useEffect(() => {
98
98
  if (selectedRun?.status === "pr_ready_for_review") {
99
- setNotice("Review the pull request, then use /recheck to refresh readiness.");
99
+ setNotice("Unfinished: resolve the pending gates, then use /recheck to refresh readiness.");
100
100
  } else if (
101
101
  selectedRun?.status === "waiting_human" ||
102
102
  selectedRun?.controlState === "waiting_human"
@@ -1174,7 +1174,7 @@ function nextActionForRun(run, repositoryMissing = false) {
1174
1174
  return { tone: "yellow", text: "Press p to resume the local agent." };
1175
1175
  }
1176
1176
  if (run.status === "pr_ready_for_review") {
1177
- return { tone: "yellow", text: "Review the pull request, then press r to refresh its evidence." };
1177
+ return { tone: "yellow", text: "Unfinished: resolve pending gates, then press r to refresh evidence." };
1178
1178
  }
1179
1179
  if (run.status === "merge_ready") {
1180
1180
  return {