@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
@@ -5,6 +5,8 @@ import { createLedgerCoordinatorAdapters } from "./update-detachment/ledger-adap
5
5
  import { createUpdateCoordinator } from "./update-detachment/coordinator.js";
6
6
  import { sessionWorkState, compareSessionWork, reconcileFamilyCompletion, expireSessionWorkState } from './session-supervision/work-state.js';
7
7
  import { createPullRequestObserver } from './session-supervision/pull-requests.js';
8
+ import { createTeamThreads, teamThreadKey } from './team-threads.js';
9
+ import { createTeamStatusFenceStore } from './team-status-fence-store.js';
8
10
  import { sessionDecisionPresentation } from './session-supervision/decision-presentation.js';
9
11
  import { PROVIDER_LINKS, readProviderLinks, writeProviderLink, requireProviderLink } from "./provider-links.js";
10
12
  import { randomUUID } from "node:crypto";
@@ -221,6 +223,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
221
223
  let statisticsSelection = { key: null, generation: 0 };
222
224
  const stopNotifier = createStopNotifier({ emit: event => { if (!closed && !relay) emit(event); } });
223
225
  if (env.SIMY_REMOTE_CHILD !== "1" && process.platform !== "win32") remoteHosts = (dependencies.createRemoteHostManager || createRemoteHostManager)({ root, onChange: () => { if (!closed && !relay) setImmediate(() => { void publish().catch(() => {}); }); } });
226
+ let teamThreads;
224
227
  let currentAccess = { state: 'checking', organizationId: null, organizationName: null };
225
228
  let nativeDevelopmentReports = { state: 'loading', enrollments: [] };
226
229
  let nativeDevelopmentReportsScope = null;
@@ -713,6 +716,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
713
716
  if (JSON.stringify(await readProviderLinks(root)) !== requestedLinks) { publishAgain = true; return; }
714
717
  if (connectionRevision !== requestedConnectionRevision || !sameAccess(sampledAccess, agent?.getDesktopAccess ? agent.getDesktopAccess() : await readAccess())) { publishAgain = true; return; }
715
718
  status.nativeDevelopmentReports = { state: 'loading', enrollments: [] };
719
+ status.teamThreads = teamThreads?.snapshot();
716
720
  if (!closed && !relay) emitStatus(status);
717
721
  return;
718
722
  }
