@awak-app/simy-cli 0.7.16 → 0.7.18

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 (80) hide show
  1. package/README.md +180 -26
  2. package/package.json +3 -2
  3. package/src/agent.js +128 -7
  4. package/src/bounded-local-task-subtask-pool.js +32 -6
  5. package/src/bounded-local-task-subtasks-contract.js +1 -1
  6. package/src/cli-contract.js +1 -1
  7. package/src/codex-plugins/simy/.codex-plugin/plugin.json +2 -2
  8. package/src/codex-plugins/simy/hooks/README.md +8 -3
  9. package/src/codex-plugins/simy/hooks/delivery-loop.mjs +261 -67
  10. package/src/codex-plugins/simy/hooks/hooks.json +1 -1
  11. package/src/codex-plugins/simy/hooks/loop-kernel/conformance.json +136 -0
  12. package/src/codex-plugins/simy/hooks/loop-kernel/index.mjs +42 -0
  13. package/src/codex-plugins/simy/hooks/loop-kernel/outcome-review.mjs +31 -0
  14. package/src/codex-plugins/simy/hooks/loop-kernel/pin.json +6 -0
  15. package/src/codex-plugins/simy/hooks/loop-kernel/policy.json +141 -0
  16. package/src/codex-plugins/simy/hooks/loop-kernel/task-pool.mjs +34 -0
  17. package/src/codex-plugins/simy/hooks/loop-kernel/work-frontier.mjs +132 -0
  18. package/src/codex-plugins/simy/hooks/loop-kernel.mjs +6 -0
  19. package/src/codex-plugins/simy/hooks/outcome-progress.mjs +15 -3
  20. package/src/codex-plugins/simy/hooks/purpose-completion-contract.mjs +10 -0
  21. package/src/codex-plugins/simy/hooks/purpose-evidence.mjs +8 -6
  22. package/src/codex-plugins/simy/hooks/result-cycle.mjs +32 -6
  23. package/src/codex-plugins/simy/hooks/work-frontier.mjs +2 -0
  24. package/src/codex-plugins/simy/skills/diagnose/SKILL.md +10 -4
  25. package/src/coding-run-recovery.js +4 -2
  26. package/src/delivery-hooks/claude-workflow.md +10 -7
  27. package/src/delivery-hooks/command.js +45 -10
  28. package/src/delivery-hooks/consult.js +60 -0
  29. package/src/delivery-hooks/diagnosis.js +2 -2
  30. package/src/delivery-hooks/legacy-hook-routing.js +46 -8
  31. package/src/delivery-hooks/local-runtime.js +50 -1
  32. package/src/delivery-hooks/local-supervisor.js +1 -1
  33. package/src/delivery-hooks/repair-continuation.js +80 -0
  34. package/src/delivery-hooks/repair.js +164 -0
  35. package/src/delivery-hooks/supervisor-service.js +3 -1
  36. package/src/delivery-hooks/supervisor.js +88 -12
  37. package/src/desktop-access.js +2 -1
  38. package/src/desktop-host.js +62 -2
  39. package/src/durable-local-task-worker.js +372 -58
  40. package/src/execution-capability-contract.js +15 -1
  41. package/src/index.js +3 -3
  42. package/src/local-photos-read.swift +33 -0
  43. package/src/local-photos.js +47 -0
  44. package/src/local-task-attachment-store.js +31 -0
  45. package/src/local-task-destination.js +174 -0
  46. package/src/local-task.js +119 -7
  47. package/src/native-delegation-contract.js +68 -0
  48. package/src/native-provider-identity.js +60 -0
  49. package/src/orchestrator/budget.js +22 -11
  50. package/src/orchestrator/independent-audit.js +9 -18
  51. package/src/orchestrator/loop.js +27 -59
  52. package/src/orchestrator/recovery.js +32 -2
  53. package/src/provider-delegation.js +336 -0
  54. package/src/runner.js +2 -1
  55. package/src/session-supervision/artifact-observer.js +138 -0
  56. package/src/session-supervision/claude-sessions.js +5 -3
  57. package/src/session-supervision/codex-app-server.js +20 -5
  58. package/src/session-supervision/completion-artifacts.js +44 -0
  59. package/src/session-supervision/consistency-auditor.js +8 -11
  60. package/src/session-supervision/continuation-analysis.js +46 -28
  61. package/src/session-supervision/discovery-window.js +1 -1
  62. package/src/session-supervision/final-response-recovery.js +5 -3
  63. package/src/session-supervision/index.js +25 -8
  64. package/src/session-supervision/purpose-recovery.js +12 -3
  65. package/src/session-supervision/purpose-verification.js +9 -57
  66. package/src/session-supervision/recovery-frontier.js +55 -0
  67. package/src/session-supervision/remote-hosts.js +1 -1
  68. package/src/session-supervision/stop-notifications.js +8 -8
  69. package/src/sqm/agent-skill.js +1 -1
  70. package/src/sqm/checkers.js +12 -6
  71. package/src/sqm/claude-plugin-update.js +22 -8
  72. package/src/sqm/claude-plugin.js +4 -0
  73. package/src/sqm/local-tests.js +2 -2
  74. package/src/sqm/plugin-cache.js +67 -0
  75. package/src/sqm/proof.js +25 -0
  76. package/src/sqm/report.js +1 -0
  77. package/src/sqm/workflow-onboarding.js +3 -39
  78. package/src/sqm/workflow-update.js +17 -6
  79. package/src/team-status-fence-store.js +32 -0
  80. package/src/team-threads.js +478 -0
