@awak-app/simy-cli 0.6.16 → 0.6.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.
- package/README.md +99 -6
- package/package.json +1 -1
- package/src/agent.js +123 -28
- package/src/auto-update.js +15 -3
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/connection-reminder.mjs +5 -2
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +30 -3
- package/src/codex-plugins/simy/hooks/dev-work.mjs +7 -1
- package/src/codex-plugins/simy/hooks/hooks.json +2 -2
- package/src/codex-work-pattern-sync.js +37 -9
- package/src/delivery-hooks/command.js +9 -2
- package/src/delivery-hooks/desktop.js +10 -12
- package/src/delivery-hooks/evidence.js +50 -0
- package/src/delivery-hooks/supervisor.js +8 -2
- package/src/desktop-access.js +4 -3
- package/src/desktop-host.js +669 -73
- package/src/doctor.js +2 -1
- package/src/host-exit-diagnostics.js +28 -0
- package/src/index.js +1 -0
- package/src/native-development-report-completion.js +143 -0
- package/src/native-development-reports.js +137 -0
- package/src/orchestrator/checklist-continuation.js +5 -0
- package/src/orchestrator/delivery/WORKFLOW.md +4 -0
- package/src/orchestrator/delivery/references/state-model.md +187 -0
- package/src/orchestrator/loop.js +119 -34
- package/src/provider-links.js +74 -0
- package/src/runner.js +31 -2
- package/src/session-supervision/automatic-continuation.js +31 -2
- package/src/session-supervision/claude-sessions.js +25 -15
- package/src/session-supervision/codex-app-server.js +27 -4
- package/src/session-supervision/index.js +163 -42
- package/src/session-supervision/statistics.js +76 -15
- package/src/sqm/command.js +58 -7
- package/src/sqm/evidence-client.js +11 -1
- package/src/sqm/failure-circuit.js +28 -6
- package/src/sqm/history-store.js +49 -18
- package/src/sqm/provider-diagnostics.js +1 -0
- package/src/sqm/review-worker.js +70 -21
- package/src/sqm/workflow-update.js +12 -3
- package/src/windows-bootstrap.js +2 -1
- package/src/windows-desktop-updater.js +6 -1
- package/src/windows-inbound.js +9 -0
package/README.md
CHANGED
|
@@ -15,12 +15,26 @@ cached in memory, so restarting the host may retrieve live contexts again.
|
|
|
15
15
|
checking for updates. `simy doctor` performs read-only local connection and
|
|
16
16
|
provider login checks and prints a JSON diagnostic report: installed/running
|
|
17
17
|
versions, stale discovery records, session expiry, provider readiness, and recent
|
|
18
|
-
structured SQM failures
|
|
18
|
+
structured SQM failures, plus `host_exit` with the latest native desktop host's
|
|
19
|
+
abnormal exit time and status. `failures.total_failures` counts scoped SQM
|
|
20
|
+
failures only; zero does not mean the desktop has never crashed. `host_exit` is
|
|
21
|
+
local machine history, independent of account/organization and current daemon
|
|
22
|
+
reachability. A recorded exit remains visible after restart; `not_recorded` means
|
|
23
|
+
no retained exit record, and `unavailable` means it could not be verified. It is
|
|
24
|
+
not a lifetime crash count. Only the fixed header is read; stderr is never
|
|
25
|
+
included. Doctor does not run a model, renew login, or restart tasks.
|
|
19
26
|
Provider login is separate from model access; unverified model access remains
|
|
20
27
|
`unknown`. Diagnostic journals retain stage/code/HTTP status only, use private
|
|
21
28
|
files scoped to the Web/account/organization, rotate each writer's four 64 KiB
|
|
22
29
|
segments, and keep lifetime failure counts after event rotation.
|
|
23
30
|
|
|
31
|
+
The desktop host continues sampling connection and access while optional
|
|
32
|
+
conversation metadata is pending. It retains the last completed conversation
|
|
33
|
+
metadata only within the same verified scope, clears it after access or account
|
|
34
|
+
changes, and merges completed metadata with the newer connection observation.
|
|
35
|
+
This prevents a slow metadata read from making a live service appear disconnected;
|
|
36
|
+
it does not conceal actual host exits, failed connections, or revoked access.
|
|
37
|
+
|
|
24
38
|
The organization menu's **Allow Codex / Claude fallback** setting defaults to
|
|
25
39
|
off and grants only read-only SQM analysis permission to use the other signed-in
|
|
26
40
|
local provider. It is saved per Web/account/organization. Switching requires an
|
|
@@ -72,6 +86,18 @@ and an already consumed remote result prevent automatic recovery. An unfinished
|
|
|
72
86
|
worker or a missing, invalid or redacted structured result still requires
|
|
73
87
|
reconciliation; no unknown external operation is automatically replayed.
|
|
74
88
|
|
|
89
|
+
After CI finishes successfully, the authenticated daemon refreshes the independent
|
|
90
|
+
review of the same saved candidate without dispatching its implementation worker.
|
|
91
|
+
The reviewed HEAD, fetched base, objective, preparation evidence and minimum
|
|
92
|
+
completion matrix must still match; changed candidates require explicit work.
|
|
93
|
+
Pending/failing checks, a dirty checkout and missing required checks do not trigger
|
|
94
|
+
this review. Human peer approval remains required when the charter requires it.
|
|
95
|
+
The refreshed review is checked against newly collected evidence before readiness
|
|
96
|
+
can pass. Stop cancels review continuation. A failed or interrupted refresh is not
|
|
97
|
+
repeated on every monitor tick; explicitly continue the same run to recover.
|
|
98
|
+
Older reviews without a saved CI basis can refresh only when their prior evidence
|
|
99
|
+
still establishes the complete reviewed identity; otherwise explicitly continue.
|
|
100
|
+
|
|
75
101
|
For interrupted Agentic work, `GET /v1/agentic-loop/:id/recovery` provides a
|
|
76
102
|
read-only objective and checkout fingerprint. Reconcile an uncertain side effect
|
|
77
103
|
with `POST` to that endpoint (`action: reconcile`, authoritative
|
|
@@ -124,6 +150,12 @@ a fresh Codex or Claude login probe succeeds, including after an app restart.
|
|
|
124
150
|
An unchanged ready status cannot repeatedly re-run a failed check. Manual retry
|
|
125
151
|
remains available; continuing the conversation still requires **Auto-continue**.
|
|
126
152
|
|
|
153
|
+
You can enable **Auto-continue** while a conversation is running when its original
|
|
154
|
+
provider configuration is verified. SIMY saves the preference and waits for the
|
|
155
|
+
current turn to finish before checking remaining work. Configuration and runtime
|
|
156
|
+
blockers appear in the conversation view with recovery guidance; turning the
|
|
157
|
+
setting off does not interrupt the current turn.
|
|
158
|
+
|
|
127
159
|
Claude read-only structured reviews retry once when the provider explicitly
|
|
128
160
|
reports exhausted turns or structured-output repair attempts, within the original
|
|
129
161
|
timeout. Other failures retain distinct codes and recovery hints. Safe native
|
|
@@ -211,9 +243,31 @@ marketplace still points to SIMY’s generated local source. Plugin versions inc
|
|
|
211
243
|
receive a fresh cache. Use a new Codex task to pick up updated skills; running
|
|
212
244
|
tasks are not restarted. Missing or disabled plugins are not enabled automatically.
|
|
213
245
|
Failed refreshes retry on the next check without blocking daemon startup.
|
|
246
|
+
Auto-continue ON saves consent for the selected original conversation; it does not
|
|
247
|
+
immediately launch a second turn. A verified desktop discovery admission or an
|
|
248
|
+
existing exact admission saves this preference without another provider RPC.
|
|
249
|
+
After the current turn settles, continuation separately rechecks context, the
|
|
250
|
+
live provider channel, approvals and the opt-in epoch. Required human decisions
|
|
251
|
+
remain pending, and OFF prevents future continuation without interrupting the
|
|
252
|
+
current turn. The macOS switch and label explain these boundaries through their
|
|
253
|
+
tooltips and accessibility help.
|
|
254
|
+
|
|
255
|
+
Conversation statistics estimate work time from activity events within the last
|
|
256
|
+
seven days. Usage counters alone leave work time, idle time and utilization
|
|
257
|
+
unavailable; a single observed activity event is a measured zero-duration sample.
|
|
258
|
+
Missing usage records remain distinct from recorded zero tokens.
|
|
259
|
+
|
|
214
260
|
The desktop Sessions page loads a recent window of up to 200 conversations,
|
|
215
|
-
ranked by last activity.
|
|
216
|
-
|
|
261
|
+
ranked by last activity. Codex candidates come from CLI, VS Code and app-server
|
|
262
|
+
conversation sources; non-interactive exec, unknown sources and delegated agents
|
|
263
|
+
are excluded. A hook observation alone cannot restore a filtered conversation.
|
|
264
|
+
Explicitly tracked, monitored, auto-continue and unsettled supervision records
|
|
265
|
+
remain visible even outside that window. When a retained Codex conversation is
|
|
266
|
+
omitted from the recent provider page, SIMY reads its exact metadata without
|
|
267
|
+
loading turns or taking writer ownership. The same interactive source filter and
|
|
268
|
+
a matching active rollout path are required before restoring provider capabilities
|
|
269
|
+
and probing its latest turn. Exact reads and turn probes share a bounded
|
|
270
|
+
observation deadline. Claude discovery
|
|
217
271
|
ranks file metadata before opening selected transcripts. A conflicting saved
|
|
218
272
|
workspace leaves only that conversation's Action association unavailable; it
|
|
219
273
|
does not stop discovery or change the saved identity. The page lets users
|
|
@@ -586,6 +640,26 @@ execution. Keep concrete regression and delivery acceptance evidence separately.
|
|
|
586
640
|
If knowledge is unavailable, the command returns exit 2 with the service cause
|
|
587
641
|
and writes no new proof; any older artifact is not evidence for that attempt.
|
|
588
642
|
|
|
643
|
+
Cloud storage is separate from the check outcome. `check` retains the completed
|
|
644
|
+
proof and local check history if its evidence POST fails, returns exit 2 when
|
|
645
|
+
storage is unconfirmed (exit 1 still takes precedence for a failed check), and
|
|
646
|
+
writes a redacted storage receipt to `<proof-path>.upload.json`. Only a server
|
|
647
|
+
acknowledgement matching the proof ID, evidence ID and proof digest counts as
|
|
648
|
+
stored. `--no-upload` intentionally keeps evidence local and does not return
|
|
649
|
+
exit 2 for storage. A successful upload is not evidence of applicable coverage.
|
|
650
|
+
|
|
651
|
+
After resolving a storage/authentication failure, explicitly recover the original
|
|
652
|
+
run with `simy sqm upload-proof --proof /path/to/proof.json` (add
|
|
653
|
+
`--signed-evidence /path/to/signature.json` if using a custom signature path, and
|
|
654
|
+
`--host <original-origin>` for its SIMY environment). This verifies the original
|
|
655
|
+
signature and current organization/account/device before sending the same proof;
|
|
656
|
+
it never reruns checks, resigns evidence or automatically replays an ambiguous
|
|
657
|
+
POST. The backend reconciles repeated proof IDs against their exact digest and
|
|
658
|
+
evidence ID. The receipt includes the saved record ID and evidence ID on success.
|
|
659
|
+
This recovers a historical run, not acceptance of the current working tree.
|
|
660
|
+
Dev/production record visibility, deployed gateway/schema and UI destination
|
|
661
|
+
permissions still require validation in the corresponding environment.
|
|
662
|
+
|
|
589
663
|
Normal runs do not require a separate sync command: the CLI
|
|
590
664
|
uses a verified local cache and revalidates it with TTL and ETag semantics.
|
|
591
665
|
The Agentic Loop runs min-check after each code modification and the full check
|
|
@@ -697,7 +771,7 @@ from an empty CLI chat, types repository, branch, executor, attachment, and task
|
|
|
697
771
|
input through a pseudoterminal, creates the Web ledger, spawns real child
|
|
698
772
|
processes, and exercises HIL plus lifecycle controls. It regenerates the
|
|
699
773
|
screenshots, terminal frames, raw transcript, and SHA-256 manifest under
|
|
700
|
-
|
|
774
|
+
`.artifacts/e2e/cli-agentic-loop-console/`.
|
|
701
775
|
|
|
702
776
|
## Delivery Loop design proposals
|
|
703
777
|
|
|
@@ -759,6 +833,11 @@ not a GitHub hook that mechanically intercepts every external close command.
|
|
|
759
833
|
|
|
760
834
|
### Submit repaired local delivery evidence to SQM
|
|
761
835
|
|
|
836
|
+
The [integrated delivery state model](src/orchestrator/delivery/references/state-model.md)
|
|
837
|
+
connects delivery milestones, repair/wait/resume branches and actual controller
|
|
838
|
+
states. Both executor and auditor receive its path. It documents existing control
|
|
839
|
+
and evidence duties; it adds no scheduler or transition-enforcement mechanism.
|
|
840
|
+
|
|
762
841
|
The bundled Delivery Loop maintains an external acceptance checklist with separate
|
|
763
842
|
user-journey and technical-invariant results. It refreshes the table after each
|
|
764
843
|
evaluated action and marks evidence pending when the code or runtime context
|
|
@@ -815,11 +894,25 @@ explains baseline updates, historical regression coverage and evidence limits.
|
|
|
815
894
|
SQM Tasks has separate **Analysis** and **Checks** lists on macOS and Windows.
|
|
816
895
|
Analysis retains local review summaries; Checks retains both `simy sqm min-check`
|
|
817
896
|
and `simy sqm check` outcomes, including checks run by the Delivery Loop.
|
|
818
|
-
Each list
|
|
819
|
-
`~/.simy/sqm-history`)
|
|
897
|
+
Each desktop list displays the latest 100 runs. Durable summary files under
|
|
898
|
+
`SIMY_HOME/sqm-history` (default `~/.simy/sqm-history`) are retained without
|
|
899
|
+
a count or age cutoff, isolated by web origin, account and organization. Adding
|
|
900
|
+
a run never deletes older history. Old job keys remain available through the
|
|
901
|
+
local task-detail lookup, including failure stage/code, HTTP status, provider
|
|
902
|
+
diagnostics and the matching web task link. The separate rotating diagnostic
|
|
903
|
+
log is a recent-event view; retained job summaries provide longer-term correlation.
|
|
904
|
+
Operators should include this directory in their disk usage and backup policy.
|
|
905
|
+
Deleting a scope directory explicitly removes its history; previously pruned
|
|
906
|
+
records cannot be recovered.
|
|
820
907
|
History remains visible with local analysis off and after restarting. A saved
|
|
821
908
|
in-flight analysis is shown as Interrupted until this client claims it again;
|
|
822
909
|
it does not contribute to the running count. The web workspace owns retry status.
|
|
910
|
+
Model/provider execution failures permit one recovery claim after a five-minute
|
|
911
|
+
cooldown, even when the app/account identity is unchanged. If that attempt fails
|
|
912
|
+
again, the pause survives reconnects and host restarts until a newer SIMY version
|
|
913
|
+
is installed. Sign-in, local installation, SIMY access, schema and persistence
|
|
914
|
+
errors do not open this automatic provider recovery path. The existing six-failure
|
|
915
|
+
circuit still protects other analysis failures.
|
|
823
916
|
|
|
824
917
|
Only summary metadata is retained, without analysis prompts, source inputs,
|
|
825
918
|
model responses or credentials. Historical summaries are not a replacement for
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { normalizeProviderModelSelection } from "./provider-model-selection.js";
|
|
2
2
|
import { assertConnectionScope, connectionScope, connectionDiagnostic } from "./connection-diagnostics.js";
|
|
3
3
|
import { authorizeLaunch } from "./launch-authorization.js";
|
|
4
|
+
import { resolveDesktopExecutorCommand } from "./desktop-executor.js";
|
|
4
5
|
import { normalizePurposeVerification, assertPurposeActor, preparePurposeVerification, verifyPurposeLocalTask, purposeSigningKeyPath, purposeVerificationCapability, purposeWorkspaceBusy } from "./purpose-local-proof.js";
|
|
5
6
|
import { startupRequirementChecks, summarizeStartupChecks } from "./startup-preflight.js";
|
|
6
7
|
import { BROWSER_HANDOFF_TRANSPORT, browserHandoffPage, decryptBrowserHandoff } from "./browser-handoff.js";
|
|
7
8
|
import { createAuthorizationWindow } from "./browser-authorization.js";
|
|
8
9
|
import { createSqmHistoryStore, projectSqmHistory, sqmHistoryScope } from "./sqm/history-store.js";
|
|
9
10
|
import { createSqmFailureCircuit, createSqmFailureCircuitStore } from "./sqm/failure-circuit.js";
|
|
11
|
+
import { readProviderLinks } from './provider-links.js';
|
|
10
12
|
import { createDesktopAccessPolicy, desktopAccessReady, requireDesktopAccess } from './desktop-access.js';
|
|
11
13
|
import { readSqmEnabled, writeSqmEnabled, readSqmSettings, writeSqmEngineFallback } from "./desktop-sqm-settings.js";
|
|
12
14
|
import { createServer } from "node:http";
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
restoreRun,
|
|
36
38
|
resumeLocalCodingRun,
|
|
37
39
|
startLocalCodingRun,
|
|
40
|
+
executeProcessAttempt,
|
|
38
41
|
syncLocalCodingRun,
|
|
39
42
|
stopLocalCodingRun,
|
|
40
43
|
toCompatibleCodingLoopState,
|
|
@@ -269,6 +272,13 @@ export async function startAgent({
|
|
|
269
272
|
daemon,
|
|
270
273
|
);
|
|
271
274
|
let availableCapabilities;
|
|
275
|
+
const inspectedLinks = new WeakMap(), runAuthorizations = new WeakMap();
|
|
276
|
+
let providerLinks = await readProviderLinks(sessionRoot);
|
|
277
|
+
const requireLinkedProvider = async (provider, expected) => {
|
|
278
|
+
const link = (await readProviderLinks(sessionRoot))[provider];
|
|
279
|
+
if (!link?.connected || expected && link.epoch !== expected.epoch) throw Object.assign(new Error("Reconnect this provider to SIMY before using it."), { code: "provider_link_disconnected" });
|
|
280
|
+
return link;
|
|
281
|
+
};
|
|
272
282
|
let providerStatuses = { codex: { state: "unknown" }, claude: { state: "unknown" } };
|
|
273
283
|
const providerRecoveryRevisions = { codex: 0, claude: 0 };
|
|
274
284
|
const refreshAvailableCapabilities = () => {
|
|
@@ -390,6 +400,7 @@ export async function startAgent({
|
|
|
390
400
|
token: session.token,
|
|
391
401
|
deviceId: session.device_id,
|
|
392
402
|
accountId: session.account_id,
|
|
403
|
+
connectedProviders: ["codex", "claude"].filter(provider => providerLinks[provider]?.connected),
|
|
393
404
|
});
|
|
394
405
|
} catch (error) {
|
|
395
406
|
reportCodexWorkPatternSyncError(error, quiet);
|
|
@@ -449,7 +460,7 @@ export async function startAgent({
|
|
|
449
460
|
providerReadForced = force;
|
|
450
461
|
providerReadInFlight = (async () => {
|
|
451
462
|
const previous = providerStatuses;
|
|
452
|
-
await Promise.all(["codex", "claude"].map(async (provider) => {
|
|
463
|
+
await Promise.all([readProviderLinks(sessionRoot).then(links => { providerLinks = links; }), ...["codex", "claude"].map(async (provider) => {
|
|
453
464
|
const next = await (provider === "codex" ? codexStatus : claudeStatus)({ force });
|
|
454
465
|
// Publish each verified result independently. A slow Claude probe must
|
|
455
466
|
// not keep a completed Codex result hidden (or vice versa).
|
|
@@ -459,7 +470,7 @@ export async function startAgent({
|
|
|
459
470
|
providerRecoveryRevisions[provider] += 1;
|
|
460
471
|
notifyDesktopStatusChange();
|
|
461
472
|
}
|
|
462
|
-
}));
|
|
473
|
+
})]);
|
|
463
474
|
const binaryStateChanged = ["codex", "claude"].some((provider) =>
|
|
464
475
|
previous[provider]?.version !== providerStatuses[provider]?.version ||
|
|
465
476
|
previous[provider]?.state !== providerStatuses[provider]?.state);
|
|
@@ -473,25 +484,85 @@ export async function startAgent({
|
|
|
473
484
|
};
|
|
474
485
|
|
|
475
486
|
const inspectReadyBackend = async (backend, options) => {
|
|
487
|
+
const link = await requireLinkedProvider(backend);
|
|
476
488
|
await readProviderStatuses(options);
|
|
489
|
+
await requireLinkedProvider(backend, link);
|
|
477
490
|
const inspection = normalizeBackendInspection(backend, availableCapabilities);
|
|
478
491
|
if (inspection.compatible) {
|
|
479
492
|
const failure = providerAuthenticationFailure(backend, providerStatuses[backend]);
|
|
480
493
|
if (failure) throw Object.assign(new Error(failure.summary), failure);
|
|
481
494
|
}
|
|
495
|
+
inspectedLinks.set(inspection, link);
|
|
482
496
|
return inspection;
|
|
483
497
|
};
|
|
484
498
|
|
|
485
|
-
const requireRunProvider = async (run) => {
|
|
499
|
+
const requireRunProvider = async (run, { backend = run.request.backend } = {}) => {
|
|
486
500
|
const scope = await authorizeDesktopWork();
|
|
487
|
-
const backend = run.request.backend;
|
|
488
501
|
const inspection = await inspectReadyBackend(backend, { force: true });
|
|
489
502
|
await authorizeDesktopWork(scope);
|
|
503
|
+
await requireLinkedProvider(backend, inspectedLinks.get(inspection));
|
|
490
504
|
if (!inspection.compatible) {
|
|
491
505
|
throw Object.assign(new Error(backendPreflightSummary(backend, inspection)), {
|
|
492
506
|
code: `desktop_executor_${inspection.status}`,
|
|
493
507
|
});
|
|
494
508
|
}
|
|
509
|
+
await bindRunAuthorization(run, scope, inspection, backend);
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const authorizeProviderDispatch = async (backend, scope, link) => {
|
|
513
|
+
await authorizeDesktopWork(scope);
|
|
514
|
+
await requireLinkedProvider(backend, link);
|
|
515
|
+
};
|
|
516
|
+
const bindRunAuthorization = async (run, scope, inspection, backend = run.request.backend) => {
|
|
517
|
+
const links = await readProviderLinks(sessionRoot);
|
|
518
|
+
links[backend] = inspectedLinks.get(inspection);
|
|
519
|
+
await authorizeProviderDispatch(backend, scope, links[backend]);
|
|
520
|
+
runAuthorizations.set(run, { scope, links });
|
|
521
|
+
};
|
|
522
|
+
const providerRuntimeOptions = (task, options = {}, scope, link) => {
|
|
523
|
+
const guard = () => authorizeProviderDispatch(task.backend, scope, link);
|
|
524
|
+
const wrap = callback => async (...args) => {
|
|
525
|
+
await guard();
|
|
526
|
+
await callback?.(...args);
|
|
527
|
+
await guard();
|
|
528
|
+
};
|
|
529
|
+
return { ...options, beforeProviderInvocation: wrap(options.beforeProviderInvocation), beforeSpawn: wrap(options.beforeSpawn) };
|
|
530
|
+
};
|
|
531
|
+
const codingRuntimeOptions = run => {
|
|
532
|
+
const wrap = (custom, marker) => async context => {
|
|
533
|
+
// Explicit resume may reauthorize the retained run callbacks. Freeze this
|
|
534
|
+
// invocation's scope so reconnection during command resolution cannot do so.
|
|
535
|
+
const authorization = runAuthorizations.get(run);
|
|
536
|
+
const guard = backend => authorizeProviderDispatch(backend, authorization?.scope, authorization?.links[backend]);
|
|
537
|
+
await guard(context.backend);
|
|
538
|
+
if (custom) return custom(context);
|
|
539
|
+
const repositoryPath = await resolveRepositoryPath(run.request);
|
|
540
|
+
return executeProcessAttempt({ ...context, run, repositoryPath, ...(marker ? { marker } : {}),
|
|
541
|
+
resolveCommandImpl: async input => {
|
|
542
|
+
const command = await (dependencies.resolveDesktopExecutorCommand || resolveDesktopExecutorCommand)(input);
|
|
543
|
+
await guard(input.backend);
|
|
544
|
+
return command;
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
return { ...runOptions, executeAttempt: wrap(runOptions.executeAttempt),
|
|
549
|
+
executeIndependentAudit: wrap(runOptions.executeIndependentAudit, "SIMY_AUDIT_JSON:") };
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const recheckAuthorizedCodingRun = (run, options = {}) => {
|
|
553
|
+
const scope = desktopScopeRevision();
|
|
554
|
+
return recheckLocalCodingRun(run, { collectEvidence: runOptions.collectEvidence, ...options,
|
|
555
|
+
executeIndependentAudit: context => withUpdateWork(updateManager,
|
|
556
|
+
() => codingRuntimeOptions(run).executeIndependentAudit(context)),
|
|
557
|
+
authorizeReview: async () => {
|
|
558
|
+
await authorizeDesktopWork(scope);
|
|
559
|
+
if (!isSessionValid(session, Date.now(), apiOrigin) || !sameRunScope(run.session, session)) {
|
|
560
|
+
throw new Error("The original authorized coding run session changed.");
|
|
561
|
+
}
|
|
562
|
+
await requireRunProvider(run, { backend: run.snapshot.charter.audit_backend });
|
|
563
|
+
await authorizeDesktopWork(scope);
|
|
564
|
+
},
|
|
565
|
+
});
|
|
495
566
|
};
|
|
496
567
|
|
|
497
568
|
let organizationCancellation = Promise.resolve();
|
|
@@ -740,7 +811,7 @@ export async function startAgent({
|
|
|
740
811
|
for (const run of registry.list()) {
|
|
741
812
|
if (!["pr_ready_for_review", "merge_ready"].includes(run.status) || run.operation || run.child) continue;
|
|
742
813
|
try {
|
|
743
|
-
await (dependencies.recheckCodingRun ||
|
|
814
|
+
await (dependencies.recheckCodingRun || recheckAuthorizedCodingRun)(run);
|
|
744
815
|
} catch (error) {
|
|
745
816
|
await markCodingRunMonitorFailure(run, error);
|
|
746
817
|
}
|
|
@@ -933,7 +1004,9 @@ export async function startAgent({
|
|
|
933
1004
|
});
|
|
934
1005
|
}
|
|
935
1006
|
await authorizeDesktopWork(scope);
|
|
936
|
-
|
|
1007
|
+
const link = inspectedLinks.get(inspection);
|
|
1008
|
+
await requireLinkedProvider(task.backend, link);
|
|
1009
|
+
return (dependencies.runLocalTask || startLocalTask)(task, providerRuntimeOptions(task, options, scope, link));
|
|
937
1010
|
},
|
|
938
1011
|
attachmentStore: localTaskAttachmentStore,
|
|
939
1012
|
getDeviceId: () => session?.device_id || null,
|
|
@@ -964,7 +1037,9 @@ export async function startAgent({
|
|
|
964
1037
|
const actor = JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id]);
|
|
965
1038
|
const settings = await readSqmSettings(sessionRoot, apiOrigin, session);
|
|
966
1039
|
const engineSettings = JSON.stringify([settings.enable_engine_fallback, settings.allowed_fallback_providers]);
|
|
967
|
-
const
|
|
1040
|
+
const reviewLinks = await readProviderLinks(sessionRoot);
|
|
1041
|
+
const beforeDispatch = async (selectedProvider = provider) => {
|
|
1042
|
+
await requireLinkedProvider(selectedProvider, reviewLinks[selectedProvider]);
|
|
968
1043
|
await authorizeDesktopWork(scope);
|
|
969
1044
|
const current = await readSqmSettings(sessionRoot, apiOrigin, session);
|
|
970
1045
|
if (actor !== JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id]) ||
|
|
@@ -973,19 +1048,22 @@ export async function startAgent({
|
|
|
973
1048
|
}
|
|
974
1049
|
await context.beforeLeaseDispatch?.();
|
|
975
1050
|
await authorizeDesktopWork(scope);
|
|
1051
|
+
await requireLinkedProvider(selectedProvider, reviewLinks[selectedProvider]);
|
|
976
1052
|
const finalSettings = await readSqmSettings(sessionRoot, apiOrigin, session);
|
|
977
1053
|
if (actor !== JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id]) ||
|
|
978
1054
|
engineSettings !== JSON.stringify([finalSettings.enable_engine_fallback, finalSettings.allowed_fallback_providers])) {
|
|
979
1055
|
throw Object.assign(new Error("SQM actor or fallback settings changed."), { code: "desktop_access_required" });
|
|
980
1056
|
}
|
|
1057
|
+
await requireLinkedProvider(selectedProvider, reviewLinks[selectedProvider]);
|
|
981
1058
|
};
|
|
982
1059
|
return (dependencies.runSqmLocalReview || ((value, selected, options) => runStructuredProviderReview(selected, value, options)))(request, provider, {
|
|
983
1060
|
signal: context.signal, beforeDispatch, readOnlyAnalysis: true, enableEngineFallback: settings.enable_engine_fallback,
|
|
1061
|
+
allowAutomaticRetry: context.allowAutomaticRetry, beforeAutomaticRetry: context.beforeAutomaticRetry,
|
|
984
1062
|
allowedFallbackProviders: settings.allowed_fallback_providers, providerReportingSupported: context.providerReportingSupported === true,
|
|
985
1063
|
});
|
|
986
1064
|
},
|
|
987
1065
|
getEnvironmentKey: () => JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id,
|
|
988
|
-
session?.expires_at, sqmEnabled, sqmEngineSettings, ["codex", "claude"].map(provider => [providerStatuses[provider]?.state, providerStatuses[provider]?.version])]),
|
|
1066
|
+
session?.expires_at, sqmEnabled, sqmEngineSettings, providerLinks, ["codex", "claude"].map(provider => [providerStatuses[provider]?.state, providerStatuses[provider]?.version])]),
|
|
989
1067
|
onChange: notifyDesktopStatusChange,
|
|
990
1068
|
getWorkspaceUrl: () => {
|
|
991
1069
|
const slug = desktopAccess.snapshot().organizationSlug;
|
|
@@ -996,7 +1074,7 @@ export async function startAgent({
|
|
|
996
1074
|
recordHistory: (scope, item) => sqmHistory.record(scope, item),
|
|
997
1075
|
onHistoryError: () => { if (!quiet) console.error("Could not save the local SQM analysis history."); },
|
|
998
1076
|
getProvider: () => !desktopWorkAllowed() || availableCapabilities?.features?.sqm_local_review !== true ? null : ["codex", "claude"].find(
|
|
999
|
-
(provider) => availableCapabilities?.backends?.[provider] === true &&
|
|
1077
|
+
(provider) => providerLinks[provider]?.connected === true && availableCapabilities?.backends?.[provider] === true &&
|
|
1000
1078
|
providerStatuses[provider]?.state === "ready",
|
|
1001
1079
|
) || null,
|
|
1002
1080
|
onError: (error) => reportSqmLocalReviewError(error, quiet),
|
|
@@ -1025,7 +1103,7 @@ export async function startAgent({
|
|
|
1025
1103
|
continueLocalCodingRunAfterRepositoryApproval(
|
|
1026
1104
|
run,
|
|
1027
1105
|
{ repository: selected.repository, localPath: selected.local_path },
|
|
1028
|
-
|
|
1106
|
+
codingRuntimeOptions(run),
|
|
1029
1107
|
),
|
|
1030
1108
|
);
|
|
1031
1109
|
};
|
|
@@ -1156,6 +1234,7 @@ export async function startAgent({
|
|
|
1156
1234
|
return;
|
|
1157
1235
|
}
|
|
1158
1236
|
try {
|
|
1237
|
+
await requireLinkedProvider(event.source === "claude_code" ? "claude" : "codex");
|
|
1159
1238
|
json(
|
|
1160
1239
|
res,
|
|
1161
1240
|
200,
|
|
@@ -1167,7 +1246,8 @@ export async function startAgent({
|
|
|
1167
1246
|
}),
|
|
1168
1247
|
);
|
|
1169
1248
|
} catch (error) {
|
|
1170
|
-
json(res, error instanceof DevWorkEventError ? error.status : 502, {
|
|
1249
|
+
json(res, error?.code === "provider_link_disconnected" ? 409 : error instanceof DevWorkEventError ? error.status : 502, {
|
|
1250
|
+
...(error?.code === "provider_link_disconnected" ? { code: error.code } : {}),
|
|
1171
1251
|
error: error instanceof DevWorkEventError ? error.message : "dev-work event failed",
|
|
1172
1252
|
});
|
|
1173
1253
|
}
|
|
@@ -1836,19 +1916,18 @@ export async function startAgent({
|
|
|
1836
1916
|
localTaskRegistry.create(task);
|
|
1837
1917
|
requestClaim.complete(task);
|
|
1838
1918
|
requestClaimCompleted = true;
|
|
1839
|
-
const runLocalTask =
|
|
1840
|
-
|
|
1841
|
-
((localTask) => startLocalTask(localTask, { session, apiOrigin, beforeSpawn: async () => {
|
|
1919
|
+
const runLocalTask = dependencies.runLocalTask || startLocalTask;
|
|
1920
|
+
const runtimeOptions = providerRuntimeOptions(task, { session, apiOrigin, beforeSpawn: async localTask => {
|
|
1842
1921
|
if (purposeWorkspaceBusy(localTask.workspace_path)) throw Object.assign(new Error("Independent verification owns this workspace."), { code: "purpose_verification_workspace_busy" });
|
|
1843
1922
|
if (purposeVerification) {
|
|
1844
1923
|
await authorizeDesktopWork(requestAccessScope);
|
|
1845
1924
|
assertPurposeActor(purposeVerification, session, apiOrigin, localTask, idempotencyKey);
|
|
1846
1925
|
if (purposeStartScope !== desktopScopeRevision() || !isSessionValid(session, Date.now(), apiOrigin)) throw Object.assign(new Error("Purpose verification actor changed."), { code: "purpose_verification_scope_changed" });
|
|
1847
1926
|
}
|
|
1848
|
-
} }));
|
|
1849
|
-
await
|
|
1927
|
+
} }, requestAccessScope, inspectedLinks.get(inspection));
|
|
1928
|
+
await authorizeProviderDispatch(body.backend, requestAccessScope, inspectedLinks.get(inspection));
|
|
1850
1929
|
if (purposeVerification && (purposeStartScope !== desktopScopeRevision() || !isSessionValid(session, Date.now(), apiOrigin))) throw Object.assign(new Error("Purpose verification actor changed."), { code: "purpose_verification_scope_changed", status: 403 });
|
|
1851
|
-
task.operation_promise = Promise.resolve(runLocalTask(task))
|
|
1930
|
+
task.operation_promise = Promise.resolve(runLocalTask(task, runtimeOptions))
|
|
1852
1931
|
.catch(async (error) => {
|
|
1853
1932
|
task.status = "failed";
|
|
1854
1933
|
task.control_state = "complete";
|
|
@@ -1973,13 +2052,20 @@ export async function startAgent({
|
|
|
1973
2052
|
repositoryPath,
|
|
1974
2053
|
});
|
|
1975
2054
|
directRegistry.create(task);
|
|
2055
|
+
const link = inspectedLinks.get(inspection);
|
|
2056
|
+
await authorizeProviderDispatch(body.backend, requestAccessScope, link);
|
|
1976
2057
|
const runDirectTask = dependencies.runDirectTask || startDirectTask;
|
|
2058
|
+
const directOptions = { resolveCommand: async input => {
|
|
2059
|
+
const command = await (dependencies.resolveDesktopExecutorCommand || resolveDesktopExecutorCommand)(input);
|
|
2060
|
+
await authorizeProviderDispatch(body.backend, requestAccessScope, link);
|
|
2061
|
+
return command;
|
|
2062
|
+
} };
|
|
1977
2063
|
const releaseUpdateWork = acquireUpdateWork(updateManager);
|
|
1978
2064
|
if (!releaseUpdateWork) {
|
|
1979
2065
|
json(res, 503, updateInProgressResponse(updateManager));
|
|
1980
2066
|
return;
|
|
1981
2067
|
}
|
|
1982
|
-
void Promise.resolve(runDirectTask(task))
|
|
2068
|
+
void Promise.resolve(runDirectTask(task, directOptions))
|
|
1983
2069
|
.catch((error) => {
|
|
1984
2070
|
task.status = "failed";
|
|
1985
2071
|
task.error = error instanceof Error ? error.message : String(error);
|
|
@@ -2177,7 +2263,8 @@ export async function startAgent({
|
|
|
2177
2263
|
if (session !== launchSession || !isSessionValid(session, Date.now(), apiOrigin)) {
|
|
2178
2264
|
json(res, 409, changedLaunchSession(launchSession, session, apiOrigin)); return;
|
|
2179
2265
|
}
|
|
2180
|
-
|
|
2266
|
+
await bindRunAuthorization(run, requestAccessScope, inspection);
|
|
2267
|
+
void startLocalCodingRun(run, codingRuntimeOptions(run));
|
|
2181
2268
|
json(res, 202, { ok: true, run_id: run.id });
|
|
2182
2269
|
return;
|
|
2183
2270
|
}
|
|
@@ -2321,7 +2408,7 @@ export async function startAgent({
|
|
|
2321
2408
|
const continuation = continueLocalCodingRun(
|
|
2322
2409
|
run,
|
|
2323
2410
|
String(body.message || "Continue after increasing the Provider token budget."),
|
|
2324
|
-
|
|
2411
|
+
codingRuntimeOptions(run),
|
|
2325
2412
|
);
|
|
2326
2413
|
json(res, 202, {
|
|
2327
2414
|
ok: true,
|
|
@@ -2434,7 +2521,7 @@ export async function startAgent({
|
|
|
2434
2521
|
const continuation = continueLocalCodingRunAfterRepositoryApproval(
|
|
2435
2522
|
run,
|
|
2436
2523
|
{ repository: selected.repository, localPath: selected.local_path },
|
|
2437
|
-
|
|
2524
|
+
codingRuntimeOptions(run),
|
|
2438
2525
|
);
|
|
2439
2526
|
json(res, 202, {
|
|
2440
2527
|
ok: true,
|
|
@@ -2466,7 +2553,7 @@ export async function startAgent({
|
|
|
2466
2553
|
json(res, 409, { error: "run is not ready for PR evidence recheck" });
|
|
2467
2554
|
return;
|
|
2468
2555
|
}
|
|
2469
|
-
const snapshot = await
|
|
2556
|
+
const snapshot = await recheckAuthorizedCodingRun(run);
|
|
2470
2557
|
json(res, 200, { ok: true, run_id: run.id, state: snapshot.state });
|
|
2471
2558
|
return;
|
|
2472
2559
|
}
|
|
@@ -2479,7 +2566,7 @@ export async function startAgent({
|
|
|
2479
2566
|
|
|
2480
2567
|
json(res, 404, { error: "not found" });
|
|
2481
2568
|
} catch (err) {
|
|
2482
|
-
if (err?.code === "provider_login_required" || err?.code === "provider_status_unknown" ||
|
|
2569
|
+
if (err?.code === "provider_link_disconnected" || err?.code === "provider_login_required" || err?.code === "provider_status_unknown" ||
|
|
2483
2570
|
(typeof err?.code === "string" && err.code.startsWith("desktop_executor_"))) {
|
|
2484
2571
|
json(res, 409, { error: err.message, code: err.code, recovery: err.recovery });
|
|
2485
2572
|
return;
|
|
@@ -2720,6 +2807,12 @@ export async function startAgent({
|
|
|
2720
2807
|
!Number.isFinite(ticket.expiresAt) || ticket.expiresAt <= Date.now() || ticket.expiresAt > Date.now() + 60_000) throw new Error("Desktop sign-in changed");
|
|
2721
2808
|
return { code: ticket.code, expiresAt: ticket.expiresAt, userId: ticket.userId, orgSlug: ticket.orgSlug };
|
|
2722
2809
|
},
|
|
2810
|
+
// This is an in-process capability for the native host. It is deliberately
|
|
2811
|
+
// absent from status frames and never written to disk by the host.
|
|
2812
|
+
getLocalCliApiSession() {
|
|
2813
|
+
if (!isSessionValid(session, Date.now(), apiOrigin) || pendingOrganization) return null;
|
|
2814
|
+
return { token: session.token, api_base_url: session.api_base_url };
|
|
2815
|
+
},
|
|
2723
2816
|
getDesktopStatus,
|
|
2724
2817
|
stopDesktopTask,
|
|
2725
2818
|
stopDesktopTasks,
|
|
@@ -2772,7 +2865,8 @@ export async function startAgent({
|
|
|
2772
2865
|
const executionTarget = normalizeDesktopExecutionTarget(input.executionTarget);
|
|
2773
2866
|
if (!requirement) throw new Error("Describe the coding task first.");
|
|
2774
2867
|
if (!repository) throw new Error("Set a repository with /repo owner/name.");
|
|
2775
|
-
|
|
2868
|
+
const inspection = await inspectReadyBackend(backend);
|
|
2869
|
+
if (!inspection.compatible) {
|
|
2776
2870
|
throw new Error(`${backend === "claude" ? "Claude Code" : "Codex"} is not available on PATH.`);
|
|
2777
2871
|
}
|
|
2778
2872
|
const indexedRepository = await ensureRepositoryIndexed(repository);
|
|
@@ -2812,18 +2906,19 @@ export async function startAgent({
|
|
|
2812
2906
|
await saveCodingRunSnapshot(codingRunStateRoot, run.snapshot);
|
|
2813
2907
|
registry.create(run);
|
|
2814
2908
|
await authorizeDesktopWork(scope);
|
|
2815
|
-
|
|
2909
|
+
await bindRunAuthorization(run, scope, inspection);
|
|
2910
|
+
void startLocalCodingRun(run, codingRuntimeOptions(run));
|
|
2816
2911
|
return run;
|
|
2817
2912
|
}),
|
|
2818
2913
|
continue: (run, guidance) =>
|
|
2819
2914
|
withUpdateWork(updateManager, async () => {
|
|
2820
2915
|
await requireRunProvider(run);
|
|
2821
|
-
return continueLocalCodingRun(run, guidance,
|
|
2916
|
+
return continueLocalCodingRun(run, guidance, codingRuntimeOptions(run));
|
|
2822
2917
|
}),
|
|
2823
2918
|
applyDecision: (run, decision) =>
|
|
2824
2919
|
withUpdateWork(updateManager, async () => {
|
|
2825
2920
|
await requireRunProvider(run);
|
|
2826
|
-
return applyLocalHumanDecision(run, decision,
|
|
2921
|
+
return applyLocalHumanDecision(run, decision, codingRuntimeOptions(run));
|
|
2827
2922
|
}),
|
|
2828
2923
|
queueGuidance: (run, guidance) => {
|
|
2829
2924
|
if (enforceDesktopAccess) requireDesktopAccess(desktopAccess.snapshot());
|
|
@@ -2838,7 +2933,7 @@ export async function startAgent({
|
|
|
2838
2933
|
recheck: (run) =>
|
|
2839
2934
|
withUpdateWork(updateManager, async () => {
|
|
2840
2935
|
await authorizeDesktopWork();
|
|
2841
|
-
return
|
|
2936
|
+
return recheckAuthorizedCodingRun(run, { collectEvidence: runOptions.collectEvidence });
|
|
2842
2937
|
}),
|
|
2843
2938
|
},
|
|
2844
2939
|
};
|
package/src/auto-update.js
CHANGED
|
@@ -72,6 +72,7 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
72
72
|
#confirmedUpdate = null;
|
|
73
73
|
#confirmedReady = false;
|
|
74
74
|
#desktopIdle = () => true;
|
|
75
|
+
#applyHolds = 0;
|
|
75
76
|
|
|
76
77
|
constructor(options) {
|
|
77
78
|
super();
|
|
@@ -136,7 +137,9 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
136
137
|
async performAction() {
|
|
137
138
|
const action = this.#snapshot.action;
|
|
138
139
|
if (action?.id === "download_macos_installer" && action.url) {
|
|
139
|
-
|
|
140
|
+
const result = await this.#options.openUrl(action.url);
|
|
141
|
+
if (result?.opened === false) throw new Error("Could not open the update installer download.");
|
|
142
|
+
return result;
|
|
140
143
|
}
|
|
141
144
|
if (action?.command) {
|
|
142
145
|
throw new Error(`Run this command after leaving SIMY: ${action.command}`);
|
|
@@ -144,8 +147,17 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
144
147
|
throw new Error("No SIMY CLI update action is currently available.");
|
|
145
148
|
}
|
|
146
149
|
|
|
150
|
+
// Hold installation across a multi-worker cancellation, even when the last
|
|
151
|
+
// registry event or work lease is released before every worker has drained.
|
|
152
|
+
holdApply() {
|
|
153
|
+
if (["updating", "restarting"].includes(this.#snapshot.state)) throw new Error("SIMY is already updating.");
|
|
154
|
+
this.#applyHolds++;
|
|
155
|
+
let released = false;
|
|
156
|
+
return () => { if (!released) { released = true; this.#applyHolds--; } };
|
|
157
|
+
}
|
|
158
|
+
|
|
147
159
|
acceptsNewWork() {
|
|
148
|
-
return !this.#confirmedUpdate && !["stopping_tasks", "updating", "restarting"].includes(this.#snapshot.state);
|
|
160
|
+
return this.#applyHolds === 0 && !this.#confirmedUpdate && !["stopping_tasks", "updating", "restarting"].includes(this.#snapshot.state);
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
beginWork() {
|
|
@@ -370,7 +382,7 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
370
382
|
}
|
|
371
383
|
|
|
372
384
|
#isIdle() {
|
|
373
|
-
return this.#activeWork === 0 && isRegistryIdle(this.#registry) && this.#desktopIdle();
|
|
385
|
+
return this.#applyHolds === 0 && this.#activeWork === 0 && isRegistryIdle(this.#registry) && this.#desktopIdle();
|
|
374
386
|
}
|
|
375
387
|
|
|
376
388
|
#clearIdleTimer() {
|
package/src/cli-contract.js
CHANGED
|
@@ -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.6.
|
|
16
|
+
export const CLI_VERSION = "0.6.18";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { linkedProvider } from "./delivery-loop.mjs";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
5
6
|
import { homedir } from "node:os";
|
|
@@ -13,12 +14,14 @@ const MAX_DISCOVERY_RECORDS = 16;
|
|
|
13
14
|
|
|
14
15
|
export async function runConnectionReminder({
|
|
15
16
|
env = process.env,
|
|
17
|
+
provider = env.CLAUDE_PLUGIN_ROOT ? "claude" : "codex",
|
|
16
18
|
input = {},
|
|
17
19
|
fetchImpl = globalThis.fetch,
|
|
18
20
|
now = () => Date.now(),
|
|
19
21
|
} = {}) {
|
|
20
22
|
const explicitWebOrigin = resolveExplicitWebOrigin(env);
|
|
21
23
|
const sessionRoot = env.SIMY_HOME?.trim() || join(homedir(), ".simy");
|
|
24
|
+
if (!await linkedProvider(join(sessionRoot, "delivery-hooks"), provider)) return { continue: true };
|
|
22
25
|
const pluginData = env.PLUGIN_DATA?.trim() || env.CLAUDE_PLUGIN_DATA?.trim() || join(sessionRoot, "plugin-data", "simy");
|
|
23
26
|
const sessionId = String(input.session_id || input.sessionId || env.CODEX_THREAD_ID || "unknown");
|
|
24
27
|
const statePath = reminderStatePath(pluginData, explicitWebOrigin || "any-origin", sessionId);
|
|
@@ -172,9 +175,9 @@ async function readHookInput(stream = process.stdin) {
|
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
export async function main() {
|
|
178
|
+
export async function main(provider) {
|
|
176
179
|
try {
|
|
177
|
-
const result = await runConnectionReminder({ input: await readHookInput() });
|
|
180
|
+
const result = await runConnectionReminder({ provider, input: await readHookInput() });
|
|
178
181
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
179
182
|
} catch {
|
|
180
183
|
process.stdout.write(`${JSON.stringify({ continue: true })}\n`);
|