@@ -764,6 +768,13 @@ export async function startDesktopHost({ input = process.stdin, output = process
764
768
  status.sessionSupervision = { ...discovery, refreshing: Boolean(sessionRefresh), sessions: reconcileFamilyCompletion(sessions.map(candidate => ({ ...candidate,
765
769
  decisionRequest: sessionDecisionPresentation(candidate, runsBySession.get(`${candidate.provider}:${candidate.session}`)),
766
770
  workState: sessionWorkState(candidate, runsBySession.get(`${candidate.provider}:${candidate.session}`) || stateRunsBySession.get(`${candidate.provider}:${candidate.session}`), { provider: discovery.providers?.[candidate.provider] }) }))).sort(compareSessionWork) };
771
+ teamThreads?.tick(sessions);
772
+ status.teamThreads = teamThreads?.snapshot();
773
+ for (const session of status.sessionSupervision.sessions) {
774
+ const key = teamThreadKey(agent.getTeamThreadsScope?.() || {}, session);
775
+ const own = status.teamThreads?.own.find(item => item.threadKey === key);
776
+ session.teamSharing = { threadKey: key, enabled: own?.enabled === true, pendingStop: status.teamThreads?.pendingStops.includes(key) === true, confirmationRequired: status.teamThreads?.confirmationRequired.includes(key) === true };
777
+ }
767
778
  const latestAccess = await accessSnapshot();
768
779
  if (JSON.stringify(await readProviderLinks(root)) !== requestedLinks ||
769
780
  !desktopAccessReady(latestAccess) || accessScope(latestAccess) !== requestedScope ||
@@ -779,12 +790,14 @@ export async function startDesktopHost({ input = process.stdin, output = process
779
790
  if (accessScope(latestConnectionStatus?.access) !== requestedScope || !desktopAccessReady(latestConnectionStatus?.access)) { publishAgain = true; return; }
780
791
  status = { ...structuredClone(latestConnectionStatus), deliveryHooks: status.deliveryHooks, sessionSupervision: status.sessionSupervision };
781
792
  }
793
+ status.teamThreads = teamThreads?.snapshot();
782
794
  if (!closed && !relay) emitStatus(status);
783
795
  }).finally(() => { publishing = null; if (publishAgain && !closed) { publishAgain = false; setImmediate(() => { void publish().catch(() => {}); }); } });
784
796
  return publishing;
785
797
  };
786
798
  const stopAgent = async () => {
787
799
  clearInterval(timer);
800
+ teamThreads?.close();
788
801
  pluginInspectionAbort.abort();
789
802
  activeStatisticsRead?.controller.abort();
790
803
  discoveryStopped = true;
@@ -843,6 +856,13 @@ export async function startDesktopHost({ input = process.stdin, output = process
843
856
  },
844
857
  });
845
858
  if (env.SIMY_UPDATE_TRANSFER_TOKEN) await coordinator.reconnect();
859
+ teamThreads = createTeamThreads({
860
+ readScope: () => agent.getTeamThreadsScope?.() || {key: accessScope(currentAccess),allowed:false,authenticated:false},
861
+ request: (body,signal) => agent.requestTeamThreads(body,signal),
862
+ fenceStore: createTeamStatusFenceStore(root),
863
+ readContext: dependencies.readTeamThreadContext,
864
+ onChange: () => { if(!closed) setImmediate(() => {void publish().catch(() => {});}); },
865
+ });
846
866
  stopWorkflows = (dependencies.startWorkflowUpdates || startWorkflowUpdates)({ enabled: env.SIMY_AUTO_UPDATE !== "0" });
847
867
  updater.on?.("change", () => { void publish().catch(() => {}); });
848
868
  updater.start({ registry: agent.registry, coordinator, isDesktopIdle: () => !unfinishedDesktopWork,
@@ -1105,15 +1125,39 @@ export async function startDesktopHost({ input = process.stdin, output = process
1105
1125
  const commandScope = accessScope(access);
1106
1126
  if (!desktopActionAllowed(access, command)) throw Object.assign(new Error('Desktop access is required.'), { code: 'desktop_access_required' });
1107
1127
  const recoveryControl = ['provider_disconnect', 'session_unmonitor'].includes(action) ||
1108
- ['session_set_auto_continue', 'session_set_email_reports'].includes(action) && command.enabled === false;
1128
+ ['session_set_auto_continue', 'session_set_email_reports', 'team_thread_share'].includes(action) && command.enabled === false;
1109
1129
  if (['detach_update', 'update_reconnect'].includes(action)) requireDesktopAccess(access);
1110
1130
  else if (!recoveryControl && desktopAccessReady(access) && !desktopActionAllowed({ state: 'unavailable' }, command)) await authorizeWork();
1111
1131
 
1112
1132
  if (!coordinator.acceptsNewWork() && !recoveryControl && !["refresh", "reconnect", "task_detail", "check_update", "quit", "delivery_stop", "delivery_pause", "delivery_reject"].includes(action)) {
1113
1133
  throw new Error("The update connection requires recovery.");
1114
1134
  }
1135
+ if (['team_threads_refresh','team_thread_open','team_thread_share'].includes(action)) {
1136
+ let ok;
1137
+ if(action === 'team_threads_refresh') ok = await teamThreads.refresh({memberId:command.memberId});
1138
+ else if(action === 'team_thread_open') ok = await teamThreads.open({memberId:command.memberId,threadKey:command.threadKey});
1139
+ else {
1140
+ if (command.organizationId !== undefined && command.organizationId !== access.organizationId) throw new Error('Sharing organization changed. Confirm again.');
1141
+ // Publication reads this host's provider context. Never reinterpret
1142
+ // a remote or ambiguous identity as a same-ID local conversation.
1143
+ if ((command.hostId && command.hostId !== 'local') || (!command.hostId && remoteHosts?.hasSession(command.session))) {
1144
+ throw new Error('Team sharing requires an explicitly selected local conversation.');
1145
+ }
1146
+ const selected = sessionStatus.sessions.find(item => (!item.hostId || item.hostId === 'local') && item.provider === command.provider && item.session === command.session);
1147
+ if (command.enabled === false) {
1148
+ // Revocation uses authenticated own rows, never provider context.
1149
+ const key = command.threadKey ?? teamThreadKey(agent.getTeamThreadsScope(), { provider: command.provider, session: command.session });
1150
+ ok = await teamThreads.stopSharing(key);
1151
+ } else {
1152
+ if(!selected || command.threadKey) throw new Error('Refresh and select your conversation again.');
1153
+ ok = await teamThreads.setSharing(selected,command.enabled);
1154
+ }
1155
+ }
1156
+ emit({type:'result',id:command.id,ok:ok===true}); void publish().catch(() => {}); return;
1157
+ }
1158
+
1115
1159
  let commandProviderLink;
1116
- if (command.provider && !["provider_connect", "provider_disconnect", "delivery_stop", "delivery_pause", "delivery_reject", "session_untrack", "session_unmonitor"].includes(action) &&
1160
+ if (command.provider && !["refresh", "provider_connect", "provider_disconnect", "delivery_stop", "delivery_pause", "delivery_reject", "session_untrack", "session_unmonitor"].includes(action) &&
1117
1161
  !(action === "session_set_auto_continue" && command.enabled === false)) {
1118
1162
  commandProviderLink = await requireProviderLink({ root: deliveryRoot, provider: command.provider });
1119
1163
  }
@@ -1424,6 +1468,22 @@ export async function startDesktopHost({ input = process.stdin, output = process
1424
1468
  await authorizeWork(commandScope);
1425
1469
  const result = await deadlines.decide(context, { ...command, requestId: command.requestId || command.decisionId });
1426
1470
  if (result.status === 'conflict') throw Object.assign(new Error('Deadline changed; refresh before deciding.'), { code: 'conflict' });
1471
+ } else if (action === "session_set_pinned") {
1472
+ if (typeof command.enabled !== 'boolean') throw new Error('Choose whether to pin this conversation.');
1473
+ const candidate = await selectedSession(command, { preferObserved: true });
1474
+ await authorizeWork(commandScope);
1475
+ const pinScope = command.pinScope ?? 'legacy';
1476
+ if (typeof pinScope !== 'string' || !pinScope || pinScope.length > 512) throw new Error('Invalid pin scope.');
1477
+ await updateRecord(deliveryRoot, command.provider, command.session, previous => {
1478
+ const scopes = new Set(Array.isArray(previous?.listPinScopes) ? previous.listPinScopes : previous?.listPinned ? ['legacy'] : []);
1479
+ if (command.enabled) scopes.add(pinScope); else scopes.delete(pinScope);
1480
+ return {
1481
+ ...(previous || { schemaVersion: 1, provider: command.provider, session: command.session, cwd: candidate.cwd, observedAt: Date.now() }),
1482
+ listPinned: scopes.size > 0, listPinScopes: [...scopes],
1483
+ ...(command.enabled ? { listMetadata: { title: candidate.title || '', createdAt: candidate.createdAt ?? null, lastActivityAt: candidate.lastActivityAt ?? null } } : {}),
1484
+ };
1485
+ });
1486
+ refreshSessions(true);
1427
1487
  } else if (action === "session_archive" || action === "session_restore") {
1428
1488
  const candidate = await selectedSession(command, { preferObserved: true });
1429
1489
  await authorizeWork(commandScope);