@awak-app/simy-cli 0.5.21 → 0.5.24
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 +17 -3
- package/package.json +5 -3
- package/src/agent.js +184 -75
- package/src/backend-executable.js +19 -5
- package/src/cli-contract.js +2 -1
- package/src/codex-plugins/simy/hooks/connection-reminder.mjs +1 -1
- package/src/console/app.js +1 -1
- package/src/desktop-host.js +25 -13
- package/src/desktop-status.js +47 -21
- package/src/direct-executor.js +3 -0
- package/src/durable-local-task-steps-contract.js +6 -1
- package/src/durable-local-task-worker.js +5 -1
- package/src/execution-capability-contract.js +19 -22
- package/src/local-task.js +20 -1
- package/src/orchestrator/recovery.js +9 -0
- package/src/provider-stream.js +20 -2
- package/src/runner.js +4 -2
- package/src/sqm/agent-skill.js +8 -2
- package/src/sqm/claude-plugin-update.js +41 -0
- package/src/sqm/claude-plugin.js +122 -0
- package/src/sqm/provider-command.js +38 -0
- package/src/sqm/review-worker.js +87 -31
- package/src/sqm/workflow-onboarding.js +6 -2
- package/src/sqm/workflow-update.js +27 -8
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ simy --daemon
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
When an interactive `simy --daemon` starts without managed SIMY workflows,
|
|
35
|
-
the CLI asks once before installing the Codex plugin
|
|
36
|
-
|
|
35
|
+
the CLI asks once before installing the Codex and Claude Code plugin
|
|
36
|
+
sources and Codex/Claude opt-in SQM skills. Existing unrelated plugins
|
|
37
37
|
and hooks are preserved. Use `--install-workflows` for explicit non-interactive
|
|
38
38
|
installation or `--no-workflow-prompt` to start the daemon without asking.
|
|
39
39
|
|
|
@@ -49,10 +49,24 @@ period in each Codex session, then rearms after the CLI reconnects. The hook doe
|
|
|
49
49
|
not start or install the CLI, read session tokens, enable SQM, or send email.
|
|
50
50
|
Review and enable the plugin hooks through Codex's `/hooks` command.
|
|
51
51
|
|
|
52
|
+
For Claude Code, register the generated local marketplace and explicitly enable SIMY:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
claude plugin marketplace add ~/.simy/integrations/claude-marketplace
|
|
56
|
+
claude plugin install simy@simy-local
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use `/simy:sqm-loop` or `/simy:pr-review` in Claude Code. The workflows are generated
|
|
60
|
+
from the same maintained source as Codex; only provider terminology and hook/manifest
|
|
61
|
+
format differ. Installing source files does not imply that either provider has enabled
|
|
62
|
+
the plugin. Existing Claude settings, unrelated marketplaces, and plugins are preserved.
|
|
63
|
+
|
|
52
64
|
After that first installation, the daemon checks installed workflows at startup
|
|
53
65
|
and every hour, including after a CLI automatic upgrade. It updates only existing
|
|
54
66
|
platform installations and refreshes an already enabled SIMY Codex plugin through
|
|
55
|
-
`codex plugin add`.
|
|
67
|
+
`codex plugin add`. Enabled user-scope Claude plugins are refreshed with
|
|
68
|
+
`claude plugin update simy@simy-local --scope user`, after verifying that the
|
|
69
|
+
marketplace still points to SIMY’s generated local source. Plugin versions include a content digest so changed bundles
|
|
56
70
|
receive a fresh cache. Use a new Codex task to pick up updated skills; running
|
|
57
71
|
tasks are not restarted. Missing or disabled plugins are not enabled automatically.
|
|
58
72
|
Failed refreshes retry on the next check without blocking daemon startup.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awak-app/simy-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.24",
|
|
4
4
|
"description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,13 +39,15 @@
|
|
|
39
39
|
"check": "npm run check:auto-update && node --check ./src/desktop-executor.js && node --check ./src/local-task.js && node --check ./scripts/desktop-executor-e2e.js && node --check ./scripts/pre-guardrail-e2e.js && node --check ./scripts/fixtures/fake-desktop-provider.js && npm run check:syntax && npm run check:real-provider-syntax && npm test && npm run check:package",
|
|
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
|
-
"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",
|
|
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 && node --check ./scripts/claude-parity-runtime-e2e.js && node --check ./scripts/claude-plugin-runtime-e2e.js",
|
|
43
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",
|
|
47
47
|
"test:macos-installer": "node --test packaging/test-macos-packaging.mjs",
|
|
48
|
-
"build:macos-installer:production": "bash packaging/build-macos-arm64.sh --production"
|
|
48
|
+
"build:macos-installer:production": "bash packaging/build-macos-arm64.sh --production",
|
|
49
|
+
"test:e2e:claude-parity": "node ./scripts/claude-parity-runtime-e2e.js",
|
|
50
|
+
"test:e2e:claude-plugin": "node ./scripts/claude-plugin-runtime-e2e.js"
|
|
49
51
|
},
|
|
50
52
|
"engines": {
|
|
51
53
|
"node": ">=20"
|
package/src/agent.js
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
} from "./desktop-executor.js";
|
|
51
51
|
import {
|
|
52
52
|
createCodexStatusInspector,
|
|
53
|
+
createProviderStatusInspector,
|
|
53
54
|
createConnectionStatusTracker,
|
|
54
55
|
desktopAuthStatus,
|
|
55
56
|
desktopTaskStatus,
|
|
@@ -187,46 +188,11 @@ export async function startAgent({
|
|
|
187
188
|
: [],
|
|
188
189
|
);
|
|
189
190
|
const installMode = updateManager?.snapshot?.()?.install_mode ?? null;
|
|
190
|
-
|
|
191
|
-
const durableControlPlane =
|
|
192
|
-
inspectedCapabilities?.features?.local_task_control_plane === true;
|
|
193
|
-
const hasSqmLocalReview = Object.hasOwn(
|
|
194
|
-
inspectedCapabilities?.features || {},
|
|
195
|
-
"sqm_local_review",
|
|
196
|
-
);
|
|
197
|
-
const contractSourceCapabilities =
|
|
198
|
-
durableControlPlane || hasSqmLocalReview
|
|
199
|
-
? {
|
|
200
|
-
...inspectedCapabilities,
|
|
201
|
-
features: {
|
|
202
|
-
...inspectedCapabilities?.features,
|
|
203
|
-
...(durableControlPlane
|
|
204
|
-
? {
|
|
205
|
-
local_task_control_plane: daemon,
|
|
206
|
-
pipeline_local_task_artifacts:
|
|
207
|
-
daemon &&
|
|
208
|
-
inspectedCapabilities.features.pipeline_local_task_artifacts === true,
|
|
209
|
-
durable_local_task_attachments: daemon,
|
|
210
|
-
durable_local_task_steps:
|
|
211
|
-
daemon &&
|
|
212
|
-
inspectedCapabilities.features.durable_local_task_steps === true,
|
|
213
|
-
bounded_local_task_subtasks:
|
|
214
|
-
daemon &&
|
|
215
|
-
inspectedCapabilities.features.bounded_local_task_subtasks === true,
|
|
216
|
-
}
|
|
217
|
-
: {}),
|
|
218
|
-
...(hasSqmLocalReview
|
|
219
|
-
? {
|
|
220
|
-
sqm_local_review:
|
|
221
|
-
daemon && inspectedCapabilities?.features?.sqm_local_review === true,
|
|
222
|
-
}
|
|
223
|
-
: {}),
|
|
224
|
-
},
|
|
225
|
-
}
|
|
226
|
-
: inspectedCapabilities;
|
|
191
|
+
let contractSourceCapabilities = daemonCapabilities(await readCapabilities(dependencies), daemon);
|
|
227
192
|
let availableCapabilities;
|
|
193
|
+
let providerStatuses = { codex: { state: "unknown" }, claude: { state: "unknown" } };
|
|
228
194
|
const refreshAvailableCapabilities = () => {
|
|
229
|
-
availableCapabilities = withCliContract(contractSourceCapabilities, installMode, {
|
|
195
|
+
availableCapabilities = withCliContract({ ...contractSourceCapabilities, ...(providerStatuses ? { provider_statuses: providerStatuses } : {}) }, installMode, {
|
|
230
196
|
authenticated:
|
|
231
197
|
isSessionValid(session, Date.now(), apiOrigin) && Boolean(session?.device_id),
|
|
232
198
|
});
|
|
@@ -255,6 +221,46 @@ export async function startAgent({
|
|
|
255
221
|
now: dependencies.statusNowMs,
|
|
256
222
|
});
|
|
257
223
|
|
|
224
|
+
const claudeStatus = createProviderStatusInspector("claude", {
|
|
225
|
+
inspect: dependencies.inspectClaudeStatus,
|
|
226
|
+
now: dependencies.statusNowMs,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const readProviderStatuses = async ({ force = false } = {}) => {
|
|
230
|
+
const [codex, claude] = await Promise.all([codexStatus({ force }), claudeStatus({ force })]);
|
|
231
|
+
const binaryStateChanged = ["codex", "claude"].some((provider) => {
|
|
232
|
+
const next = provider === "codex" ? codex : claude;
|
|
233
|
+
return providerStatuses[provider]?.version !== next.version ||
|
|
234
|
+
providerStatuses[provider]?.state !== next.state;
|
|
235
|
+
});
|
|
236
|
+
if (force || binaryStateChanged) {
|
|
237
|
+
contractSourceCapabilities = daemonCapabilities(await readCapabilities(dependencies), daemon);
|
|
238
|
+
}
|
|
239
|
+
providerStatuses = { codex, claude };
|
|
240
|
+
refreshAvailableCapabilities();
|
|
241
|
+
return providerStatuses;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const inspectReadyBackend = async (backend, options) => {
|
|
245
|
+
await readProviderStatuses(options);
|
|
246
|
+
const inspection = normalizeBackendInspection(backend, availableCapabilities);
|
|
247
|
+
if (inspection.compatible) {
|
|
248
|
+
const failure = providerAuthenticationFailure(backend, providerStatuses[backend]);
|
|
249
|
+
if (failure) throw Object.assign(new Error(failure.summary), failure);
|
|
250
|
+
}
|
|
251
|
+
return inspection;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const requireRunProvider = async (run) => {
|
|
255
|
+
const backend = run.request.backend;
|
|
256
|
+
const inspection = await inspectReadyBackend(backend, { force: true });
|
|
257
|
+
if (!inspection.compatible) {
|
|
258
|
+
throw Object.assign(new Error(backendPreflightSummary(backend, inspection)), {
|
|
259
|
+
code: `desktop_executor_${inspection.status}`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
258
264
|
const requestDesktopLogin = () => {
|
|
259
265
|
if (isSessionValid(session, Date.now(), apiOrigin)) return null;
|
|
260
266
|
if (!loginUrl) {
|
|
@@ -300,6 +306,7 @@ export async function startAgent({
|
|
|
300
306
|
if (heartbeatInFlight) return heartbeatInFlight;
|
|
301
307
|
connectionStatus.attempt();
|
|
302
308
|
let deviceHeartbeatSucceeded = false;
|
|
309
|
+
const advertisedProviders = JSON.stringify(providerStatuses);
|
|
303
310
|
heartbeatInFlight = Promise.resolve()
|
|
304
311
|
.then(() =>
|
|
305
312
|
heartbeatDevice({
|
|
@@ -314,6 +321,13 @@ export async function startAgent({
|
|
|
314
321
|
.then(async () => {
|
|
315
322
|
deviceHeartbeatSucceeded = true;
|
|
316
323
|
connectionStatus.success();
|
|
324
|
+
// Register the device immediately; provider subprocesses must not delay
|
|
325
|
+
// browser authorization. Publish verified readiness after their probe.
|
|
326
|
+
await readProviderStatuses();
|
|
327
|
+
if (JSON.stringify(providerStatuses) !== advertisedProviders) {
|
|
328
|
+
await heartbeatDevice({ apiOrigin, apiBaseUrl: session.api_base_url, token: session.token,
|
|
329
|
+
port, capabilities: refreshAvailableCapabilities(), repoInventory: repositoryInventory });
|
|
330
|
+
}
|
|
317
331
|
const sqmResult = sqmLocalReviewWorker
|
|
318
332
|
? await sqmLocalReviewWorker.poll()
|
|
319
333
|
: null;
|
|
@@ -429,7 +443,15 @@ export async function startAgent({
|
|
|
429
443
|
storageRoot: taskStorageRoot,
|
|
430
444
|
workspaceRoot: dependencies.localTaskWorkspaceRoot,
|
|
431
445
|
resolveRepository: ensureRepositoryIndexed,
|
|
432
|
-
runLocalTask:
|
|
446
|
+
runLocalTask: async (task, options) => {
|
|
447
|
+
const inspection = await inspectReadyBackend(task.backend);
|
|
448
|
+
if (!inspection.compatible) {
|
|
449
|
+
throw Object.assign(new Error(backendPreflightSummary(task.backend, inspection)), {
|
|
450
|
+
code: `desktop_executor_${inspection.status}`,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return (dependencies.runLocalTask || startLocalTask)(task, options);
|
|
454
|
+
},
|
|
433
455
|
attachmentStore: localTaskAttachmentStore,
|
|
434
456
|
getDeviceId: () => session?.device_id || null,
|
|
435
457
|
beginWork: () => acquireUpdateWork(updateManager),
|
|
@@ -437,7 +459,7 @@ export async function startAgent({
|
|
|
437
459
|
});
|
|
438
460
|
}
|
|
439
461
|
|
|
440
|
-
if (daemon && availableCapabilities?.features
|
|
462
|
+
if (daemon && Object.hasOwn(availableCapabilities?.features || {}, "sqm_local_review")) {
|
|
441
463
|
const sqmApi =
|
|
442
464
|
dependencies.sqmLocalReviewApi ||
|
|
443
465
|
createSqmLocalReviewApi({
|
|
@@ -454,6 +476,10 @@ export async function startAgent({
|
|
|
454
476
|
api: sqmApi,
|
|
455
477
|
beginWork: () => acquireUpdateWork(updateManager),
|
|
456
478
|
runReview: dependencies.runSqmLocalReview,
|
|
479
|
+
getProvider: () => ["codex", "claude"].find(
|
|
480
|
+
(provider) => availableCapabilities?.backends?.[provider] === true &&
|
|
481
|
+
providerStatuses[provider]?.state === "ready",
|
|
482
|
+
) || null,
|
|
457
483
|
onError: (error) => reportSqmLocalReviewError(error, quiet),
|
|
458
484
|
});
|
|
459
485
|
}
|
|
@@ -472,6 +498,7 @@ export async function startAgent({
|
|
|
472
498
|
throw new Error(`This run requires ${run.request.repository}; select that local repository.`);
|
|
473
499
|
}
|
|
474
500
|
if (!run) return selected;
|
|
501
|
+
await requireRunProvider(run);
|
|
475
502
|
return withUpdateWork(
|
|
476
503
|
updateManager,
|
|
477
504
|
() =>
|
|
@@ -659,6 +686,7 @@ export async function startAgent({
|
|
|
659
686
|
? body.local_path
|
|
660
687
|
: indexedRepository?.local_path || null,
|
|
661
688
|
dependencies,
|
|
689
|
+
providerStatus: (await readProviderStatuses())[backend],
|
|
662
690
|
});
|
|
663
691
|
json(res, 200, environment);
|
|
664
692
|
return;
|
|
@@ -904,6 +932,7 @@ export async function startAgent({
|
|
|
904
932
|
requestFingerprint = createLocalTaskRequestFingerprint({
|
|
905
933
|
instruction,
|
|
906
934
|
backend: body.backend,
|
|
935
|
+
model: body.model,
|
|
907
936
|
operation: guardrail.operation,
|
|
908
937
|
permissionMode: guardrail.permission_mode,
|
|
909
938
|
workspaceMode,
|
|
@@ -965,7 +994,7 @@ export async function startAgent({
|
|
|
965
994
|
json(res, 503, updateInProgressResponse(updateManager));
|
|
966
995
|
return;
|
|
967
996
|
}
|
|
968
|
-
const inspection =
|
|
997
|
+
const inspection = await inspectReadyBackend(body.backend);
|
|
969
998
|
if (!inspection.compatible) {
|
|
970
999
|
const summary = backendPreflightSummary(body.backend, inspection);
|
|
971
1000
|
json(res, 409, {
|
|
@@ -1020,6 +1049,7 @@ export async function startAgent({
|
|
|
1020
1049
|
id: workspace.id,
|
|
1021
1050
|
instruction,
|
|
1022
1051
|
backend: body.backend,
|
|
1052
|
+
model: body.model,
|
|
1023
1053
|
operation: guardrail.operation,
|
|
1024
1054
|
permissionMode: guardrail.permission_mode,
|
|
1025
1055
|
workspaceMode,
|
|
@@ -1127,7 +1157,7 @@ export async function startAgent({
|
|
|
1127
1157
|
});
|
|
1128
1158
|
return;
|
|
1129
1159
|
}
|
|
1130
|
-
const inspection =
|
|
1160
|
+
const inspection = await inspectReadyBackend(body.backend);
|
|
1131
1161
|
if (!inspection.compatible) {
|
|
1132
1162
|
const summary = backendPreflightSummary(body.backend, inspection);
|
|
1133
1163
|
json(res, 409, {
|
|
@@ -1242,22 +1272,20 @@ export async function startAgent({
|
|
|
1242
1272
|
json(res, 400, { error: "backend must be codex or claude" });
|
|
1243
1273
|
return;
|
|
1244
1274
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1275
|
+
const inspection = await inspectReadyBackend(body.backend);
|
|
1276
|
+
if (!inspection.compatible) {
|
|
1277
|
+
const summary = backendPreflightSummary(body.backend, inspection);
|
|
1278
|
+
json(res, 409, {
|
|
1279
|
+
error: summary,
|
|
1280
|
+
code: `desktop_executor_${inspection.status}`,
|
|
1281
|
+
execution_target: executionTarget,
|
|
1282
|
+
recovery: recoveryContractForPreflight({
|
|
1283
|
+
key: "backend",
|
|
1284
|
+
summary,
|
|
1285
|
+
details: inspection,
|
|
1286
|
+
}),
|
|
1287
|
+
});
|
|
1288
|
+
return;
|
|
1261
1289
|
}
|
|
1262
1290
|
let stagedAttachments = [];
|
|
1263
1291
|
try {
|
|
@@ -1359,6 +1387,7 @@ export async function startAgent({
|
|
|
1359
1387
|
json(res, 503, updateInProgressResponse(updateManager));
|
|
1360
1388
|
return;
|
|
1361
1389
|
}
|
|
1390
|
+
await requireRunProvider(run);
|
|
1362
1391
|
resumeLocalCodingRun(run);
|
|
1363
1392
|
} else {
|
|
1364
1393
|
json(res, 400, { error: "action must be pause, resume, or stop" });
|
|
@@ -1393,6 +1422,7 @@ export async function startAgent({
|
|
|
1393
1422
|
return;
|
|
1394
1423
|
}
|
|
1395
1424
|
const body = await readJson(req);
|
|
1425
|
+
await requireRunProvider(run);
|
|
1396
1426
|
let budget;
|
|
1397
1427
|
try {
|
|
1398
1428
|
budget = await increaseLocalCodingRunProviderTokenBudget(
|
|
@@ -1518,6 +1548,7 @@ export async function startAgent({
|
|
|
1518
1548
|
return;
|
|
1519
1549
|
}
|
|
1520
1550
|
|
|
1551
|
+
await requireRunProvider(run);
|
|
1521
1552
|
const continuation = continueLocalCodingRunAfterRepositoryApproval(
|
|
1522
1553
|
run,
|
|
1523
1554
|
{ repository: selected.repository, localPath: selected.local_path },
|
|
@@ -1566,6 +1597,11 @@ export async function startAgent({
|
|
|
1566
1597
|
|
|
1567
1598
|
json(res, 404, { error: "not found" });
|
|
1568
1599
|
} catch (err) {
|
|
1600
|
+
if (err?.code === "provider_login_required" || err?.code === "provider_status_unknown" ||
|
|
1601
|
+
(typeof err?.code === "string" && err.code.startsWith("desktop_executor_"))) {
|
|
1602
|
+
json(res, 409, { error: err.message, code: err.code, recovery: err.recovery });
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1569
1605
|
if (err instanceof LocalTaskFileCapabilityError) {
|
|
1570
1606
|
json(res, 400, localTaskFileErrorPayload(err));
|
|
1571
1607
|
return;
|
|
@@ -1634,6 +1670,7 @@ export async function startAgent({
|
|
|
1634
1670
|
});
|
|
1635
1671
|
const getDesktopStatus = async () => {
|
|
1636
1672
|
const auth = desktopAuthStatus(session, { webOrigin: apiOrigin });
|
|
1673
|
+
const { codex, claude } = await readProviderStatuses();
|
|
1637
1674
|
return {
|
|
1638
1675
|
schemaVersion: 1,
|
|
1639
1676
|
sampledAt: new Date().toISOString(),
|
|
@@ -1643,7 +1680,9 @@ export async function startAgent({
|
|
|
1643
1680
|
connection: connectionStatus.snapshot({ authenticated: auth.state === "signed_in" }),
|
|
1644
1681
|
tasks: desktopTaskStatus([registry, directRegistry, localTaskRegistry]),
|
|
1645
1682
|
update: updateManager?.snapshot?.() ?? null,
|
|
1646
|
-
codex
|
|
1683
|
+
codex,
|
|
1684
|
+
claude,
|
|
1685
|
+
providers: { codex, claude },
|
|
1647
1686
|
loginUrl: auth.state === "signed_in" ? null : requestDesktopLogin(),
|
|
1648
1687
|
};
|
|
1649
1688
|
};
|
|
@@ -1670,6 +1709,7 @@ export async function startAgent({
|
|
|
1670
1709
|
return getDesktopStatus();
|
|
1671
1710
|
},
|
|
1672
1711
|
refreshCodexStatus: () => codexStatus({ force: true }),
|
|
1712
|
+
refreshProviderStatus: () => readProviderStatuses({ force: true }),
|
|
1673
1713
|
workspace,
|
|
1674
1714
|
repositoryScanRoot,
|
|
1675
1715
|
get capabilities() {
|
|
@@ -1690,7 +1730,7 @@ export async function startAgent({
|
|
|
1690
1730
|
const executionTarget = normalizeDesktopExecutionTarget(input.executionTarget);
|
|
1691
1731
|
if (!requirement) throw new Error("Describe the coding task first.");
|
|
1692
1732
|
if (!repository) throw new Error("Set a repository with /repo owner/name.");
|
|
1693
|
-
if (
|
|
1733
|
+
if (!(await inspectReadyBackend(backend)).compatible) {
|
|
1694
1734
|
throw new Error(`${backend === "claude" ? "Claude Code" : "Codex"} is not available on PATH.`);
|
|
1695
1735
|
}
|
|
1696
1736
|
const indexedRepository = await ensureRepositoryIndexed(repository);
|
|
@@ -1730,12 +1770,21 @@ export async function startAgent({
|
|
|
1730
1770
|
return run;
|
|
1731
1771
|
}),
|
|
1732
1772
|
continue: (run, guidance) =>
|
|
1733
|
-
withUpdateWork(updateManager, () =>
|
|
1773
|
+
withUpdateWork(updateManager, async () => {
|
|
1774
|
+
await requireRunProvider(run);
|
|
1775
|
+
return continueLocalCodingRun(run, guidance, runOptions);
|
|
1776
|
+
}),
|
|
1734
1777
|
applyDecision: (run, decision) =>
|
|
1735
|
-
withUpdateWork(updateManager, () =>
|
|
1778
|
+
withUpdateWork(updateManager, async () => {
|
|
1779
|
+
await requireRunProvider(run);
|
|
1780
|
+
return applyLocalHumanDecision(run, decision, runOptions);
|
|
1781
|
+
}),
|
|
1736
1782
|
queueGuidance: queueLocalCodingGuidance,
|
|
1737
1783
|
pause: pauseLocalCodingRun,
|
|
1738
|
-
resume: (run) =>
|
|
1784
|
+
resume: (run) => withUpdateWork(updateManager, async () => {
|
|
1785
|
+
await requireRunProvider(run);
|
|
1786
|
+
return resumeLocalCodingRun(run);
|
|
1787
|
+
}),
|
|
1739
1788
|
stop: stopLocalCodingRun,
|
|
1740
1789
|
recheck: (run) =>
|
|
1741
1790
|
withUpdateWork(updateManager, () =>
|
|
@@ -1772,16 +1821,6 @@ async function withUpdateWork(updateManager, action) {
|
|
|
1772
1821
|
}
|
|
1773
1822
|
}
|
|
1774
1823
|
|
|
1775
|
-
function withUpdateWorkSync(updateManager, action) {
|
|
1776
|
-
const release = acquireUpdateWork(updateManager);
|
|
1777
|
-
if (!release) ensureAcceptingNewWork(updateManager);
|
|
1778
|
-
try {
|
|
1779
|
-
return action();
|
|
1780
|
-
} finally {
|
|
1781
|
-
release?.();
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1824
|
function updateInProgressResponse(updateManager) {
|
|
1786
1825
|
return {
|
|
1787
1826
|
error:
|
|
@@ -1983,15 +2022,17 @@ async function capabilities() {
|
|
|
1983
2022
|
local_task_artifacts: true,
|
|
1984
2023
|
local_task_streaming: true,
|
|
1985
2024
|
local_task_idempotency: true,
|
|
2025
|
+
local_task_model: true,
|
|
1986
2026
|
local_task_reconciliation: true,
|
|
1987
2027
|
local_task_control_plane: true,
|
|
1988
2028
|
pipeline_local_task_artifacts: true,
|
|
1989
2029
|
durable_local_task_attachments: true,
|
|
1990
2030
|
durable_local_task_scenario_packs: true,
|
|
1991
2031
|
durable_local_task_steps: true,
|
|
2032
|
+
durable_local_task_step_model: true,
|
|
1992
2033
|
bounded_local_task_subtasks: true,
|
|
1993
2034
|
sqm_agent_skill_management: true,
|
|
1994
|
-
sqm_local_review: codex.compatible,
|
|
2035
|
+
sqm_local_review: codex.compatible || claude.compatible,
|
|
1995
2036
|
},
|
|
1996
2037
|
session_ttl_hours: 48,
|
|
1997
2038
|
};
|
|
@@ -2007,6 +2048,7 @@ async function inspectCodingLoopEnvironment({
|
|
|
2007
2048
|
executionTarget,
|
|
2008
2049
|
localPath,
|
|
2009
2050
|
dependencies,
|
|
2051
|
+
providerStatus,
|
|
2010
2052
|
}) {
|
|
2011
2053
|
const checks = [
|
|
2012
2054
|
{
|
|
@@ -2063,8 +2105,17 @@ async function inspectCodingLoopEnvironment({
|
|
|
2063
2105
|
}),
|
|
2064
2106
|
});
|
|
2065
2107
|
|
|
2108
|
+
const authFailure = inspection.compatible ? providerAuthenticationFailure(backend, providerStatus) : null;
|
|
2109
|
+
if (inspection.compatible) {
|
|
2110
|
+
checks.push({
|
|
2111
|
+
key: "provider_auth",
|
|
2112
|
+
status: authFailure ? "failed" : "passed",
|
|
2113
|
+
summary: authFailure?.summary || `${backend === "claude" ? "Claude Code" : "Codex"} is signed in.`,
|
|
2114
|
+
...(authFailure ? { recovery: authFailure.recovery } : {}),
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2066
2117
|
return {
|
|
2067
|
-
ready: Boolean(repositoryPath && inspection.compatible),
|
|
2118
|
+
ready: Boolean(repositoryPath && inspection.compatible && !authFailure),
|
|
2068
2119
|
repository,
|
|
2069
2120
|
backend,
|
|
2070
2121
|
execution_target: executionTarget,
|
|
@@ -2332,3 +2383,61 @@ function multipartTooLargeError() {
|
|
|
2332
2383
|
},
|
|
2333
2384
|
);
|
|
2334
2385
|
}
|
|
2386
|
+
|
|
2387
|
+
function daemonCapabilities(inspectedCapabilities, daemon) {
|
|
2388
|
+
const durableControlPlane =
|
|
2389
|
+
inspectedCapabilities?.features?.local_task_control_plane === true;
|
|
2390
|
+
const hasSqmLocalReview = Object.hasOwn(
|
|
2391
|
+
inspectedCapabilities?.features || {},
|
|
2392
|
+
"sqm_local_review",
|
|
2393
|
+
);
|
|
2394
|
+
return durableControlPlane || hasSqmLocalReview
|
|
2395
|
+
? {
|
|
2396
|
+
...inspectedCapabilities,
|
|
2397
|
+
features: {
|
|
2398
|
+
...inspectedCapabilities?.features,
|
|
2399
|
+
...(durableControlPlane
|
|
2400
|
+
? {
|
|
2401
|
+
local_task_control_plane: daemon,
|
|
2402
|
+
pipeline_local_task_artifacts:
|
|
2403
|
+
daemon &&
|
|
2404
|
+
inspectedCapabilities.features.pipeline_local_task_artifacts === true,
|
|
2405
|
+
durable_local_task_attachments: daemon,
|
|
2406
|
+
durable_local_task_steps:
|
|
2407
|
+
daemon &&
|
|
2408
|
+
inspectedCapabilities.features.durable_local_task_steps === true,
|
|
2409
|
+
durable_local_task_step_model:
|
|
2410
|
+
daemon &&
|
|
2411
|
+
inspectedCapabilities.features.durable_local_task_step_model === true,
|
|
2412
|
+
bounded_local_task_subtasks:
|
|
2413
|
+
daemon &&
|
|
2414
|
+
inspectedCapabilities.features.bounded_local_task_subtasks === true,
|
|
2415
|
+
}
|
|
2416
|
+
: {}),
|
|
2417
|
+
...(hasSqmLocalReview
|
|
2418
|
+
? {
|
|
2419
|
+
sqm_local_review:
|
|
2420
|
+
daemon && inspectedCapabilities?.features?.sqm_local_review === true,
|
|
2421
|
+
}
|
|
2422
|
+
: {}),
|
|
2423
|
+
},
|
|
2424
|
+
}
|
|
2425
|
+
: inspectedCapabilities;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function providerAuthenticationFailure(provider, status) {
|
|
2429
|
+
if (status?.state === "ready") return null;
|
|
2430
|
+
const label = provider === "claude" ? "Claude Code" : "Codex";
|
|
2431
|
+
const loginRequired = status?.state === "signed_out";
|
|
2432
|
+
const code = loginRequired ? "provider_login_required" : "provider_status_unknown";
|
|
2433
|
+
const summary = loginRequired
|
|
2434
|
+
? `Sign in to ${label} before starting this task.`
|
|
2435
|
+
: `SIMY could not confirm ${label} authentication. Refresh the connection status before starting this task.`;
|
|
2436
|
+
return {
|
|
2437
|
+
code, summary,
|
|
2438
|
+
recovery: recoveryContractForPreflight({
|
|
2439
|
+
key: "provider_auth", summary,
|
|
2440
|
+
details: { provider, status: status?.state || "unknown" },
|
|
2441
|
+
}),
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import spawn from "cross-spawn";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
|
|
3
5
|
const MACOS_CODEX_BINARY = "/Applications/ChatGPT.app/Contents/Resources/codex";
|
|
4
6
|
|
|
@@ -22,7 +24,7 @@ export async function resolveBackendExecutable(backend, options = {}) {
|
|
|
22
24
|
|
|
23
25
|
export async function inspectBackendExecutable(
|
|
24
26
|
backend,
|
|
25
|
-
{ spawnImpl = spawn, candidates = backendCandidates(backend), timeoutMs =
|
|
27
|
+
{ spawnImpl = spawn, candidates = backendCandidates(backend), timeoutMs = 5_000 } = {},
|
|
26
28
|
) {
|
|
27
29
|
const policy = versionPolicy(backend);
|
|
28
30
|
for (const candidate of [...new Set(candidates.filter(Boolean))]) {
|
|
@@ -65,14 +67,26 @@ export async function inspectBackendExecutable(
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export function backendCandidates(backend
|
|
70
|
+
export function backendCandidates(backend, {
|
|
71
|
+
platform = process.platform,
|
|
72
|
+
environment = process.env,
|
|
73
|
+
home = os.homedir(),
|
|
74
|
+
} = {}) {
|
|
69
75
|
if (backend === "claude") {
|
|
70
|
-
|
|
76
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
77
|
+
const userHome = platform === "win32" ? environment.USERPROFILE || home : home;
|
|
78
|
+
return [
|
|
79
|
+
environment.SIMY_CLAUDE_BIN,
|
|
80
|
+
"claude",
|
|
81
|
+
// Desktop launch environments may predate the installer's PATH update.
|
|
82
|
+
paths.join(userHome, ".local", "bin", platform === "win32" ? "claude.exe" : "claude"),
|
|
83
|
+
...(platform === "darwin" ? ["/opt/homebrew/bin/claude", "/usr/local/bin/claude"] : []),
|
|
84
|
+
];
|
|
71
85
|
}
|
|
72
86
|
return [
|
|
73
|
-
|
|
87
|
+
environment.SIMY_CODEX_BIN,
|
|
74
88
|
"codex",
|
|
75
|
-
...(
|
|
89
|
+
...(platform === "darwin" ? [MACOS_CODEX_BINARY] : []),
|
|
76
90
|
];
|
|
77
91
|
}
|
|
78
92
|
|
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.5.
|
|
16
|
+
export const CLI_VERSION = "0.5.24";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
|
@@ -43,6 +43,7 @@ export function withCliContract(
|
|
|
43
43
|
installMode,
|
|
44
44
|
backends: capabilities.backends,
|
|
45
45
|
backendVersions: capabilities.backend_versions,
|
|
46
|
+
providerStatuses: capabilities.provider_statuses,
|
|
46
47
|
authenticated,
|
|
47
48
|
}),
|
|
48
49
|
...(installMode ? { install_mode: installMode } : {}),
|
|
@@ -19,7 +19,7 @@ export async function runConnectionReminder({
|
|
|
19
19
|
} = {}) {
|
|
20
20
|
const explicitWebOrigin = resolveExplicitWebOrigin(env);
|
|
21
21
|
const sessionRoot = env.SIMY_HOME?.trim() || join(homedir(), ".simy");
|
|
22
|
-
const pluginData = env.PLUGIN_DATA?.trim() || join(sessionRoot, "plugin-data", "simy");
|
|
22
|
+
const pluginData = env.PLUGIN_DATA?.trim() || env.CLAUDE_PLUGIN_DATA?.trim() || join(sessionRoot, "plugin-data", "simy");
|
|
23
23
|
const sessionId = String(input.session_id || input.sessionId || env.CODEX_THREAD_ID || "unknown");
|
|
24
24
|
const statePath = reminderStatePath(pluginData, explicitWebOrigin || "any-origin", sessionId);
|
|
25
25
|
const connected = explicitWebOrigin
|
package/src/console/app.js
CHANGED
|
@@ -215,7 +215,7 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
|
|
|
215
215
|
setNotice("Executor paused.");
|
|
216
216
|
break;
|
|
217
217
|
case "resume":
|
|
218
|
-
agent.controls.resume(selectedRun);
|
|
218
|
+
await agent.controls.resume(selectedRun);
|
|
219
219
|
setNotice("Executor resumed.");
|
|
220
220
|
break;
|
|
221
221
|
case "stop":
|