@@ -58,6 +58,7 @@ export function createBoundedLocalTaskSubtaskPool({
58
58
  prepareWorkspace = prepareLocalTaskWorkspace,
59
59
  leaseSeconds = DEFAULT_LEASE_SECONDS,
60
60
  heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS,
61
+ now = Date.now,
61
62
  setIntervalImpl = setInterval,
62
63
  clearIntervalImpl = clearInterval,
63
64
  onError = () => {},
@@ -111,10 +112,12 @@ export function createBoundedLocalTaskSubtaskPool({
111
112
  let completionConfirmed = false;
112
113
  let completionState = null;
113
114
  let providerStarted = false;
115
+ let knownLeaseExpiry = Number.NaN;
114
116
  const startedAt = Date.now();
115
117
  try {
116
118
  normalized = normalizeDurableLocalTaskSubtaskClaim(claim, {
117
119
  parentAuthority: parent,
120
+ nowMs: now(),
118
121
  leaseSeconds,
119
122
  });
120
123
  if (normalized.disposition !== "execute") {
@@ -129,6 +132,12 @@ export function createBoundedLocalTaskSubtaskPool({
129
132
  }
130
133
  batchId ??= normalized.batch.batch_id;
131
134
 
135
+ knownLeaseExpiry = normalized.attempt.leaseExpiresAtMs;
136
+ const checkLease = async () => {
137
+ if (!controller.signal.aborted && !completionConfirmed && now() >= knownLeaseExpiry) await cancel("lease_lost");
138
+ return !controller.signal.aborted;
139
+ };
140
+
132
141
  const execution = boundedLocalTaskSubtaskExecution(normalized, parent);
133
142
  const runtime = await materializeSubtaskRuntime({
134
143
  normalized,
@@ -140,7 +149,7 @@ export function createBoundedLocalTaskSubtaskPool({
140
149
  downloadSource: sourceDownloader,
141
150
  signal: controller.signal,
142
151
  });
143
- if (controller.signal.aborted) return;
152
+ if (!await checkLease()) return;
144
153
 
145
154
  const fence = durableLocalTaskSubtaskFence(normalized, parent);
146
155
  let boundaryConfirmed = false;
@@ -161,7 +170,7 @@ export function createBoundedLocalTaskSubtaskPool({
161
170
  }).catch(onError);
162
171
  return;
163
172
  }
164
- if (boundaryConfirmed !== true || controller.signal.aborted) {
173
+ if (boundaryConfirmed !== true || !await checkLease()) {
165
174
  await api.abandon_local_task_subtask_attempt_v2({
166
175
  ...fence,
167
176
  p_reason_code: controller.signal.aborted
@@ -174,6 +183,8 @@ export function createBoundedLocalTaskSubtaskPool({
174
183
 
175
184
  const heartbeat = async () => {
176
185
  if (controller.signal.aborted || completionConfirmed) return;
186
+ const renewalStartedAt = now();
187
+ if (!await checkLease()) return;
177
188
  try {
178
189
  const response =
179
190
  await api.heartbeat_local_task_subtask_attempt_v2({
@@ -190,6 +201,7 @@ export function createBoundedLocalTaskSubtaskPool({
190
201
  ) {
191
202
  return;
192
203
  }
204
+ if (!await checkLease()) return;
193
205
  if (
194
206
  response?.ok !== true ||
195
207
  response.cancel_requested === true ||
@@ -198,6 +210,8 @@ export function createBoundedLocalTaskSubtaskPool({
198
210
  normalized.parent.cancel_generation)
199
211
  ) {
200
212
  await cancel("server_cancelled");
213
+ } else {
214
+ knownLeaseExpiry = renewalStartedAt + leaseSeconds * 1_000;
201
215
  }
202
216
  } catch (error) {
203
217
  if (completionConfirmed && isKnownSubtaskFenceError(error)) return;
@@ -214,9 +228,11 @@ export function createBoundedLocalTaskSubtaskPool({
214
228
  }
215
229
  };
216
230
  heartbeatTimer = setIntervalImpl(() => {
217
- if (heartbeatInFlight) return;
218
- heartbeatInFlight = heartbeat().finally(() => {
219
- heartbeatInFlight = null;
231
+ void checkLease().then((valid) => {
232
+ if (!valid || heartbeatInFlight || completionConfirmed) return;
233
+ heartbeatInFlight = heartbeat().finally(() => {
234
+ heartbeatInFlight = null;
235
+ });
220
236
  });
221
237
  }, heartbeatIntervalMs);
222
238
 
@@ -245,6 +261,13 @@ export function createBoundedLocalTaskSubtaskPool({
245
261
  ),
246
262
  );
247
263
  }
264
+ // Lease loss after the paid boundary cannot establish a cancelled result.
265
+ // Preserve a known result, but leave interrupted work for server resolution.
266
+ if (providerStarted && controller.signal.reason === "lease_lost" &&
267
+ rawResult?.status === "cancelled") {
268
+ rawResult = { ...rawResult, status: "unknown",
269
+ failure_code: "local_task_subtask_provider_result_unknown" };
270
+ }
248
271
  const result = normalizeDurableLocalTaskSubtaskResult(
249
272
  normalizeRunResult(rawResult, startedAt),
250
273
  { normalizedClaim: normalized },
@@ -305,12 +328,15 @@ export function createBoundedLocalTaskSubtaskPool({
305
328
  }
306
329
  } finally {
307
330
  if (heartbeatTimer) clearIntervalImpl(heartbeatTimer);
308
- if (heartbeatInFlight) await heartbeatInFlight;
331
+ // Pending renewal has its own fenced completion handler; it must not
332
+ // hold a finished/cancelled slot open after its timer is cleared.
309
333
  }
310
334
  };
311
335
 
312
336
  const scheduler = async () => {
313
337
  if (!initialized) {
338
+ // Initialization only acknowledges the canonical plan. Backend keeps
339
+ // existing attempts/reservations intact; claim reconciles old generations.
314
340
  const initializedBatch =
315
341
  await api.initialize_local_task_subtask_batch_v2({
316
342
  p_turn_id: parent.turnId,
@@ -1043,7 +1043,7 @@ function uploadedAttachmentRefs(value, name) {
1043
1043
  return refs;
1044
1044
  }
1045
1045
 
1046
- function normalizeParentUploadedRef(item, name) {
1046
+ export function normalizeParentUploadedRef(item, name = "uploaded source") {
1047
1047
  record(item, name);
1048
1048
  const allowed = new Set([
1049
1049
  "schema_version",
@@ -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.7.16";
16
+ export const CLI_VERSION = "0.7.18";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function narrativeImageCapabilityMetadata() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "simy",
3
3
  "version": "0.1.0",
4
- "description": "SIMY quality and PR review. Delivery uses the explicitly requested CLI Agentic Loop.",
4
+ "description": "SIMY quality and PR review, with automatic checkpoint tracking through enabled provider hooks.",
5
5
  "author": {
6
6
  "name": "Specialist Doctors Inc.",
7
7
  "url": "https://github.com/SpecialistDoctors-Inc"
@@ -17,7 +17,7 @@
17
17
  "interface": {
18
18
  "displayName": "SIMY",
19
19
  "shortDescription": "Quality and PR review.",
20
- "longDescription": "Run explicitly requested quality checks and PR reviews. Work directly in Codex by default. When the user explicitly requests SIMY Delivery Loop, use the CLI Agentic Loop; the local hook-based Delivery skill is retired.",
20
+ "longDescription": "Run explicitly requested quality checks and PR reviews. Enabled hooks automatically track eligible conversations from user prompts through checkpoints. Stops, permission waits and opt-outs are preserved. The local Delivery skill remains retired; independently managed work uses the CLI Agentic Loop.",
21
21
  "developerName": "Specialist Doctors Inc.",
22
22
  "category": "Developer Tools",
23
23
  "capabilities": [
@@ -18,9 +18,14 @@ scans ports or reads stored session tokens.
18
18
 
19
19
  ## Delivery Loop hooks
20
20
 
21
- `delivery-loop.mjs` is an independent opt-in workflow, shared by the Codex and
22
- Claude distributions. SessionStart supplies the exact session identity. It does
23
- not start a loop. After an explicit `simy delivery start`, Stop checks that
21
+ `delivery-loop.mjs` is shared by the Codex and Claude distributions. The CLI
22
+ prepares missing plugin sources automatically and maintains enabled plugin caches.
23
+ SessionStart supplies identity without starting work. A nonempty UserPromptSubmit
24
+ starts standard Delivery Loop by default for eligible connected conversations,
25
+ including when SessionStart was missed. No per-project enable command is needed.
26
+ `simy delivery auto off --provider codex|claude` saves a project override; updates
27
+ preserve it. Existing loops, explicit stops, session opt-outs and revoked provider
28
+ consent remain authoritative. No hook grants provider trust. Stop checks that
24
29
  session's checkpoint and returns bounded next-step instructions. Approval/input
25
30
  blocks, interruption, completion, failures and missing progress stop automatic continuation.
26
31
  Provider errors/exits are surfaced to SIMY's desktop; a dead provider is not