@awak-app/simy-cli 0.7.15 → 0.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -98
- package/package.json +5 -3
- package/src/agent.js +129 -7
- package/src/backend-executable.js +7 -6
- package/src/bounded-local-task-subtask-pool.js +32 -6
- package/src/bounded-local-task-subtasks-contract.js +1 -1
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/.codex-plugin/plugin.json +2 -2
- package/src/codex-plugins/simy/hooks/README.md +8 -3
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +302 -78
- package/src/codex-plugins/simy/hooks/hooks.json +1 -1
- package/src/codex-plugins/simy/hooks/loop-kernel/conformance.json +136 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/index.mjs +42 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/outcome-review.mjs +31 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/pin.json +6 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/policy.json +141 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/task-pool.mjs +34 -0
- package/src/codex-plugins/simy/hooks/loop-kernel/work-frontier.mjs +132 -0
- package/src/codex-plugins/simy/hooks/loop-kernel.mjs +6 -0
- package/src/codex-plugins/simy/hooks/outcome-progress.mjs +16 -5
- package/src/codex-plugins/simy/hooks/purpose-completion-contract.mjs +10 -0
- package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +12 -11
- package/src/codex-plugins/simy/hooks/purpose-evidence.mjs +28 -38
- package/src/codex-plugins/simy/hooks/purpose-rule-policy.mjs +1 -2
- package/src/codex-plugins/simy/hooks/purpose-rule-review.mjs +5 -63
- package/src/codex-plugins/simy/hooks/purpose-search.mjs +21 -4
- package/src/codex-plugins/simy/hooks/result-cycle.mjs +166 -0
- package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +98 -12
- package/src/codex-plugins/simy/hooks/user-experience.mjs +26 -0
- package/src/codex-plugins/simy/hooks/work-frontier.mjs +2 -0
- package/src/codex-plugins/simy/skills/diagnose/SKILL.md +10 -4
- package/src/coding-run-recovery.js +4 -2
- package/src/delivery-hooks/claude-workflow.md +10 -7
- package/src/delivery-hooks/command.js +76 -10
- package/src/delivery-hooks/consistency-monitor.js +20 -4
- package/src/delivery-hooks/consult.js +60 -0
- package/src/delivery-hooks/diagnosis.js +2 -2
- package/src/delivery-hooks/legacy-hook-routing.js +46 -8
- package/src/delivery-hooks/local-runtime.js +81 -1
- package/src/delivery-hooks/local-supervisor.js +13 -4
- package/src/delivery-hooks/repair-continuation.js +80 -0
- package/src/delivery-hooks/repair.js +164 -0
- package/src/delivery-hooks/supervisor-handover.js +84 -0
- package/src/delivery-hooks/supervisor-owner.js +3 -2
- package/src/delivery-hooks/supervisor-service.js +38 -6
- package/src/delivery-hooks/supervisor-watchdog.js +15 -4
- package/src/delivery-hooks/supervisor.js +151 -18
- package/src/desktop-access.js +2 -1
- package/src/desktop-host.js +35 -2
- package/src/durable-local-task-worker.js +372 -58
- package/src/execution-capability-contract.js +15 -1
- package/src/index.js +3 -3
- package/src/local-photos-read.swift +33 -0
- package/src/local-photos.js +47 -0
- package/src/local-task-attachment-store.js +31 -0
- package/src/local-task-destination.js +174 -0
- package/src/local-task.js +119 -7
- package/src/native-delegation-contract.js +68 -0
- package/src/native-provider-identity.js +60 -0
- package/src/orchestrator/audit.js +24 -323
- package/src/orchestrator/budget.js +22 -11
- package/src/orchestrator/charter-validation.js +3 -41
- package/src/orchestrator/checklist-continuation.js +2 -14
- package/src/orchestrator/completion-contract.js +4 -49
- package/src/orchestrator/contract.js +5 -9
- package/src/orchestrator/delivery/WORKFLOW.md +30 -710
- package/src/orchestrator/delivery/references/acceptance-evidence.md +4 -216
- package/src/orchestrator/delivery/references/change-boundary-matrix.md +4 -103
- package/src/orchestrator/delivery/references/chrome-user-verification.md +5 -133
- package/src/orchestrator/delivery/references/cross-repository-control.md +4 -74
- package/src/orchestrator/delivery/references/delivery-levels.md +3 -117
- package/src/orchestrator/delivery/references/local-sqm-evidence.md +1 -1
- package/src/orchestrator/delivery/references/minimum-completion.md +4 -144
- package/src/orchestrator/delivery/references/pr-closure.md +4 -200
- package/src/orchestrator/delivery/references/shared-system-specifications.md +4 -161
- package/src/orchestrator/delivery/references/state-and-receipts.md +4 -245
- package/src/orchestrator/delivery/references/state-model.md +4 -185
- package/src/orchestrator/delivery/references/ux-specification.md +4 -95
- package/src/orchestrator/delivery-level.js +2 -10
- package/src/orchestrator/evidence.js +2 -3
- package/src/orchestrator/independent-audit.js +15 -164
- package/src/orchestrator/instruction.js +19 -254
- package/src/orchestrator/loop.js +35 -95
- package/src/orchestrator/outcome-monitor.js +21 -0
- package/src/orchestrator/pr-closure.js +2 -16
- package/src/orchestrator/recovery.js +32 -2
- package/src/orchestrator/ux-specification.js +3 -12
- package/src/provider-delegation.js +336 -0
- package/src/runner.js +5 -2
- package/src/session-supervision/acceptance-reads.js +34 -0
- package/src/session-supervision/artifact-observer.js +138 -0
- package/src/session-supervision/browser-cdp.js +59 -0
- package/src/session-supervision/browser-recovery.js +97 -0
- package/src/session-supervision/codex-app-server.js +61 -22
- package/src/session-supervision/completion-artifacts.js +44 -0
- package/src/session-supervision/consistency-auditor.js +8 -11
- package/src/session-supervision/continuation-analysis.js +47 -28
- package/src/session-supervision/final-response-recovery.js +5 -0
- package/src/session-supervision/index.js +22 -5
- package/src/session-supervision/interaction-proxy.js +250 -0
- package/src/session-supervision/interaction-task.js +189 -0
- package/src/session-supervision/native-interaction.js +102 -0
- package/src/session-supervision/provider-failure.js +40 -0
- package/src/session-supervision/purpose-instruction.js +14 -14
- package/src/session-supervision/purpose-recovery.js +32 -12
- package/src/session-supervision/purpose-verification.js +50 -216
- package/src/session-supervision/recovery-frontier.js +55 -0
- package/src/session-supervision/stop-notifications.js +8 -8
- package/src/sqm/agent-skill.js +1 -1
- package/src/sqm/checkers.js +12 -6
- package/src/sqm/claude-plugin-update.js +22 -8
- package/src/sqm/claude-plugin.js +6 -0
- package/src/sqm/local-tests.js +2 -1
- package/src/sqm/plugin-cache.js +67 -0
- package/src/sqm/proof.js +25 -0
- package/src/sqm/report.js +1 -0
- package/src/sqm/workflow-onboarding.js +3 -39
- package/src/sqm/workflow-update.js +17 -6
- package/src/startup-preflight.js +2 -20
- package/src/team-status-fence-store.js +32 -0
- package/src/team-threads.js +457 -0
package/src/agent.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { readLocalPhotoSource } from "./local-photos.js";
|
|
2
|
+
import { resolveLocalTaskDestination, saveLocalTaskDestination } from "./local-task-destination.js";
|
|
3
|
+
import { observePairedNativeIdentities } from './native-provider-identity.js';
|
|
4
|
+
import { providerDelegationFromRequest } from './provider-delegation.js';
|
|
1
5
|
import { createLedgerExecutionWorker, discoverLedgerWorkers } from './update-detachment/worker-ledger.js';
|
|
2
6
|
import { normalizeProviderModelSelection } from "./provider-model-selection.js";
|
|
3
7
|
import { assertConnectionScope, connectionScope, connectionDiagnostic } from "./connection-diagnostics.js";
|
|
@@ -147,6 +151,7 @@ import {
|
|
|
147
151
|
import {
|
|
148
152
|
createDurableLocalTaskApi,
|
|
149
153
|
createDurableLocalTaskWorker,
|
|
154
|
+
prepareRepositoryWorkspace,
|
|
150
155
|
} from "./durable-local-task-worker.js";
|
|
151
156
|
import { createLocalTaskAttachmentStore } from "./local-task-attachment-store.js";
|
|
152
157
|
import {
|
|
@@ -297,12 +302,20 @@ export async function startAgent({
|
|
|
297
302
|
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" });
|
|
298
303
|
return link;
|
|
299
304
|
};
|
|
305
|
+
let nativeIdentityCapability = null, nativeIdentityActor = null, nativeIdentityObservedAt = 0, nativeIdentityReadInFlight = null;
|
|
306
|
+
const nativeActor = () => JSON.stringify([apiOrigin, session?.account_id, session?.organization_id, session?.device_id]);
|
|
300
307
|
let providerStatuses = { codex: { state: "unknown" }, claude: { state: "unknown" } };
|
|
301
308
|
const providerRecoveryRevisions = { codex: 0, claude: 0 };
|
|
302
309
|
const refreshAvailableCapabilities = () => {
|
|
303
310
|
const features = contractSourceCapabilities.features;
|
|
304
311
|
availableCapabilities = withCliContract({
|
|
305
312
|
...contractSourceCapabilities,
|
|
313
|
+
...(features?.native_delegation === true || dependencies.observePairedNativeIdentities ? {
|
|
314
|
+
native_delegation: nativeIdentityActor === nativeActor() && isSessionValid(session, Date.now(), apiOrigin)
|
|
315
|
+
? { ...nativeIdentityCapability, identities: (nativeIdentityCapability?.identities || []).filter(identity =>
|
|
316
|
+
providerLinks[identity.provider]?.connected && (providerLinks[identity.provider]?.epoch ?? null) === identity.connection_epoch) }
|
|
317
|
+
: { schema_version: 'native_delegation_capability.v1', device_id: session?.device_id || null, identities: [] },
|
|
318
|
+
} : {}),
|
|
306
319
|
...(purposeSigner ? { purpose_verification: purposeSigner } : {}),
|
|
307
320
|
...(features ? { features: { ...features, purpose_host_verification: Boolean(purposeSigner) && isSessionValid(session, Date.now(), apiOrigin), ...(Object.hasOwn(features, "sqm_local_review")
|
|
308
321
|
? { sqm_local_review: features.sqm_local_review === true && sqmEnabled && !pendingOrganization && desktopWorkAllowed() } : {}) } } : {}),
|
|
@@ -489,6 +502,24 @@ export async function startAgent({
|
|
|
489
502
|
notifyDesktopStatusChange();
|
|
490
503
|
}
|
|
491
504
|
})]);
|
|
505
|
+
if ((contractSourceCapabilities.features?.native_delegation === true || dependencies.observePairedNativeIdentities) && session?.device_id && !nativeIdentityReadInFlight && (force || nativeIdentityActor !== nativeActor() || Date.now() - nativeIdentityObservedAt > 60000)) {
|
|
506
|
+
const actor = nativeActor();
|
|
507
|
+
const observation = (async () => {
|
|
508
|
+
const observed = await (dependencies.observePairedNativeIdentities || observePairedNativeIdentities)({
|
|
509
|
+
deviceId: session.device_id, providerStatuses, providerLinks });
|
|
510
|
+
if (!shuttingDown && actor === nativeActor()) {
|
|
511
|
+
nativeIdentityCapability = observed; nativeIdentityActor = actor; nativeIdentityObservedAt = Date.now();
|
|
512
|
+
refreshAvailableCapabilities();
|
|
513
|
+
notifyDesktopStatusChange();
|
|
514
|
+
// Official account observation must not delay the existing desktop
|
|
515
|
+
// startup/status path. Publish its catalog when it is actually ready.
|
|
516
|
+
if (deferStartupSynchronization) void heartbeat().catch(error => reportHeartbeatError(error, quiet));
|
|
517
|
+
}
|
|
518
|
+
})();
|
|
519
|
+
nativeIdentityReadInFlight = observation;
|
|
520
|
+
void observation.finally(() => { if (nativeIdentityReadInFlight === observation) nativeIdentityReadInFlight = null; }).catch(() => {});
|
|
521
|
+
if (!deferStartupSynchronization) await observation;
|
|
522
|
+
}
|
|
492
523
|
const binaryStateChanged = ["codex", "claude"].some((provider) =>
|
|
493
524
|
previous[provider]?.version !== providerStatuses[provider]?.version ||
|
|
494
525
|
previous[provider]?.state !== providerStatuses[provider]?.state);
|
|
@@ -544,7 +575,21 @@ export async function startAgent({
|
|
|
544
575
|
await callback?.(...args);
|
|
545
576
|
await guard();
|
|
546
577
|
};
|
|
547
|
-
return { ...options, sessionRoot,
|
|
578
|
+
return { ...options, sessionRoot, delegationOptions: { supervisionRoot: join(sessionRoot, "delivery-hooks"), pairedDeviceId: session?.device_id, providerLinkEpoch: link?.epoch ?? null, ...options.delegationOptions,
|
|
579
|
+
onWorkspaceAuthorized: async roots => {
|
|
580
|
+
await guard();
|
|
581
|
+
if (task.local_destination?.authorized_root) {
|
|
582
|
+
await resolveLocalTaskDestination({ local_destination: task.local_destination, operation: task.operation,
|
|
583
|
+
permission_mode: task.permission_mode, workspace_mode: task.workspace_mode, repository: task.repository }, {
|
|
584
|
+
deviceId: session?.device_id, resolveRepository: ensureRepositoryIndexed, nativeWritableRoots: roots,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
task.native_writable_roots = [...roots];
|
|
588
|
+
await options.delegationOptions?.onWorkspaceAuthorized?.(roots);
|
|
589
|
+
await guard();
|
|
590
|
+
} },
|
|
591
|
+
beforeCompletion: options.beforeCompletion ? wrap(options.beforeCompletion) : null,
|
|
592
|
+
beforeProviderInvocation: wrap(options.beforeProviderInvocation), beforeSpawn: wrap(options.beforeSpawn) };
|
|
548
593
|
};
|
|
549
594
|
const codingRuntimeOptions = run => {
|
|
550
595
|
const wrap = (custom, marker) => async context => {
|
|
@@ -856,7 +901,7 @@ export async function startAgent({
|
|
|
856
901
|
notifyDesktopStatusChange();
|
|
857
902
|
let deviceHeartbeatSucceeded = false;
|
|
858
903
|
const requestedSession = session;
|
|
859
|
-
const advertisedProviders = JSON.stringify(providerStatuses);
|
|
904
|
+
const advertisedProviders = JSON.stringify([providerStatuses, nativeIdentityCapability]);
|
|
860
905
|
heartbeatInFlight = Promise.resolve()
|
|
861
906
|
.then(async () => { if (enforceDesktopAccess) await desktopAccess.refresh(true); })
|
|
862
907
|
.then(() =>
|
|
@@ -878,7 +923,7 @@ export async function startAgent({
|
|
|
878
923
|
// browser authorization. Publish verified readiness after their probe.
|
|
879
924
|
const publishVerifiedProviders = async () => {
|
|
880
925
|
await readProviderStatuses();
|
|
881
|
-
if (!shuttingDown && JSON.stringify(providerStatuses) !== advertisedProviders) {
|
|
926
|
+
if (!shuttingDown && JSON.stringify([providerStatuses, nativeIdentityCapability]) !== advertisedProviders) {
|
|
882
927
|
await heartbeatDevice({ apiOrigin, apiBaseUrl: session.api_base_url, token: session.token,
|
|
883
928
|
port, capabilities: refreshAvailableCapabilities(), repoInventory: repositoryInventory });
|
|
884
929
|
}
|
|
@@ -1011,6 +1056,8 @@ export async function startAgent({
|
|
|
1011
1056
|
createDurableLocalTaskWorker;
|
|
1012
1057
|
durableLocalTaskWorker = createWorker({
|
|
1013
1058
|
api: durableApi,
|
|
1059
|
+
enableBoundedSubtasks:
|
|
1060
|
+
availableCapabilities.features.bounded_local_task_subtasks === true,
|
|
1014
1061
|
registry: localTaskRegistry,
|
|
1015
1062
|
storageRoot: taskStorageRoot,
|
|
1016
1063
|
workspaceRoot: dependencies.localTaskWorkspaceRoot,
|
|
@@ -1029,6 +1076,7 @@ export async function startAgent({
|
|
|
1029
1076
|
return (dependencies.runLocalTask || startLocalTask)(task, providerRuntimeOptions(task, options, scope, link));
|
|
1030
1077
|
},
|
|
1031
1078
|
attachmentStore: localTaskAttachmentStore,
|
|
1079
|
+
readPhotoSource: dependencies.readLocalPhotoSource || readLocalPhotoSource,
|
|
1032
1080
|
getDeviceId: () => session?.device_id || null,
|
|
1033
1081
|
beginWork: () => acquireUpdateWork(updateManager),
|
|
1034
1082
|
onError: (error) => reportDurableLocalTaskError(error, quiet),
|
|
@@ -1777,12 +1825,17 @@ export async function startAgent({
|
|
|
1777
1825
|
});
|
|
1778
1826
|
return;
|
|
1779
1827
|
}
|
|
1780
|
-
let requestFingerprint;
|
|
1828
|
+
let requestFingerprint, providerDelegation;
|
|
1781
1829
|
try {
|
|
1830
|
+
providerDelegation = providerDelegationFromRequest(body, idempotencyKey);
|
|
1782
1831
|
requestFingerprint = createLocalTaskRequestFingerprint({
|
|
1783
1832
|
instruction,
|
|
1784
1833
|
backend: body.backend,
|
|
1785
1834
|
model: body.model,
|
|
1835
|
+
delegation: providerDelegation,
|
|
1836
|
+
localDestination: body.local_destination,
|
|
1837
|
+
localPhotoSource: body.local_photo_source,
|
|
1838
|
+
nativeIdentity: body.native_identity,
|
|
1786
1839
|
operation: guardrail.operation,
|
|
1787
1840
|
permissionMode: guardrail.permission_mode,
|
|
1788
1841
|
workspaceMode,
|
|
@@ -1882,7 +1935,7 @@ export async function startAgent({
|
|
|
1882
1935
|
workspace = {
|
|
1883
1936
|
...prepared,
|
|
1884
1937
|
id,
|
|
1885
|
-
|
|
1938
|
+
...(await prepareRepositoryWorkspace({ repositoryPath: indexedRepository.local_path, expectedRepository: repository, localTaskId: id, prepareOutputs: body.permission_mode === "workspace_write" })),
|
|
1886
1939
|
};
|
|
1887
1940
|
} else {
|
|
1888
1941
|
const id = createLocalTaskId();
|
|
@@ -1895,6 +1948,19 @@ export async function startAgent({
|
|
|
1895
1948
|
})),
|
|
1896
1949
|
};
|
|
1897
1950
|
}
|
|
1951
|
+
if (body.local_destination?.authorized_root && providerDelegation?.provider !== "codex") {
|
|
1952
|
+
throw Object.assign(new Error("The selected folder requires an already authorized native Codex workspace."), { code: "local_task_destination_unconfirmed" });
|
|
1953
|
+
}
|
|
1954
|
+
let deliveryTask = null;
|
|
1955
|
+
const destinationSpec = { ...body, workspace_mode: workspaceMode, repository };
|
|
1956
|
+
const resolveDestination = () => resolveLocalTaskDestination(destinationSpec, {
|
|
1957
|
+
deviceId: session?.device_id,
|
|
1958
|
+
resolveRepository: ensureRepositoryIndexed,
|
|
1959
|
+
nativeWritableRoots: deliveryTask?.native_writable_roots,
|
|
1960
|
+
});
|
|
1961
|
+
let destination = body.local_destination && !body.local_destination.authorized_root ? await resolveDestination() : null;
|
|
1962
|
+
const photoContext = body.local_photo_source ? await (dependencies.readLocalPhotoSource || readLocalPhotoSource)(body.local_photo_source, { deviceId: session?.device_id }) : null;
|
|
1963
|
+
await authorizeDesktopWork(requestAccessScope);
|
|
1898
1964
|
const stagedAttachments = await stagePreparedRunAttachments({
|
|
1899
1965
|
runId: workspace.id,
|
|
1900
1966
|
prepared: preparedAttachments,
|
|
@@ -1905,6 +1971,10 @@ export async function startAgent({
|
|
|
1905
1971
|
instruction,
|
|
1906
1972
|
backend: body.backend,
|
|
1907
1973
|
model: body.model,
|
|
1974
|
+
delegation: providerDelegation,
|
|
1975
|
+
localDestination: body.local_destination,
|
|
1976
|
+
localPhotoSource: body.local_photo_source,
|
|
1977
|
+
nativeIdentity: body.native_identity,
|
|
1908
1978
|
operation: guardrail.operation,
|
|
1909
1979
|
permissionMode: guardrail.permission_mode,
|
|
1910
1980
|
workspaceMode,
|
|
@@ -1915,6 +1985,7 @@ export async function startAgent({
|
|
|
1915
1985
|
outputsRoot: workspace.outputs_root,
|
|
1916
1986
|
outputsRootExpected: workspace.outputs_root_expected,
|
|
1917
1987
|
attachments: stagedAttachments,
|
|
1988
|
+
localPhotoContext: photoContext,
|
|
1918
1989
|
expectedOutputs: body.expected_outputs,
|
|
1919
1990
|
scenarioPack,
|
|
1920
1991
|
providerTokenBudget: body.provider_token_budget,
|
|
@@ -1927,6 +1998,7 @@ export async function startAgent({
|
|
|
1927
1998
|
workflowStartIntent,
|
|
1928
1999
|
purposeVerification,
|
|
1929
2000
|
});
|
|
2001
|
+
deliveryTask = task;
|
|
1930
2002
|
if (purposeVerification) await preparePurposeVerification(task, { session, apiOrigin, clientRequestId: idempotencyKey, keyPath: purposeKeyPath,
|
|
1931
2003
|
authorize: async () => {
|
|
1932
2004
|
await authorizeDesktopWork(requestAccessScope);
|
|
@@ -1937,7 +2009,27 @@ export async function startAgent({
|
|
|
1937
2009
|
requestClaim.complete(task);
|
|
1938
2010
|
requestClaimCompleted = true;
|
|
1939
2011
|
const runLocalTask = dependencies.runLocalTask || startLocalTask;
|
|
1940
|
-
const runtimeOptions = providerRuntimeOptions(task, { session, apiOrigin,
|
|
2012
|
+
const runtimeOptions = providerRuntimeOptions(task, { session, apiOrigin,
|
|
2013
|
+
beforeCompletion: body.local_destination ? async completed => {
|
|
2014
|
+
await authorizeDesktopWork(requestAccessScope);
|
|
2015
|
+
const current = await resolveDestination();
|
|
2016
|
+
if (destination && (current.root !== destination.root || current.directory !== destination.directory)) {
|
|
2017
|
+
throw Object.assign(new Error("The authorized save folder changed."), { code: "local_task_destination_unconfirmed" });
|
|
2018
|
+
}
|
|
2019
|
+
destination ||= current;
|
|
2020
|
+
completed.local_destination_receipts = [];
|
|
2021
|
+
await saveLocalTaskDestination({ task: completed, destination: current,
|
|
2022
|
+
assertActive: async () => {
|
|
2023
|
+
await authorizeDesktopWork(requestAccessScope);
|
|
2024
|
+
if (completed.stop_requested) throw Object.assign(new Error("Local delivery was stopped."), { code: "local_task_destination_unconfirmed" });
|
|
2025
|
+
},
|
|
2026
|
+
onReceipt: async receipt => {
|
|
2027
|
+
completed.local_destination_receipts.push(receipt);
|
|
2028
|
+
await persistLocalTask(completed);
|
|
2029
|
+
},
|
|
2030
|
+
});
|
|
2031
|
+
} : null,
|
|
2032
|
+
beforeSpawn: async localTask => {
|
|
1941
2033
|
if (purposeWorkspaceBusy(localTask.workspace_path)) throw Object.assign(new Error("Independent verification owns this workspace."), { code: "purpose_verification_workspace_busy" });
|
|
1942
2034
|
if (purposeVerification) {
|
|
1943
2035
|
await authorizeDesktopWork(requestAccessScope);
|
|
@@ -1999,6 +2091,10 @@ export async function startAgent({
|
|
|
1999
2091
|
return;
|
|
2000
2092
|
}
|
|
2001
2093
|
const body = await readJson(req);
|
|
2094
|
+
if (body.delegation || body.delegation_target) {
|
|
2095
|
+
json(res, 409, { error: 'Provider delegation requires the existing Local Task admission.', code: 'delegation_local_task_required' });
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2002
2098
|
const instruction = String(body.instruction || "").trim();
|
|
2003
2099
|
if (!instruction) {
|
|
2004
2100
|
json(res, 400, { error: "instruction is required" });
|
|
@@ -2256,6 +2352,7 @@ export async function startAgent({
|
|
|
2256
2352
|
? body.expected_evidence
|
|
2257
2353
|
: [],
|
|
2258
2354
|
required_checks: Array.isArray(body.required_checks) ? body.required_checks : [],
|
|
2355
|
+
independent_review_required: body.independent_review_required === true,
|
|
2259
2356
|
require_human_approval: body.require_human_approval !== false,
|
|
2260
2357
|
design_summary:
|
|
2261
2358
|
typeof body.design_summary === "string" ? body.design_summary : "",
|
|
@@ -2704,7 +2801,7 @@ export async function startAgent({
|
|
|
2704
2801
|
if (codingRunMonitorTimer) clearInterval(codingRunMonitorTimer);
|
|
2705
2802
|
updateManager?.stop?.();
|
|
2706
2803
|
await Promise.all([stopDesktopTasks(), codexWorkPatternSync?.close?.(),
|
|
2707
|
-
codexWorkPatternSyncInFlight, codingRunMonitorInFlight]);
|
|
2804
|
+
codexWorkPatternSyncInFlight, codingRunMonitorInFlight, nativeIdentityReadInFlight?.catch(() => {})]);
|
|
2708
2805
|
try {
|
|
2709
2806
|
await (dependencies.removeConnectionDiscovery || removeConnectionDiscovery)(
|
|
2710
2807
|
connectionDiscovery,
|
|
@@ -2829,6 +2926,29 @@ export async function startAgent({
|
|
|
2829
2926
|
sqmLocalReviewWorker?.retry?.(); notifyDesktopStatusChange();
|
|
2830
2927
|
},
|
|
2831
2928
|
async retrySqmEnvironment() { await authorizeDesktopWork(); sqmLocalReviewWorker?.retry?.(); notifyDesktopStatusChange(); },
|
|
2929
|
+
getTeamThreadsScope() {
|
|
2930
|
+
return { key: desktopScopeRevision(),
|
|
2931
|
+
fenceKey: createHash('sha256').update(JSON.stringify([apiOrigin, session?.account_id ?? null, session?.organization_id ?? null, session?.device_id ?? null])).digest('hex'),
|
|
2932
|
+
organizationId: session?.organization_id ?? null,
|
|
2933
|
+
authenticated: !shuttingDown && !pendingOrganization && isSessionValid(session, Date.now(), apiOrigin) && Boolean(session?.device_id),
|
|
2934
|
+
allowed: !shuttingDown && desktopWorkAllowed() && desktopAccessReady(desktopAccess.snapshot()) };
|
|
2935
|
+
},
|
|
2936
|
+
async requestTeamThreads(body, signal) {
|
|
2937
|
+
const selected = session;
|
|
2938
|
+
const scope = desktopScopeRevision();
|
|
2939
|
+
const off = body?.operation === 'consent' && body.enabled === false;
|
|
2940
|
+
if (!off) await authorizeDesktopWork(scope);
|
|
2941
|
+
if (shuttingDown || pendingOrganization || !isSessionValid(selected, Date.now(), apiOrigin) || selected !== session) throw new Error('Team sign-in required');
|
|
2942
|
+
const response = await (dependencies.fetch || globalThis.fetch)(webApiUrl('team-threads', { webOrigin: apiOrigin, apiBaseUrl: selected.api_base_url }), {
|
|
2943
|
+
method: body ? 'POST' : 'GET', headers: webApiHeaders({ token: selected.token }, body ? { 'Content-Type': 'application/json' } : {}),
|
|
2944
|
+
...(body ? { body: JSON.stringify(body) } : {}), signal: AbortSignal.any([signal, AbortSignal.timeout(10_000)]), cache: 'no-store', redirect: 'error',
|
|
2945
|
+
});
|
|
2946
|
+
if (scope !== desktopScopeRevision() || selected !== session || signal.aborted) throw new Error('Team sign-in changed');
|
|
2947
|
+
if (!response.ok) throw Object.assign(new Error('Shared conversations unavailable'), { status: response.status });
|
|
2948
|
+
const raw = await response.text();
|
|
2949
|
+
if (raw.length > 1_048_576) throw new Error('Team response too large');
|
|
2950
|
+
return JSON.parse(raw);
|
|
2951
|
+
},
|
|
2832
2952
|
async createDesktopWebSession() {
|
|
2833
2953
|
const scope = await authorizeDesktopWork();
|
|
2834
2954
|
const selected = session;
|
|
@@ -3297,6 +3417,7 @@ function capabilitiesFromInspections(codex, claude) {
|
|
|
3297
3417
|
},
|
|
3298
3418
|
},
|
|
3299
3419
|
features: {
|
|
3420
|
+
team_threads: true,
|
|
3300
3421
|
repository_scan_approval: true,
|
|
3301
3422
|
executor_version_preflight: true,
|
|
3302
3423
|
desktop_executor: true,
|
|
@@ -3307,6 +3428,7 @@ function capabilitiesFromInspections(codex, claude) {
|
|
|
3307
3428
|
local_task_streaming: true,
|
|
3308
3429
|
local_task_idempotency: true,
|
|
3309
3430
|
local_task_model: true,
|
|
3431
|
+
native_delegation: true,
|
|
3310
3432
|
progress_continuation_policy: true,
|
|
3311
3433
|
local_task_reconciliation: true,
|
|
3312
3434
|
local_task_control_plane: true,
|
|
@@ -26,7 +26,7 @@ export async function resolveBackendExecutable(backend, options = {}) {
|
|
|
26
26
|
|
|
27
27
|
export async function inspectBackendExecutable(
|
|
28
28
|
backend,
|
|
29
|
-
{ spawnImpl = spawn, candidates, timeoutMs = 5_000, signal, discoveryOptions = {}, minimumVersion } = {},
|
|
29
|
+
{ spawnImpl = spawn, candidates, timeoutMs = 5_000, signal, discoveryOptions = {}, minimumVersion, executionEnvironment } = {},
|
|
30
30
|
) {
|
|
31
31
|
const assertActive = () => { if (signal?.aborted) throw Object.assign(new Error('Backend inspection was cancelled.'), { code: 'cancelled' }); };
|
|
32
32
|
assertActive();
|
|
@@ -41,7 +41,7 @@ export async function inspectBackendExecutable(
|
|
|
41
41
|
// still fall back to automatic discovery, as before.
|
|
42
42
|
if (explicitCodex && candidates.includes(explicitCodex)) {
|
|
43
43
|
const pinned = await inspectBackendExecutable(backend, {
|
|
44
|
-
candidates: [explicitCodex], spawnImpl, timeoutMs, signal, minimumVersion,
|
|
44
|
+
candidates: [explicitCodex], spawnImpl, timeoutMs, signal, minimumVersion, executionEnvironment,
|
|
45
45
|
discoveryOptions: { ...discoveryOptions, environment: {} },
|
|
46
46
|
});
|
|
47
47
|
if (pinned.compatible) return pinned;
|
|
@@ -51,11 +51,11 @@ export async function inspectBackendExecutable(
|
|
|
51
51
|
// Comparing all Codex installs must not turn several slow wrappers into a
|
|
52
52
|
// serial startup delay. Keep concurrency bounded and results in source order.
|
|
53
53
|
const probes = backend === "codex"
|
|
54
|
-
? await probeCandidates(candidates, { spawnImpl, timeoutMs, signal }, assertActive) : null;
|
|
54
|
+
? await probeCandidates(candidates, { spawnImpl, timeoutMs, signal, executionEnvironment }, assertActive) : null;
|
|
55
55
|
let selected = null;
|
|
56
56
|
for (const [index, candidate] of candidates.entries()) {
|
|
57
57
|
assertActive();
|
|
58
|
-
const probe = probes ? probes[index] : await probeExecutable(candidate, { spawnImpl, timeoutMs, signal });
|
|
58
|
+
const probe = probes ? probes[index] : await probeExecutable(candidate, { spawnImpl, timeoutMs, signal, executionEnvironment });
|
|
59
59
|
assertActive();
|
|
60
60
|
if (!probe.available) continue;
|
|
61
61
|
const installedVersion = parseBackendVersion(probe.output);
|
|
@@ -111,6 +111,7 @@ export function backendCandidates(backend, {
|
|
|
111
111
|
environment.SIMY_CODEX_BIN,
|
|
112
112
|
"codex",
|
|
113
113
|
...codexPathCandidates(platform, environment),
|
|
114
|
+
...(platform !== "win32" ? [path.posix.join(home, ".local", "bin", "codex")] : []),
|
|
114
115
|
...(platform === "darwin" ? ["/opt/homebrew/bin/codex", "/usr/local/bin/codex"] : []),
|
|
115
116
|
...(platform === "darwin" ? ["/Applications", path.posix.join(home, "Applications")].flatMap(directory =>
|
|
116
117
|
["ChatGPT.app", "Codex.app"].flatMap(app => {
|
|
@@ -228,13 +229,13 @@ function numericVersionParts(value) {
|
|
|
228
229
|
return match.slice(1).map(Number);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
function probeExecutable(command, { spawnImpl, timeoutMs, signal }) {
|
|
232
|
+
function probeExecutable(command, { spawnImpl, timeoutMs, signal, executionEnvironment }) {
|
|
232
233
|
return new Promise((resolve) => {
|
|
233
234
|
let settled = false, terminating = false;
|
|
234
235
|
let timeout, force, limit;
|
|
235
236
|
let output = Buffer.alloc(0);
|
|
236
237
|
let child;
|
|
237
|
-
try { child = spawnImpl(command, ["--version"], { stdio: ["ignore", "pipe", "pipe"] }); }
|
|
238
|
+
try { child = spawnImpl(command, ["--version"], { stdio: ["ignore", "pipe", "pipe"], ...(executionEnvironment ? { env: { ...process.env, ...executionEnvironment } } : {}) }); }
|
|
238
239
|
catch { resolve({ available: false, output: "" }); return; }
|
|
239
240
|
const capture = chunk => {
|
|
240
241
|
// Version probes need a tiny prefix. Drain noisy executables without
|
|
@@ -58,6 +58,7 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
58
58
|
prepareWorkspace = prepareLocalTaskWorkspace,
|
|
59
59
|
leaseSeconds = DEFAULT_LEASE_SECONDS,
|
|
60
60
|
heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
61
|
+
now = Date.now,
|
|
61
62
|
setIntervalImpl = setInterval,
|
|
62
63
|
clearIntervalImpl = clearInterval,
|
|
63
64
|
onError = () => {},
|
|
@@ -111,10 +112,12 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
111
112
|
let completionConfirmed = false;
|
|
112
113
|
let completionState = null;
|
|
113
114
|
let providerStarted = false;
|
|
115
|
+
let knownLeaseExpiry = Number.NaN;
|
|
114
116
|
const startedAt = Date.now();
|
|
115
117
|
try {
|
|
116
118
|
normalized = normalizeDurableLocalTaskSubtaskClaim(claim, {
|
|
117
119
|
parentAuthority: parent,
|
|
120
|
+
nowMs: now(),
|
|
118
121
|
leaseSeconds,
|
|
119
122
|
});
|
|
120
123
|
if (normalized.disposition !== "execute") {
|
|
@@ -129,6 +132,12 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
129
132
|
}
|
|
130
133
|
batchId ??= normalized.batch.batch_id;
|
|
131
134
|
|
|
135
|
+
knownLeaseExpiry = normalized.attempt.leaseExpiresAtMs;
|
|
136
|
+
const checkLease = async () => {
|
|
137
|
+
if (!controller.signal.aborted && !completionConfirmed && now() >= knownLeaseExpiry) await cancel("lease_lost");
|
|
138
|
+
return !controller.signal.aborted;
|
|
139
|
+
};
|
|
140
|
+
|
|
132
141
|
const execution = boundedLocalTaskSubtaskExecution(normalized, parent);
|
|
133
142
|
const runtime = await materializeSubtaskRuntime({
|
|
134
143
|
normalized,
|
|
@@ -140,7 +149,7 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
140
149
|
downloadSource: sourceDownloader,
|
|
141
150
|
signal: controller.signal,
|
|
142
151
|
});
|
|
143
|
-
if (
|
|
152
|
+
if (!await checkLease()) return;
|
|
144
153
|
|
|
145
154
|
const fence = durableLocalTaskSubtaskFence(normalized, parent);
|
|
146
155
|
let boundaryConfirmed = false;
|
|
@@ -161,7 +170,7 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
161
170
|
}).catch(onError);
|
|
162
171
|
return;
|
|
163
172
|
}
|
|
164
|
-
if (boundaryConfirmed !== true ||
|
|
173
|
+
if (boundaryConfirmed !== true || !await checkLease()) {
|
|
165
174
|
await api.abandon_local_task_subtask_attempt_v2({
|
|
166
175
|
...fence,
|
|
167
176
|
p_reason_code: controller.signal.aborted
|
|
@@ -174,6 +183,8 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
174
183
|
|
|
175
184
|
const heartbeat = async () => {
|
|
176
185
|
if (controller.signal.aborted || completionConfirmed) return;
|
|
186
|
+
const renewalStartedAt = now();
|
|
187
|
+
if (!await checkLease()) return;
|
|
177
188
|
try {
|
|
178
189
|
const response =
|
|
179
190
|
await api.heartbeat_local_task_subtask_attempt_v2({
|
|
@@ -190,6 +201,7 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
190
201
|
) {
|
|
191
202
|
return;
|
|
192
203
|
}
|
|
204
|
+
if (!await checkLease()) return;
|
|
193
205
|
if (
|
|
194
206
|
response?.ok !== true ||
|
|
195
207
|
response.cancel_requested === true ||
|
|
@@ -198,6 +210,8 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
198
210
|
normalized.parent.cancel_generation)
|
|
199
211
|
) {
|
|
200
212
|
await cancel("server_cancelled");
|
|
213
|
+
} else {
|
|
214
|
+
knownLeaseExpiry = renewalStartedAt + leaseSeconds * 1_000;
|
|
201
215
|
}
|
|
202
216
|
} catch (error) {
|
|
203
217
|
if (completionConfirmed && isKnownSubtaskFenceError(error)) return;
|
|
@@ -214,9 +228,11 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
214
228
|
}
|
|
215
229
|
};
|
|
216
230
|
heartbeatTimer = setIntervalImpl(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
heartbeatInFlight =
|
|
231
|
+
void checkLease().then((valid) => {
|
|
232
|
+
if (!valid || heartbeatInFlight || completionConfirmed) return;
|
|
233
|
+
heartbeatInFlight = heartbeat().finally(() => {
|
|
234
|
+
heartbeatInFlight = null;
|
|
235
|
+
});
|
|
220
236
|
});
|
|
221
237
|
}, heartbeatIntervalMs);
|
|
222
238
|
|
|
@@ -245,6 +261,13 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
245
261
|
),
|
|
246
262
|
);
|
|
247
263
|
}
|
|
264
|
+
// Lease loss after the paid boundary cannot establish a cancelled result.
|
|
265
|
+
// Preserve a known result, but leave interrupted work for server resolution.
|
|
266
|
+
if (providerStarted && controller.signal.reason === "lease_lost" &&
|
|
267
|
+
rawResult?.status === "cancelled") {
|
|
268
|
+
rawResult = { ...rawResult, status: "unknown",
|
|
269
|
+
failure_code: "local_task_subtask_provider_result_unknown" };
|
|
270
|
+
}
|
|
248
271
|
const result = normalizeDurableLocalTaskSubtaskResult(
|
|
249
272
|
normalizeRunResult(rawResult, startedAt),
|
|
250
273
|
{ normalizedClaim: normalized },
|
|
@@ -305,12 +328,15 @@ export function createBoundedLocalTaskSubtaskPool({
|
|
|
305
328
|
}
|
|
306
329
|
} finally {
|
|
307
330
|
if (heartbeatTimer) clearIntervalImpl(heartbeatTimer);
|
|
308
|
-
|
|
331
|
+
// Pending renewal has its own fenced completion handler; it must not
|
|
332
|
+
// hold a finished/cancelled slot open after its timer is cleared.
|
|
309
333
|
}
|
|
310
334
|
};
|
|
311
335
|
|
|
312
336
|
const scheduler = async () => {
|
|
313
337
|
if (!initialized) {
|
|
338
|
+
// Initialization only acknowledges the canonical plan. Backend keeps
|
|
339
|
+
// existing attempts/reservations intact; claim reconciles old generations.
|
|
314
340
|
const initializedBatch =
|
|
315
341
|
await api.initialize_local_task_subtask_batch_v2({
|
|
316
342
|
p_turn_id: parent.turnId,
|
|
@@ -1043,7 +1043,7 @@ function uploadedAttachmentRefs(value, name) {
|
|
|
1043
1043
|
return refs;
|
|
1044
1044
|
}
|
|
1045
1045
|
|
|
1046
|
-
function normalizeParentUploadedRef(item, name) {
|
|
1046
|
+
export function normalizeParentUploadedRef(item, name = "uploaded source") {
|
|
1047
1047
|
record(item, name);
|
|
1048
1048
|
const allowed = new Set([
|
|
1049
1049
|
"schema_version",
|
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.7.
|
|
16
|
+
export const CLI_VERSION = "0.7.17";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function narrativeImageCapabilityMetadata() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simy",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"description": "SIMY quality and PR review
|
|
4
|
+
"description": "SIMY quality and PR review, with automatic checkpoint tracking through enabled provider hooks.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Specialist Doctors Inc.",
|
|
7
7
|
"url": "https://github.com/SpecialistDoctors-Inc"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"interface": {
|
|
18
18
|
"displayName": "SIMY",
|
|
19
19
|
"shortDescription": "Quality and PR review.",
|
|
20
|
-
"longDescription": "Run explicitly requested quality checks and PR reviews.
|
|
20
|
+
"longDescription": "Run explicitly requested quality checks and PR reviews. Enabled hooks automatically track eligible conversations from user prompts through checkpoints. Stops, permission waits and opt-outs are preserved. The local Delivery skill remains retired; independently managed work uses the CLI Agentic Loop.",
|
|
21
21
|
"developerName": "Specialist Doctors Inc.",
|
|
22
22
|
"category": "Developer Tools",
|
|
23
23
|
"capabilities": [
|
|
@@ -18,9 +18,14 @@ scans ports or reads stored session tokens.
|
|
|
18
18
|
|
|
19
19
|
## Delivery Loop hooks
|
|
20
20
|
|
|
21
|
-
`delivery-loop.mjs` is
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
`delivery-loop.mjs` is shared by the Codex and Claude distributions. The CLI
|
|
22
|
+
prepares missing plugin sources automatically and maintains enabled plugin caches.
|
|
23
|
+
SessionStart supplies identity without starting work. A nonempty UserPromptSubmit
|
|
24
|
+
starts standard Delivery Loop by default for eligible connected conversations,
|
|
25
|
+
including when SessionStart was missed. No per-project enable command is needed.
|
|
26
|
+
`simy delivery auto off --provider codex|claude` saves a project override; updates
|
|
27
|
+
preserve it. Existing loops, explicit stops, session opt-outs and revoked provider
|
|
28
|
+
consent remain authoritative. No hook grants provider trust. Stop checks that
|
|
24
29
|
session's checkpoint and returns bounded next-step instructions. Approval/input
|
|
25
30
|
blocks, interruption, completion, failures and missing progress stop automatic continuation.
|
|
26
31
|
Provider errors/exits are surfaced to SIMY's desktop; a dead provider is not
|