@hecer/yoke 1.18.0 → 1.20.0
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +86 -910
- package/bench/RESULTS.md +30 -0
- package/bench/analyze-codex-comparison.mjs +22 -0
- package/bench/compare-codex.mjs +71 -0
- package/bench/fixtures/independent-utils/.yoke/prd.yaml +30 -0
- package/bench/fixtures/independent-utils/bench-verify.mjs +4 -0
- package/bench/fixtures/independent-utils/package.json +1 -0
- package/bench/fixtures/independent-utils/src/chunk.mjs +1 -0
- package/bench/fixtures/independent-utils/src/range.mjs +1 -0
- package/bench/fixtures/independent-utils/src/unique.mjs +1 -0
- package/bench/fixtures/independent-utils/tests/STORY-1.test.mjs +8 -0
- package/bench/fixtures/independent-utils/tests/STORY-2.test.mjs +8 -0
- package/bench/fixtures/independent-utils/tests/STORY-3.test.mjs +8 -0
- package/bench/probe-goal-integration.mjs +93 -0
- package/bench/probe-parallel-gate-context.mjs +29 -0
- package/bench/results/codex-comparison-2026-09-29.json +305 -0
- package/bench/results/goal-integration-probes-2026-09-29.json +107 -0
- package/bench/results/native-goal-validation-2026-09-30.json +122 -0
- package/bench/results/parallel-validation-2026-09-30.json +86 -0
- package/canon/manifest.yaml +1 -1
- package/dist/agents/process-record-identity.js +33 -0
- package/dist/agents/process.js +42 -11
- package/dist/agents/providers.js +11 -2
- package/dist/agents/sol-pi-runtime.js +76 -0
- package/dist/check/command.js +218 -0
- package/dist/cli.js +11 -7
- package/dist/code-intelligence/adapters/graphify.js +1 -1
- package/dist/code-intelligence/adapters/mcp.js +25 -4
- package/dist/code-intelligence/adapters/serena.js +1 -1
- package/dist/code-intelligence/coordinator.js +2 -1
- package/dist/code-intelligence/evidence.js +12 -1
- package/dist/code-intelligence/mcp-client.js +2 -2
- package/dist/dashboard/page.js +1 -1
- package/dist/dashboard/panels.js +25 -3
- package/dist/dashboard/server.js +88 -7
- package/dist/goals/codex-native.js +340 -0
- package/dist/goals/command.js +230 -29
- package/dist/loop/cleanup.js +10 -1
- package/dist/loop/dispatcher.js +82 -10
- package/dist/loop/git.js +1 -1
- package/dist/loop/parallel-adapters.js +53 -7
- package/dist/loop/parallel-command.js +8 -1
- package/dist/loop/recovery.js +81 -2
- package/dist/loop/reporter.js +3 -0
- package/dist/loop/resource-pool.js +21 -15
- package/dist/loop/run-command.js +91 -25
- package/dist/loop/run-state.js +116 -0
- package/dist/prd/explore.js +12 -4
- package/dist/retrofit/config.js +29 -0
- package/dist/retrofit/gitignore.js +3 -0
- package/docs/CODEX-COMPARISON-2026-09-29.md +27 -0
- package/docs/CONTINUOUS-EXPLORATION.md +6 -2
- package/docs/GOALS-RESOURCE-AUDIT-2026-09-29.md +92 -0
- package/docs/GOALS.md +109 -0
- package/docs/HARNESSES.md +1 -1
- package/docs/RELEASE-VALIDATION-1.20.0.md +100 -0
- package/docs/SOL-PI.md +41 -0
- package/docs/VERIFIED-PROJECTS.md +4 -4
- package/docs/code-intelligence-stdio-fix-2026-09-27.md +8 -0
- package/docs/parallel-execution.md +15 -0
- package/docs/superpowers/plans/2026-09-30-goals-resources-release.md +78 -0
- package/gemini-extension.json +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"date": "2026-09-29",
|
|
3
|
+
"auditedVersion": "1.19.0",
|
|
4
|
+
"codexVersion": "0.161.0-alpha.2",
|
|
5
|
+
"poolProbe": {
|
|
6
|
+
"fakeExecutor": true,
|
|
7
|
+
"limit": 1,
|
|
8
|
+
"executedWhilePoolFull": true,
|
|
9
|
+
"goalStatus": "complete"
|
|
10
|
+
},
|
|
11
|
+
"budgetProbe": {
|
|
12
|
+
"fakeExecutor": true,
|
|
13
|
+
"tokenBudget": 1,
|
|
14
|
+
"measuredTokens": 100,
|
|
15
|
+
"timeBudgetMs": 60,
|
|
16
|
+
"totalWallMs": 652,
|
|
17
|
+
"recordedAgentMs": 3,
|
|
18
|
+
"status": "complete"
|
|
19
|
+
},
|
|
20
|
+
"objectiveProbe": {
|
|
21
|
+
"requestedArtifact": "new-feature.txt",
|
|
22
|
+
"requestedArtifactExists": false,
|
|
23
|
+
"acceptance": "only checks old-feature.txt",
|
|
24
|
+
"executorCalled": false,
|
|
25
|
+
"attempts": 0,
|
|
26
|
+
"status": "complete"
|
|
27
|
+
},
|
|
28
|
+
"nativeProtocolProbe": {
|
|
29
|
+
"modelDevelopmentTurnRequested": false,
|
|
30
|
+
"archived": true,
|
|
31
|
+
"calls": [
|
|
32
|
+
{
|
|
33
|
+
"method": "thread/goal/set",
|
|
34
|
+
"requestedStatus": null,
|
|
35
|
+
"returnedStatus": "active",
|
|
36
|
+
"tokenBudget": 1000,
|
|
37
|
+
"tokensUsed": 0
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"method": "thread/goal/get",
|
|
41
|
+
"requestedStatus": null,
|
|
42
|
+
"returnedStatus": "active",
|
|
43
|
+
"tokenBudget": 1000,
|
|
44
|
+
"tokensUsed": 0
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"method": "thread/goal/set",
|
|
48
|
+
"requestedStatus": "paused",
|
|
49
|
+
"returnedStatus": "paused",
|
|
50
|
+
"tokenBudget": 1000,
|
|
51
|
+
"tokensUsed": 0
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"method": "thread/goal/get",
|
|
55
|
+
"requestedStatus": null,
|
|
56
|
+
"returnedStatus": "paused",
|
|
57
|
+
"tokenBudget": 1000,
|
|
58
|
+
"tokensUsed": 0
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"method": "thread/goal/clear",
|
|
62
|
+
"requestedStatus": null,
|
|
63
|
+
"returnedStatus": null,
|
|
64
|
+
"tokenBudget": null,
|
|
65
|
+
"tokensUsed": null,
|
|
66
|
+
"cleared": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"method": "thread/goal/get",
|
|
70
|
+
"requestedStatus": null,
|
|
71
|
+
"returnedStatus": null,
|
|
72
|
+
"tokenBudget": null,
|
|
73
|
+
"tokensUsed": null
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"parallelGateProbe": [
|
|
78
|
+
{
|
|
79
|
+
"fakeAgentsAndGit": true,
|
|
80
|
+
"requiresStoryContext": false,
|
|
81
|
+
"exitCode": 0,
|
|
82
|
+
"observedContexts": [
|
|
83
|
+
"A",
|
|
84
|
+
"B",
|
|
85
|
+
"C",
|
|
86
|
+
null,
|
|
87
|
+
null,
|
|
88
|
+
null
|
|
89
|
+
],
|
|
90
|
+
"acceptedStories": 3
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"fakeAgentsAndGit": true,
|
|
94
|
+
"requiresStoryContext": true,
|
|
95
|
+
"exitCode": 1,
|
|
96
|
+
"observedContexts": [
|
|
97
|
+
"A",
|
|
98
|
+
"B",
|
|
99
|
+
"C",
|
|
100
|
+
null,
|
|
101
|
+
null,
|
|
102
|
+
null
|
|
103
|
+
],
|
|
104
|
+
"acceptedStories": 0
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"validationDate": "2026-09-30",
|
|
4
|
+
"generatedBy": "Codex-assisted validation",
|
|
5
|
+
"codexVersion": "0.161.0-alpha.2",
|
|
6
|
+
"model": "gpt-6.1-sol",
|
|
7
|
+
"reasoningEffort": "low",
|
|
8
|
+
"evidence": "Protected executable acceptance commands, saved provider counters, and shared capacity snapshots.",
|
|
9
|
+
"cases": {
|
|
10
|
+
"nativeBareStartup": {
|
|
11
|
+
"status": "blocked",
|
|
12
|
+
"limitation": "Codex app-server does not support ignoring user config. Native bare mode is now rejected before spawn."
|
|
13
|
+
},
|
|
14
|
+
"nativeBeforeWindowsShellFix": {
|
|
15
|
+
"status": "blocked",
|
|
16
|
+
"artifactCorrect": true,
|
|
17
|
+
"limitation": "Windows Store PowerShell repeatedly failed sandbox command launch; the two-minute agent deadline interrupted the turn and its usage remained unknown."
|
|
18
|
+
},
|
|
19
|
+
"retainedThreadResume": {
|
|
20
|
+
"status": "complete",
|
|
21
|
+
"sameGoalId": true,
|
|
22
|
+
"sameNativeThread": true,
|
|
23
|
+
"attemptsBefore": 1,
|
|
24
|
+
"attemptsAfter": 1,
|
|
25
|
+
"independentCheckStatus": "passed",
|
|
26
|
+
"explicitDisposableBudgetOverride": {
|
|
27
|
+
"clearTokenBudget": true,
|
|
28
|
+
"reason": "Historical interrupted turn had unknown usage; retained as unknown, previous report preserved"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"nativeAfterShellFixBeforeAccountingFix": {
|
|
32
|
+
"status": "complete",
|
|
33
|
+
"independentCheckStatus": "passed",
|
|
34
|
+
"reportedLastIncrement": {
|
|
35
|
+
"inputTokens": 23419,
|
|
36
|
+
"outputTokens": 60
|
|
37
|
+
},
|
|
38
|
+
"savedProviderCumulativeTotals": {
|
|
39
|
+
"inputTokens": 133408,
|
|
40
|
+
"outputTokens": 780,
|
|
41
|
+
"cachedInputTokens": 111232,
|
|
42
|
+
"reasoningOutputTokens": 56
|
|
43
|
+
},
|
|
44
|
+
"limitation": "This historical run initially counted only the last model request. The saved provider totals exposed the error; this report preserves both measurements."
|
|
45
|
+
},
|
|
46
|
+
"nativeAfterAccountingFix": {
|
|
47
|
+
"status": "complete",
|
|
48
|
+
"attempts": 1,
|
|
49
|
+
"attemptSuccess": true,
|
|
50
|
+
"nativeThreadBound": true,
|
|
51
|
+
"artifactCorrect": true,
|
|
52
|
+
"independentCheckStatus": "passed",
|
|
53
|
+
"agentDurationMs": 82634,
|
|
54
|
+
"wallDurationMs": 89855,
|
|
55
|
+
"limits": {
|
|
56
|
+
"maxAttempts": 1,
|
|
57
|
+
"agentMinutes": 5,
|
|
58
|
+
"wallMinutes": 7,
|
|
59
|
+
"tokenBudget": 300000
|
|
60
|
+
},
|
|
61
|
+
"measuredTurnUsage": {
|
|
62
|
+
"inputTokens": 153736,
|
|
63
|
+
"outputTokens": 606,
|
|
64
|
+
"cachedInputTokens": 134528,
|
|
65
|
+
"cacheWriteInputTokens": 0,
|
|
66
|
+
"reasoningOutputTokens": 53
|
|
67
|
+
},
|
|
68
|
+
"durableUsageBaselineMatchesTurnUsage": true,
|
|
69
|
+
"nativeGoalCounter": {
|
|
70
|
+
"tokensUsed": 0,
|
|
71
|
+
"interpretation": "The paused native goal counter remains separate from measured model usage."
|
|
72
|
+
},
|
|
73
|
+
"resume": {
|
|
74
|
+
"status": "complete",
|
|
75
|
+
"attempts": 1,
|
|
76
|
+
"sameGoalId": true,
|
|
77
|
+
"sameNativeThread": true
|
|
78
|
+
},
|
|
79
|
+
"cleanup": {
|
|
80
|
+
"remainingProviderRecords": 0,
|
|
81
|
+
"activeModelUnits": 0,
|
|
82
|
+
"activeCheckUnits": 0
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"sharedCapacity": {
|
|
86
|
+
"fakeExecutor": true,
|
|
87
|
+
"limit": 1,
|
|
88
|
+
"queuedWhilePoolFull": true,
|
|
89
|
+
"executedWhilePoolFull": false,
|
|
90
|
+
"goalStatus": "complete"
|
|
91
|
+
},
|
|
92
|
+
"budgetOverrun": {
|
|
93
|
+
"fakeExecutor": true,
|
|
94
|
+
"tokenBudget": 1,
|
|
95
|
+
"measuredTokens": 100,
|
|
96
|
+
"timeBudgetMs": 60,
|
|
97
|
+
"totalWallMs": 1208,
|
|
98
|
+
"recordedAgentMs": 30,
|
|
99
|
+
"status": "blocked"
|
|
100
|
+
},
|
|
101
|
+
"unboundObjective": {
|
|
102
|
+
"requestedArtifact": "new-feature.txt",
|
|
103
|
+
"requestedArtifactExists": false,
|
|
104
|
+
"acceptance": "only checks old-feature.txt; intentionally unbound objective",
|
|
105
|
+
"executorCalled": false,
|
|
106
|
+
"attempts": 0,
|
|
107
|
+
"status": "blocked"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"accounting": {
|
|
111
|
+
"source": "thread/tokenUsage/updated.tokenUsage.total",
|
|
112
|
+
"method": "Subtract the durable same-thread cumulative baseline; new threads begin at zero. Repeated absolute snapshots are not added again.",
|
|
113
|
+
"unknownUsage": "Missing or malformed snapshots remain unknown. Decreasing counters mark the reused binding untrusted until a fresh thread is explicitly selected.",
|
|
114
|
+
"reference": "https://github.com/openai/symphony/blob/main/elixir/docs/token_accounting.md"
|
|
115
|
+
},
|
|
116
|
+
"limits": [
|
|
117
|
+
"These are bounded disposable smoke cases, not a long-running recovery or provider parity benchmark.",
|
|
118
|
+
"Native Codex goals remain experimental and opt-in; method-unavailable fallback does not hide authentication, network, or provider errors.",
|
|
119
|
+
"Native goals stay paused during explicit Yoke turns; independent executable checks determine Yoke completion.",
|
|
120
|
+
"Privacy exports omit thread identifiers, local root paths, and raw transcripts."
|
|
121
|
+
]
|
|
122
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"date": "2026-09-30",
|
|
4
|
+
"purpose": "Single-arm parallel acceptance smoke validation; no baseline or efficiency comparison",
|
|
5
|
+
"aiDisclosure": "AI assisted report; authenticated AI providers implemented work inside disposable benchmark fixtures.",
|
|
6
|
+
"source": {
|
|
7
|
+
"version": "1.20.0",
|
|
8
|
+
"commit": "05fac3db1a51412563005e07f80e6e5a9aab8e06",
|
|
9
|
+
"state": "Uncommitted validation worktree; built before execution"
|
|
10
|
+
},
|
|
11
|
+
"fixture": "independent-utils",
|
|
12
|
+
"model": "gpt-6.1-sol",
|
|
13
|
+
"reasoningEffort": "low",
|
|
14
|
+
"arm": "yoke-parallel",
|
|
15
|
+
"parallelWorkers": 3,
|
|
16
|
+
"maxIterations": 3,
|
|
17
|
+
"routing": false,
|
|
18
|
+
"nativeMultiAgent": false,
|
|
19
|
+
"acceptanceDigest": "3e9734e6d286b7c9d19e6ce473eea17e1adf9e30a6ee8927c7240170602f14f7",
|
|
20
|
+
"samples": [
|
|
21
|
+
{
|
|
22
|
+
"sample": "initial-runtime-claims-failure",
|
|
23
|
+
"root": "<disposable-initial-smoke>",
|
|
24
|
+
"buildContext": "Before production runtime claim exclusions were added",
|
|
25
|
+
"version": "1.20.0",
|
|
26
|
+
"exitCode": 1,
|
|
27
|
+
"status": "blocked",
|
|
28
|
+
"accepted": false,
|
|
29
|
+
"durationMs": 148237,
|
|
30
|
+
"immutableAcceptanceDurationMs": 245,
|
|
31
|
+
"stories": { "total": 3, "accepted": 0, "rejectedIntegration": 3 },
|
|
32
|
+
"immutableTests": { "total": 15, "passed": 0, "failed": 15, "cancelled": 0, "skipped": 0 },
|
|
33
|
+
"usage": {
|
|
34
|
+
"inputTokens": 646600,
|
|
35
|
+
"cachedInputTokens": 571264,
|
|
36
|
+
"cacheWriteInputTokens": 0,
|
|
37
|
+
"outputTokens": 3796,
|
|
38
|
+
"reasoningOutputTokens": 91,
|
|
39
|
+
"costUsd": null
|
|
40
|
+
},
|
|
41
|
+
"measurement": { "measuredCalls": 3, "unknownCalls": 0, "unmeasuredAttempts": 3, "costAvailable": "unknown" },
|
|
42
|
+
"failure": {
|
|
43
|
+
"classification": "Runtime claim files missing from Git cleanliness exclusions",
|
|
44
|
+
"integrationReason": "target working tree is not clean",
|
|
45
|
+
"immutableReplayReason": "No implementations integrated; original source stubs remained unimplemented",
|
|
46
|
+
"diagnostic": "Built realGitOps.isClean returned true without a claim file and false with a temporary diagnostic claim file in the completed disposable fixture.",
|
|
47
|
+
"retainedCandidates": 3,
|
|
48
|
+
"modelCandidateQuality": "Not independently established by the final root replay"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"sample": "corrected-runtime-claims-smoke",
|
|
53
|
+
"root": "<disposable-corrected-smoke>",
|
|
54
|
+
"buildContext": "Production claim and worktree runtime exclusions added; rebuilt before execution",
|
|
55
|
+
"version": "1.20.0",
|
|
56
|
+
"exitCode": 0,
|
|
57
|
+
"status": "complete",
|
|
58
|
+
"accepted": true,
|
|
59
|
+
"durationMs": 112949,
|
|
60
|
+
"immutableAcceptanceDurationMs": 257,
|
|
61
|
+
"stories": { "total": 3, "accepted": 3, "rejectedIntegration": 0 },
|
|
62
|
+
"immutableTests": { "total": 15, "passed": 15, "failed": 0, "cancelled": 0, "skipped": 0, "nodeTestDurationMs": 159.3043 },
|
|
63
|
+
"immutableProtectedFiles": { "total": 4, "byteIdenticalToSeedAfterReplay": true },
|
|
64
|
+
"usage": {
|
|
65
|
+
"inputTokens": 456659,
|
|
66
|
+
"cachedInputTokens": 372096,
|
|
67
|
+
"cacheWriteInputTokens": 0,
|
|
68
|
+
"outputTokens": 3217,
|
|
69
|
+
"reasoningOutputTokens": 64,
|
|
70
|
+
"costUsd": null
|
|
71
|
+
},
|
|
72
|
+
"measurement": { "measuredCalls": 3, "unknownCalls": 0, "unmeasuredAttempts": 3, "costAvailable": "unknown" }
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"sampleLimits": [
|
|
76
|
+
"One initial failed parallel sample and one corrected parallel sample; each executed once",
|
|
77
|
+
"No baseline or serial arm; no general efficiency conclusion",
|
|
78
|
+
"Small fixture with three independent stories and fifteen visible tests",
|
|
79
|
+
"Immutable original verification files restored and replayed after provider execution",
|
|
80
|
+
"CPU and RAM not measured; machine load not controlled",
|
|
81
|
+
"Fixed model and low effort; routing and native multi-agent delegation disabled",
|
|
82
|
+
"User config disabled; installed plugins and host skill discovery not exhaustively audited",
|
|
83
|
+
"Reported token counters retained as observed; unknown cost is not zero cost",
|
|
84
|
+
"Private prompts, transcripts, identifiers and local root paths excluded; raw logs retained outside Git"
|
|
85
|
+
]
|
|
86
|
+
}
|
package/canon/manifest.yaml
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, renameSync, existsSync, rmSync } from 'node:fs';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { processIncarnation } from './process-incarnation.js';
|
|
5
|
+
/** Resolve ownership without blocking cancellation timers on a slow CIM query. */
|
|
6
|
+
export function trackProcessRecordIdentity(record, child) {
|
|
7
|
+
let cancelled = false;
|
|
8
|
+
const update = (identity) => {
|
|
9
|
+
if (!identity || cancelled || child.exitCode !== null || child.signalCode !== null || !record || !existsSync(record.path))
|
|
10
|
+
return;
|
|
11
|
+
const temporary = `${record.path}.${randomUUID()}.tmp`;
|
|
12
|
+
try {
|
|
13
|
+
writeFileSync(temporary, JSON.stringify({ version: record.version, owner: record.owner, targetDir: record.targetDir, childPid: record.childPid, startedAt: identity, workerId: record.workerId }), { flag: 'wx', mode: 0o600 });
|
|
14
|
+
renameSync(temporary, record.path);
|
|
15
|
+
}
|
|
16
|
+
catch { /* unknown identity remains conservative */ }
|
|
17
|
+
finally {
|
|
18
|
+
try {
|
|
19
|
+
rmSync(temporary, { force: true });
|
|
20
|
+
}
|
|
21
|
+
catch { /* stale temporary is diagnostic evidence */ }
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
if (!record || !child.pid)
|
|
25
|
+
return () => { };
|
|
26
|
+
if (process.platform !== 'win32') {
|
|
27
|
+
update(processIncarnation(child.pid));
|
|
28
|
+
return () => { cancelled = true; };
|
|
29
|
+
}
|
|
30
|
+
const query = execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', '(Get-CimInstance Win32_Process -Filter "ProcessId = $env:YOKE_PROCESS_PID").CreationDate'], { env: { ...process.env, YOKE_PROCESS_PID: String(child.pid) }, windowsHide: true, timeout: 5000, maxBuffer: 4096 }, (error, stdout) => update(!error && stdout.trim() ? `win32:${stdout.trim()}` : undefined));
|
|
31
|
+
return () => { cancelled = true; if (query.exitCode === null && query.signalCode === null)
|
|
32
|
+
query.kill('SIGKILL'); };
|
|
33
|
+
}
|
package/dist/agents/process.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createBoundedOutput, createTelemetryAccumulator } from './process-strea
|
|
|
6
6
|
import { processIncarnation } from './process-incarnation.js';
|
|
7
7
|
import { prepareWindowsInvocation, resolveWindowsCommand } from './windows-launch.js';
|
|
8
8
|
import { createSupervision, supervisionLimits, assertPreviousProvidersStopped } from './supervision.js';
|
|
9
|
+
import { prepareSolPiTemporaryConfig } from './sol-pi-runtime.js';
|
|
9
10
|
function cancellationReason(signal) {
|
|
10
11
|
return typeof signal.reason === 'string' && signal.reason.length > 0
|
|
11
12
|
? signal.reason
|
|
@@ -26,25 +27,48 @@ export function startProviderProcess(agent, invocation, options = {}) {
|
|
|
26
27
|
let progress = () => { };
|
|
27
28
|
const limits = supervisionLimits(invocation.cwd);
|
|
28
29
|
const supervision = createSupervision(invocation.cwd, reason => failure(reason), () => progress(), options.attempt);
|
|
30
|
+
let restoreSolPiConfig = () => { };
|
|
29
31
|
let prepared;
|
|
30
32
|
try {
|
|
33
|
+
if (agent === 'pi')
|
|
34
|
+
restoreSolPiConfig = prepareSolPiTemporaryConfig(invocation.cwd);
|
|
31
35
|
assertPreviousProvidersStopped(invocation.cwd);
|
|
32
36
|
prepared = process.platform === 'win32' ? prepareWindowsInvocation(invocation) : { command: invocation.command, args: invocation.args, env: process.env };
|
|
33
37
|
}
|
|
34
38
|
catch (error) {
|
|
35
|
-
|
|
39
|
+
let message = error.message;
|
|
40
|
+
try {
|
|
41
|
+
restoreSolPiConfig();
|
|
42
|
+
}
|
|
43
|
+
catch (cleanupError) {
|
|
44
|
+
message = cleanupError.message;
|
|
45
|
+
}
|
|
36
46
|
supervision.stop(message);
|
|
37
47
|
return { pid: undefined, invocation, recordPath: '', cancel: () => false, completion: Promise.resolve({ kind: 'spawn-failed', error: message, invocation, pid: undefined, stdout: '', stderr: '', stdoutTruncated: false, stderrTruncated: false, telemetry: { usageAvailable: false } }) };
|
|
38
48
|
}
|
|
39
49
|
const spawnOptions = { ...prepared, cwd: invocation.cwd, shell: false, detached: process.platform !== 'win32' };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
let child;
|
|
51
|
+
try {
|
|
52
|
+
child = spawn(spawnOptions.command, [...spawnOptions.args], {
|
|
53
|
+
cwd: spawnOptions.cwd,
|
|
54
|
+
shell: spawnOptions.shell,
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
|
+
detached: spawnOptions.detached,
|
|
57
|
+
env: prepared.env,
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
let message = error.message;
|
|
63
|
+
try {
|
|
64
|
+
restoreSolPiConfig();
|
|
65
|
+
}
|
|
66
|
+
catch (cleanupError) {
|
|
67
|
+
message = cleanupError.message;
|
|
68
|
+
}
|
|
69
|
+
supervision.stop(message);
|
|
70
|
+
return { pid: undefined, invocation, recordPath: '', cancel: () => false, completion: Promise.resolve({ kind: 'spawn-failed', error: message, invocation, pid: undefined, stdout: '', stderr: '', stdoutTruncated: false, stderrTruncated: false, telemetry: { usageAvailable: false } }) };
|
|
71
|
+
}
|
|
48
72
|
const targetDir = resolve(invocation.cwd);
|
|
49
73
|
const pid = child.pid;
|
|
50
74
|
const startedAt = pid === undefined ? `unverified:${new Date().toISOString()}` : processIncarnation(pid) ?? `unverified:${new Date().toISOString()}`;
|
|
@@ -130,11 +154,18 @@ export function startProviderProcess(agent, invocation, options = {}) {
|
|
|
130
154
|
return;
|
|
131
155
|
settled = true;
|
|
132
156
|
clearTimers();
|
|
133
|
-
|
|
157
|
+
let completedResult = result;
|
|
158
|
+
try {
|
|
159
|
+
restoreSolPiConfig();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
completedResult = { ...evidence(), kind: 'spawn-failed', error: error.message };
|
|
163
|
+
}
|
|
164
|
+
supervision.stop(termination?.reason ?? (completedResult.kind === 'succeeded' ? 'provider-exited' : 'provider-failed'), !termination || terminationConfirmed);
|
|
134
165
|
options.signal?.removeEventListener('abort', onAbort);
|
|
135
166
|
if (!termination || terminationConfirmed)
|
|
136
167
|
removeRecord();
|
|
137
|
-
resolveCompletion(
|
|
168
|
+
resolveCompletion(completedResult);
|
|
138
169
|
};
|
|
139
170
|
const evidence = () => ({
|
|
140
171
|
invocation,
|
package/dist/agents/providers.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ModelSelectionSchema } from './contracts.js';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { loadSolPiProjectConfig } from './sol-pi-runtime.js';
|
|
4
|
+
const SOL_PI_EXTENSION = 'git:github.com/NVlabs/SoL-Pi@d7ecfc089944f0d04b80122a0a9a6ca0d786f3d0';
|
|
3
5
|
export { providerSpawnOptions, startProviderProcess, } from './process.js';
|
|
4
6
|
const argsFor = (agent, permissions) => {
|
|
5
7
|
if (agent === 'claude') {
|
|
@@ -76,6 +78,13 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
76
78
|
if (parsedSelection.variant && !['opencode', 'kilo', 'pi', 'hermes'].includes(agent))
|
|
77
79
|
throw new Error(`${agent} does not support a model variant selection`);
|
|
78
80
|
const args = argsFor(agent, permissions);
|
|
81
|
+
if (agent === 'pi' && loadSolPiProjectConfig(cwd)?.solpi?.enabled) {
|
|
82
|
+
const [major, minor] = process.version.slice(1).split('.').map(Number);
|
|
83
|
+
if (major < 22 || (major === 22 && minor < 19)) {
|
|
84
|
+
throw new Error(`SoL-Pi requires Node.js 22.19 or newer; current version is ${process.version}`);
|
|
85
|
+
}
|
|
86
|
+
args.push('--extension', SOL_PI_EXTENSION);
|
|
87
|
+
}
|
|
79
88
|
if (output.schemaFile !== undefined || output.jsonSchema !== undefined) {
|
|
80
89
|
if (agent === 'codex' && output.schemaFile && output.jsonSchema === undefined) {
|
|
81
90
|
if (/[\0\r\n]/u.test(output.schemaFile) || (process.platform === 'win32' && !/^[A-Za-z0-9_./:\\-]+$/u.test(output.schemaFile)))
|
|
@@ -119,7 +128,7 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
119
128
|
else if (agent === 'pi')
|
|
120
129
|
args.push('--thinking', parsedSelection.reasoningEffort);
|
|
121
130
|
else if (agent === 'hermes')
|
|
122
|
-
args.push('--reasoning
|
|
131
|
+
args.push('--reasoning', parsedSelection.reasoningEffort);
|
|
123
132
|
}
|
|
124
133
|
if (parsedSelection.variant) {
|
|
125
134
|
if ((agent === 'opencode' || agent === 'kilo') && !parsedSelection.reasoningEffort)
|
|
@@ -134,7 +143,7 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
134
143
|
if (parsedSelection.reasoningEffort && parsedSelection.reasoningEffort !== parsedSelection.variant)
|
|
135
144
|
throw new Error('Hermes reasoningEffort and variant selections must match');
|
|
136
145
|
if (!parsedSelection.reasoningEffort)
|
|
137
|
-
args.push('--reasoning
|
|
146
|
+
args.push('--reasoning', parsedSelection.variant);
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
if (agent === 'codex' && parsedSelection.nativeMultiAgent === false)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { lstatSync, mkdirSync, readFileSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { loadConfig, toSolPiNativeConfig } from '../retrofit/config.js';
|
|
4
|
+
export function loadSolPiProjectConfig(directory) {
|
|
5
|
+
let current = resolve(directory);
|
|
6
|
+
while (true) {
|
|
7
|
+
const config = loadConfig(current);
|
|
8
|
+
if (config)
|
|
9
|
+
return config;
|
|
10
|
+
const parent = dirname(current);
|
|
11
|
+
if (parent === current)
|
|
12
|
+
return null;
|
|
13
|
+
current = parent;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function lstatIfPresent(path) {
|
|
17
|
+
try {
|
|
18
|
+
return lstatSync(path);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code === 'ENOENT')
|
|
22
|
+
return undefined;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function assertNoConfigSymlinks(paths) {
|
|
27
|
+
for (const path of paths) {
|
|
28
|
+
if (lstatIfPresent(path)?.isSymbolicLink())
|
|
29
|
+
throw new Error('SoL-Pi refuses symbolic-link config paths: ' + path);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function removeCreatedDirectories(directories) {
|
|
33
|
+
for (const directory of [...directories].reverse()) {
|
|
34
|
+
try {
|
|
35
|
+
rmdirSync(directory);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const code = error.code;
|
|
39
|
+
if (code !== 'ENOENT' && code !== 'ENOTEMPTY')
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function prepareSolPiTemporaryConfig(workspace) {
|
|
45
|
+
const config = loadSolPiProjectConfig(workspace);
|
|
46
|
+
if (!config?.solpi?.enabled)
|
|
47
|
+
return () => { };
|
|
48
|
+
const piDirectory = resolve(workspace, '.pi');
|
|
49
|
+
const solPiConfigPath = join(piDirectory, 'sol-pi.json');
|
|
50
|
+
const configPaths = [piDirectory, solPiConfigPath];
|
|
51
|
+
assertNoConfigSymlinks(configPaths);
|
|
52
|
+
const existingConfig = lstatIfPresent(solPiConfigPath) ? readFileSync(solPiConfigPath) : undefined;
|
|
53
|
+
const createdDirectories = [];
|
|
54
|
+
const restore = () => {
|
|
55
|
+
assertNoConfigSymlinks(configPaths);
|
|
56
|
+
if (existingConfig === undefined)
|
|
57
|
+
rmSync(solPiConfigPath, { force: true });
|
|
58
|
+
else
|
|
59
|
+
writeFileSync(solPiConfigPath, existingConfig);
|
|
60
|
+
removeCreatedDirectories(createdDirectories);
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
for (const directory of [piDirectory]) {
|
|
64
|
+
if (!lstatIfPresent(directory)) {
|
|
65
|
+
mkdirSync(directory);
|
|
66
|
+
createdDirectories.push(directory);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(solPiConfigPath, JSON.stringify(toSolPiNativeConfig(config)), { flag: existingConfig === undefined ? 'wx' : 'w' });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
restore();
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
return restore;
|
|
76
|
+
}
|