@zq-silk/yui 0.14.0 → 0.14.2
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/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
|
|
2
1
|
export const FAULT_CLASSES = [
|
|
3
|
-
"provider-transient",
|
|
4
|
-
"policy-denied",
|
|
5
2
|
"session-dead",
|
|
6
3
|
"delivery-uncertain",
|
|
7
|
-
"
|
|
4
|
+
"result-missing",
|
|
5
|
+
"runtime-failure",
|
|
6
|
+
"workspace-state",
|
|
8
7
|
"review-infra",
|
|
9
|
-
"review-semantic-negative",
|
|
10
8
|
"integration-environment",
|
|
11
9
|
"integration-candidate-failure",
|
|
12
10
|
"stale-base-target-cas",
|
|
13
|
-
"archive-resource-leak",
|
|
14
11
|
"other"
|
|
15
12
|
];
|
|
16
13
|
export const NO_FAULT = Object.freeze({
|
|
@@ -18,87 +15,76 @@ export const NO_FAULT = Object.freeze({
|
|
|
18
15
|
basis: "none",
|
|
19
16
|
evidence: ""
|
|
20
17
|
});
|
|
21
|
-
const PROVIDER_TRANSIENT_PATTERN = /\b5\d{2}\b|gateway time|connection lost|server error|overloaded|rate.?limit|ECONNRESET|socket hang up|API Error|nonstream call error|mid-response/iu;
|
|
22
|
-
const POLICY_DENIED_PATTERN = /policy|permission denied|forbidden|not authorized|\b403\b/iu;
|
|
23
|
-
const SESSION_DEAD_PATTERN = /tmux session exited|session cannot be replaced|could not start|pane[^\n]*(dead|exited)|session[^\n]*(dead|exited|broken)|native session|launch reservation/iu;
|
|
24
|
-
const STORAGE_LOCK_PATTERN = /storage lock|database is locked|SQLITE_BUSY|lock timeout|COMMAND_TIMED_OUT|storage conflict|timed out waiting/iu;
|
|
25
|
-
const DELIVERY_UNCERTAIN_PATTERN = /delivery[^\n]*(uncertain|unknown)|uncertain[^\n]*delivery|push[^\n]*uncertain/iu;
|
|
26
|
-
const STALE_BASE_PATTERN = /stale (role|agent|base|target|state)|expected head|base[^\n]*(changed|moved)|conflict|CAS/iu;
|
|
27
18
|
const INTEGRATION_ENVIRONMENT_PATTERN = /tsc: not found|command not found|ENOENT|runner disappeared|runner vanished|dirty target|wrong argument|not a git repository|npm error|node: not found/iu;
|
|
28
19
|
function excerpt(text, max = 160) {
|
|
29
20
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
30
21
|
return normalized.length <= max ? normalized : `${normalized.slice(0, max)}…`;
|
|
31
22
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
* provider) always wins; otherwise the summary is matched as historical
|
|
35
|
-
* import text. Non-failed Turns return {@link NO_FAULT}.
|
|
36
|
-
*/
|
|
37
|
-
export function classifyTurnFailure(run, structured) {
|
|
23
|
+
/** Classify a failed Turn without inspecting Agent-authored output. */
|
|
24
|
+
export function classifyTurnFailure(run) {
|
|
38
25
|
if (run.status !== "failed")
|
|
39
26
|
return NO_FAULT;
|
|
40
|
-
|
|
27
|
+
const failureReason = run.result?.failureReason;
|
|
28
|
+
if (failureReason === "cancelled")
|
|
29
|
+
return NO_FAULT;
|
|
30
|
+
if (failureReason === "delivery-unknown") {
|
|
41
31
|
return {
|
|
42
|
-
faultClass:
|
|
43
|
-
basis: "
|
|
44
|
-
evidence:
|
|
32
|
+
faultClass: "delivery-uncertain",
|
|
33
|
+
basis: "core-fact",
|
|
34
|
+
evidence: failureReason
|
|
45
35
|
};
|
|
46
36
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { faultClass: "policy-denied", basis: "text-historical", evidence: text };
|
|
54
|
-
}
|
|
55
|
-
if (PROVIDER_TRANSIENT_PATTERN.test(summary)) {
|
|
56
|
-
return { faultClass: "provider-transient", basis: "text-historical", evidence: text };
|
|
57
|
-
}
|
|
58
|
-
if (STORAGE_LOCK_PATTERN.test(summary)) {
|
|
59
|
-
return { faultClass: "storage-backend-lock", basis: "text-historical", evidence: text };
|
|
60
|
-
}
|
|
61
|
-
if (DELIVERY_UNCERTAIN_PATTERN.test(summary)) {
|
|
62
|
-
return { faultClass: "delivery-uncertain", basis: "text-historical", evidence: text };
|
|
37
|
+
if (failureReason === "startup-failed") {
|
|
38
|
+
return {
|
|
39
|
+
faultClass: "session-dead",
|
|
40
|
+
basis: "core-fact",
|
|
41
|
+
evidence: failureReason
|
|
42
|
+
};
|
|
63
43
|
}
|
|
64
|
-
if (
|
|
65
|
-
return {
|
|
44
|
+
if (failureReason === "missing-result") {
|
|
45
|
+
return {
|
|
46
|
+
faultClass: "result-missing",
|
|
47
|
+
basis: "core-fact",
|
|
48
|
+
evidence: failureReason
|
|
49
|
+
};
|
|
66
50
|
}
|
|
67
|
-
if (
|
|
68
|
-
return {
|
|
51
|
+
if (failureReason === "runtime-failed") {
|
|
52
|
+
return {
|
|
53
|
+
faultClass: "runtime-failure",
|
|
54
|
+
basis: "core-fact",
|
|
55
|
+
evidence: failureReason
|
|
56
|
+
};
|
|
69
57
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* Review execution failure (the Round itself failed to execute/deliver) is
|
|
74
|
-
* `review-infra`; a completed Round with failed checks is a semantic negative.
|
|
75
|
-
*/
|
|
76
|
-
export function classifyReviewRound(round, evidence) {
|
|
77
|
-
const outcome = classifyReviewRoundOutcome(round, evidence);
|
|
78
|
-
if (outcome?.kind === "non-semantic") {
|
|
58
|
+
if (failureReason === "workspace-unavailable"
|
|
59
|
+
|| failureReason === "workspace-dirty"
|
|
60
|
+
|| failureReason === "workspace-branch-mismatch") {
|
|
79
61
|
return {
|
|
80
|
-
faultClass: "
|
|
81
|
-
basis: "
|
|
82
|
-
evidence:
|
|
62
|
+
faultClass: "workspace-state",
|
|
63
|
+
basis: "core-fact",
|
|
64
|
+
evidence: failureReason
|
|
83
65
|
};
|
|
84
66
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
return {
|
|
68
|
+
faultClass: "other",
|
|
69
|
+
basis: "core-fact",
|
|
70
|
+
evidence: failureReason ?? "failed without Core failure reason"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** ReviewRound failure is an execution fault; completed prose is not classified. */
|
|
74
|
+
export function classifyReviewRound(round) {
|
|
75
|
+
if (round.status === "failed") {
|
|
90
76
|
return {
|
|
91
|
-
faultClass: "review-
|
|
92
|
-
basis: "
|
|
93
|
-
evidence:
|
|
77
|
+
faultClass: "review-infra",
|
|
78
|
+
basis: "core-fact",
|
|
79
|
+
evidence: round.failure?.message ?? "Review execution failed without Core failure detail."
|
|
94
80
|
};
|
|
95
81
|
}
|
|
96
82
|
return NO_FAULT;
|
|
97
83
|
}
|
|
98
84
|
/**
|
|
99
85
|
* Integration failure classes: environment/toolchain failure, stale base/CAS
|
|
100
|
-
* conflict, or candidate failure. Conflict
|
|
101
|
-
*
|
|
86
|
+
* conflict, or candidate failure. Conflict/check records are Core-owned, but
|
|
87
|
+
* environment attribution still uses a regex over their diagnostic text.
|
|
102
88
|
*/
|
|
103
89
|
export function classifyIntegrationAttempt(attempt) {
|
|
104
90
|
if (attempt.status !== "failed")
|
|
@@ -106,7 +92,7 @@ export function classifyIntegrationAttempt(attempt) {
|
|
|
106
92
|
if (attempt.conflict !== undefined) {
|
|
107
93
|
return {
|
|
108
94
|
faultClass: "stale-base-target-cas",
|
|
109
|
-
basis: "
|
|
95
|
+
basis: "core-fact",
|
|
110
96
|
evidence: excerpt(attempt.conflict.summary)
|
|
111
97
|
};
|
|
112
98
|
}
|
|
@@ -116,20 +102,20 @@ export function classifyIntegrationAttempt(attempt) {
|
|
|
116
102
|
if (INTEGRATION_ENVIRONMENT_PATTERN.test(checkText)) {
|
|
117
103
|
return {
|
|
118
104
|
faultClass: "integration-environment",
|
|
119
|
-
basis: "
|
|
105
|
+
basis: "core-diagnostic",
|
|
120
106
|
evidence: excerpt(checkText)
|
|
121
107
|
};
|
|
122
108
|
}
|
|
123
109
|
if ((attempt.checks ?? []).some((c) => c.outcome === "failed")) {
|
|
124
110
|
return {
|
|
125
111
|
faultClass: "integration-candidate-failure",
|
|
126
|
-
basis: "
|
|
112
|
+
basis: "core-fact",
|
|
127
113
|
evidence: excerpt(checkText)
|
|
128
114
|
};
|
|
129
115
|
}
|
|
130
116
|
return {
|
|
131
117
|
faultClass: "other",
|
|
132
|
-
basis: "
|
|
118
|
+
basis: "core-fact",
|
|
133
119
|
evidence: "failed without checks or conflict"
|
|
134
120
|
};
|
|
135
121
|
}
|
|
@@ -138,8 +124,10 @@ export function emptyFaultClassCounts() {
|
|
|
138
124
|
}
|
|
139
125
|
export function countFaultClasses(classifications) {
|
|
140
126
|
const counts = new Map(FAULT_CLASSES.map((name) => [name, 0]));
|
|
141
|
-
for (const
|
|
142
|
-
|
|
127
|
+
for (const classification of classifications) {
|
|
128
|
+
if (classification.basis === "none")
|
|
129
|
+
continue;
|
|
130
|
+
counts.set(classification.faultClass, (counts.get(classification.faultClass) ?? 0) + 1);
|
|
143
131
|
}
|
|
144
132
|
return Object.freeze(Object.fromEntries(counts));
|
|
145
133
|
}
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import {
|
|
2
|
+
import { isCompletedReviewExecutionFromTurns } from "../review/reviewAcceptance.js";
|
|
3
3
|
import { projectFirstProgressAdvisory } from "../runtime/firstProgressAdvisory.js";
|
|
4
4
|
/** One Task's orchestration cost and advisory projection, with no writes. */
|
|
5
5
|
export function projectTaskOrchestration(facts) {
|
|
6
|
-
const evidence = {
|
|
7
|
-
listTurns: () => facts.turns,
|
|
8
|
-
listReviewFindings: () => facts.reviewFindings,
|
|
9
|
-
listEvents: () => facts.events
|
|
10
|
-
};
|
|
11
6
|
const fullRounds = facts.reviewRounds.filter((round) => round.deltaRecheck === undefined);
|
|
12
7
|
const deltaRounds = facts.reviewRounds.filter((round) => round.deltaRecheck !== undefined);
|
|
13
|
-
const classifications = new Map(facts.reviewRounds.map((round) => [round.id, classifyReviewRoundOutcome(round, evidence)]));
|
|
14
|
-
const semanticRounds = facts.reviewRounds.filter((round) => (classifications.get(round.id)?.kind === "semantic"));
|
|
15
|
-
const p1P2Findings = facts.reviewFindings.filter((finding) => ((finding.severity === "p1" || finding.severity === "p2")
|
|
16
|
-
&& semanticRounds.some((round) => round.id === finding.firstReviewRoundId))).length;
|
|
17
8
|
const candidateTimes = [
|
|
18
9
|
...facts.changeSets.map(({ createdAt }) => createdAt),
|
|
19
10
|
...facts.workItems.flatMap((item) => item.candidates
|
|
@@ -43,7 +34,7 @@ export function projectTaskOrchestration(facts) {
|
|
|
43
34
|
reviewRounds: facts.reviewRounds,
|
|
44
35
|
integrations: facts.integrations
|
|
45
36
|
});
|
|
46
|
-
const advisories = projectAdvisories(facts,
|
|
37
|
+
const advisories = projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgress.attentionRecommended);
|
|
47
38
|
const publicationAt = facts.task.completedAt === undefined
|
|
48
39
|
? undefined
|
|
49
40
|
: facts.publications
|
|
@@ -66,12 +57,7 @@ export function projectTaskOrchestration(facts) {
|
|
|
66
57
|
reviews: {
|
|
67
58
|
full: fullRounds.length,
|
|
68
59
|
delta: deltaRounds.length,
|
|
69
|
-
|
|
70
|
-
ambiguous: [...classifications.values()].filter((value) => value?.kind === "ambiguous").length,
|
|
71
|
-
p1P2Findings,
|
|
72
|
-
p1P2FindingsPerSemanticReview: semanticRounds.length === 0
|
|
73
|
-
? 0
|
|
74
|
-
: p1P2Findings / semanticRounds.length
|
|
60
|
+
failed: facts.reviewRounds.filter(({ status }) => status === "failed").length
|
|
75
61
|
},
|
|
76
62
|
integrations: {
|
|
77
63
|
attempts: facts.integrations.length,
|
|
@@ -87,7 +73,7 @@ export function projectTaskOrchestration(facts) {
|
|
|
87
73
|
advisories
|
|
88
74
|
});
|
|
89
75
|
}
|
|
90
|
-
function projectAdvisories(facts,
|
|
76
|
+
function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressAttention) {
|
|
91
77
|
const result = [];
|
|
92
78
|
if (facts.task.type === "bugfix" && facts.workItems.length > 0) {
|
|
93
79
|
result.push({
|
|
@@ -96,26 +82,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
|
|
|
96
82
|
refs: facts.workItems.map(({ id }) => `work-item:${id}`)
|
|
97
83
|
});
|
|
98
84
|
}
|
|
99
|
-
const repairItems = facts.workItems.filter((item) => (item.acceptance.some((line) => line.startsWith("review-finding:"))));
|
|
100
|
-
const byRound = new Map();
|
|
101
|
-
for (const item of repairItems) {
|
|
102
|
-
const roundIds = new Set(item.acceptance.flatMap((line) => {
|
|
103
|
-
const findingId = line.startsWith("review-finding:") ? line.slice("review-finding:".length) : "";
|
|
104
|
-
const finding = facts.reviewFindings.find(({ id }) => id === findingId);
|
|
105
|
-
return finding === undefined ? [] : [finding.firstReviewRoundId];
|
|
106
|
-
}));
|
|
107
|
-
for (const roundId of roundIds)
|
|
108
|
-
byRound.set(roundId, [...(byRound.get(roundId) ?? []), item]);
|
|
109
|
-
}
|
|
110
|
-
for (const [roundId, items] of byRound) {
|
|
111
|
-
if (items.length < 2 || hasRepairFanoutDecision(facts.decisions, roundId, items))
|
|
112
|
-
continue;
|
|
113
|
-
result.push({
|
|
114
|
-
code: "review-repair-fanout",
|
|
115
|
-
reason: `Findings from Review ${roundId} were split across ${items.length} WorkItems without a durable Decision explaining independent ownership.`,
|
|
116
|
-
refs: [`review-round:${roundId}`, ...items.map(({ id }) => `work-item:${id}`)]
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
85
|
if (repeatedIdentities > 0) {
|
|
120
86
|
result.push({
|
|
121
87
|
code: "repeated-integration-check",
|
|
@@ -125,19 +91,17 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
|
|
|
125
91
|
.map(({ id }) => `durable-job:${id}`)
|
|
126
92
|
});
|
|
127
93
|
}
|
|
128
|
-
const
|
|
94
|
+
const completedFull = fullRounds.filter((round) => (isCompletedReviewExecutionFromTurns(round, facts.turns)))
|
|
129
95
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
130
|
-
const recent =
|
|
131
|
-
const recentIds = new Set(recent.map(({ id }) => id));
|
|
132
|
-
const newFinding = facts.reviewFindings.some(({ firstReviewRoundId }) => recentIds.has(firstReviewRoundId));
|
|
96
|
+
const recent = completedFull.slice(-3);
|
|
133
97
|
const sameCandidate = recent.length === 3
|
|
134
98
|
&& recent.every((round) => isDeepStrictEqual(round.taskCandidate, recent[0].taskCandidate));
|
|
135
99
|
const sameReviewer = recent.length === 3
|
|
136
100
|
&& recent.every((round) => round.reviewerRoleName === recent[0].reviewerRoleName);
|
|
137
|
-
if (sameCandidate && sameReviewer
|
|
101
|
+
if (sameCandidate && sameReviewer) {
|
|
138
102
|
result.push({
|
|
139
103
|
code: "repeated-full-review",
|
|
140
|
-
reason: `The same Reviewer completed three full
|
|
104
|
+
reason: `The same Reviewer completed three full Reviews of the same frozen candidate; Core does not inspect their result text, so this is only a cost advisory.`,
|
|
141
105
|
refs: recent.map(({ id }) => `review-round:${id}`)
|
|
142
106
|
});
|
|
143
107
|
}
|
|
@@ -150,12 +114,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
|
|
|
150
114
|
}
|
|
151
115
|
return result;
|
|
152
116
|
}
|
|
153
|
-
function hasRepairFanoutDecision(decisions, roundId, items) {
|
|
154
|
-
return decisions.some((decision) => {
|
|
155
|
-
const text = `${decision.title}\n${decision.rationale}`;
|
|
156
|
-
return text.includes(roundId) && items.every(({ id }) => text.includes(id));
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
117
|
function terminalWorkspaceCount(facts) {
|
|
160
118
|
return facts.managedWorkspaces.filter(({ owner }) => {
|
|
161
119
|
if (owner.type === "task")
|
|
@@ -8,7 +8,7 @@ export const BUILTIN_PROFILE_IDS = Object.freeze([
|
|
|
8
8
|
export function createAgentProfile(input, now) {
|
|
9
9
|
const timestamp = now.toISOString();
|
|
10
10
|
return validateAgentProfile({
|
|
11
|
-
schemaVersion:
|
|
11
|
+
schemaVersion: 3,
|
|
12
12
|
id: requireIdentity(input.id, "Agent Profile id"),
|
|
13
13
|
revision: 1,
|
|
14
14
|
...normalizeInput(input),
|
|
@@ -24,15 +24,15 @@ export function updateAgentProfile(profile, patch, now) {
|
|
|
24
24
|
revision: profile.revision + 1,
|
|
25
25
|
updatedAt: now.toISOString()
|
|
26
26
|
};
|
|
27
|
-
for (const key of ["description", "instructions", "skills"
|
|
27
|
+
for (const key of ["description", "instructions", "skills"]) {
|
|
28
28
|
if (candidate[key] === undefined)
|
|
29
29
|
delete candidate[key];
|
|
30
30
|
}
|
|
31
31
|
return validateAgentProfile(candidate);
|
|
32
32
|
}
|
|
33
33
|
export function validateAgentProfile(profile) {
|
|
34
|
-
if (profile.schemaVersion !==
|
|
35
|
-
throw new Error("AgentProfile must use schemaVersion
|
|
34
|
+
if (profile.schemaVersion !== 3) {
|
|
35
|
+
throw new Error("AgentProfile must use schemaVersion 3.");
|
|
36
36
|
}
|
|
37
37
|
requireIdentity(profile.id, "Agent Profile id");
|
|
38
38
|
requirePositiveInteger(profile.revision, "Agent Profile revision");
|
|
@@ -43,10 +43,7 @@ export function validateAgentProfile(profile) {
|
|
|
43
43
|
optionalText(profile.instructions, "Profile instructions");
|
|
44
44
|
if (profile.skills !== undefined)
|
|
45
45
|
normalizedUniqueText(profile.skills, "Profile skill");
|
|
46
|
-
|
|
47
|
-
optionalText(profile.model, "Profile model");
|
|
48
|
-
if (profile.effort !== undefined)
|
|
49
|
-
optionalText(profile.effort, "Profile effort");
|
|
46
|
+
validateAgentProfileRuntime(profile.runtime);
|
|
50
47
|
requireTimestamp(profile.createdAt, "Agent Profile createdAt");
|
|
51
48
|
requireTimestamp(profile.updatedAt, "Agent Profile updatedAt");
|
|
52
49
|
if (Date.parse(profile.updatedAt) < Date.parse(profile.createdAt)) {
|
|
@@ -59,24 +56,28 @@ export function builtinAgentProfileInputs() {
|
|
|
59
56
|
{
|
|
60
57
|
id: "worker",
|
|
61
58
|
description: "Implement and validate one bounded delegated WorkItem.",
|
|
62
|
-
defaultAccess: "write"
|
|
59
|
+
defaultAccess: "write",
|
|
60
|
+
runtime: { source: "global-worker" }
|
|
63
61
|
},
|
|
64
62
|
{
|
|
65
63
|
id: "explorer",
|
|
66
64
|
description: "Inspect sources and return evidence without modifying them.",
|
|
67
65
|
instructions: "Do not modify files or external state.",
|
|
68
|
-
defaultAccess: "read"
|
|
66
|
+
defaultAccess: "read",
|
|
67
|
+
runtime: { source: "global-worker" }
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
70
|
id: "implementer",
|
|
72
71
|
description: "Implement and validate one bounded result.",
|
|
73
|
-
defaultAccess: "write"
|
|
72
|
+
defaultAccess: "write",
|
|
73
|
+
runtime: { source: "global-worker" }
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
id: "reviewer",
|
|
77
77
|
description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
|
|
78
78
|
instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and the Reviewer Session and physical workspace continue across changed-head Rounds without reusing an earlier verdict. The current Turn's frozen candidate remains the only scope even if Task main advances. For Delta Recheck, return equivalent-and-accepted, finding, or requires-full-review with explicit reasoning; never create the next Round. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In the Review workspace you may edit source or tests, run local checks, and optionally commit diagnostic evidence. Never push, integrate, mutate Task state, touch another workspace or stable checkout, or write the real Yui control-plane home. End the Provider turn with complete findings, checks actually run, uncertainty, and bounded next actions; Yui preserves the full free-form report automatically. Expose evidence and options to the Leader, who decides.",
|
|
79
|
-
defaultAccess: "write"
|
|
79
|
+
defaultAccess: "write",
|
|
80
|
+
runtime: { source: "global-worker" }
|
|
80
81
|
}
|
|
81
82
|
];
|
|
82
83
|
}
|
|
@@ -92,8 +93,7 @@ function normalizeInput(input) {
|
|
|
92
93
|
...(input.skills === undefined
|
|
93
94
|
? {}
|
|
94
95
|
: { skills: normalizedUniqueText(input.skills, "Profile skill") }),
|
|
95
|
-
|
|
96
|
-
...(input.effort === undefined ? {} : { effort: optionalText(input.effort, "Profile effort") })
|
|
96
|
+
runtime: normalizeRuntime(input.runtime ?? { source: "global-worker" })
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
function normalizePatch(patch) {
|
|
@@ -101,7 +101,7 @@ function normalizePatch(patch) {
|
|
|
101
101
|
if (Object.hasOwn(patch, "defaultAccess")) {
|
|
102
102
|
result.defaultAccess = validatedAccess(patch.defaultAccess);
|
|
103
103
|
}
|
|
104
|
-
for (const key of ["description", "instructions"
|
|
104
|
+
for (const key of ["description", "instructions"]) {
|
|
105
105
|
if (!Object.hasOwn(patch, key))
|
|
106
106
|
continue;
|
|
107
107
|
result[key] = patch[key] === undefined
|
|
@@ -113,8 +113,53 @@ function normalizePatch(patch) {
|
|
|
113
113
|
? undefined
|
|
114
114
|
: normalizedUniqueText(patch.skills, "Profile skill");
|
|
115
115
|
}
|
|
116
|
+
if (Object.hasOwn(patch, "runtime")) {
|
|
117
|
+
if (patch.runtime === undefined) {
|
|
118
|
+
throw new Error("Agent Profile runtime is required.");
|
|
119
|
+
}
|
|
120
|
+
result.runtime = normalizeRuntime(patch.runtime);
|
|
121
|
+
}
|
|
116
122
|
return result;
|
|
117
123
|
}
|
|
124
|
+
function normalizeRuntime(runtime) {
|
|
125
|
+
validateAgentProfileRuntime(runtime);
|
|
126
|
+
return runtime.source === "global-worker"
|
|
127
|
+
? { source: "global-worker" }
|
|
128
|
+
: {
|
|
129
|
+
source: "explicit",
|
|
130
|
+
agentId: requireIdentity(runtime.agentId, "Profile Agent id"),
|
|
131
|
+
...(runtime.model === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { model: optionalText(runtime.model, "Profile model") }),
|
|
134
|
+
...(runtime.effort === undefined
|
|
135
|
+
? {}
|
|
136
|
+
: { effort: optionalText(runtime.effort, "Profile effort") })
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function validateAgentProfileRuntime(runtime) {
|
|
140
|
+
if (runtime === null || typeof runtime !== "object" || Array.isArray(runtime)) {
|
|
141
|
+
throw new Error("Agent Profile runtime is invalid.");
|
|
142
|
+
}
|
|
143
|
+
const source = runtime.source;
|
|
144
|
+
if (source === "global-worker") {
|
|
145
|
+
if (Object.keys(runtime).some((key) => key !== "source")) {
|
|
146
|
+
throw new Error("Worker-inherited Agent Profile runtime cannot persist Agent settings.");
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (source !== "explicit") {
|
|
151
|
+
throw new Error(`Agent Profile runtime source is invalid: ${String(source)}.`);
|
|
152
|
+
}
|
|
153
|
+
if (Object.keys(runtime).some((key) => (key !== "source" && key !== "agentId" && key !== "model" && key !== "effort"))) {
|
|
154
|
+
throw new Error("Explicit Agent Profile runtime contains unsupported fields.");
|
|
155
|
+
}
|
|
156
|
+
const explicit = runtime;
|
|
157
|
+
requireIdentity(explicit.agentId, "Profile Agent id");
|
|
158
|
+
if (explicit.model !== undefined)
|
|
159
|
+
optionalText(explicit.model, "Profile model");
|
|
160
|
+
if (explicit.effort !== undefined)
|
|
161
|
+
optionalText(explicit.effort, "Profile effort");
|
|
162
|
+
}
|
|
118
163
|
function validatedAccess(value) {
|
|
119
164
|
if (value === undefined)
|
|
120
165
|
throw new Error("Agent Profile default access is required.");
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRoleAgentBinding } from "../role/role.js";
|
|
2
|
+
/** The single read model for persisted Profile intent and current runtime. */
|
|
3
|
+
export function resolveAgentProfileView(profile, store) {
|
|
4
|
+
return {
|
|
5
|
+
profile,
|
|
6
|
+
runtime: resolveAgentProfileRuntime(profile, store)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one Profile into a complete Role binding. Inherited Profiles copy
|
|
11
|
+
* the current Global Worker active binding verbatim. An explicit Profile with
|
|
12
|
+
* a matching Worker binding preserves that complete binding, whether active or
|
|
13
|
+
* dormant, and overlays its own model/effort; another Agent starts from its
|
|
14
|
+
* provider defaults.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveAgentProfileRuntime(profile, store) {
|
|
17
|
+
if (profile.runtime.source === "global-worker") {
|
|
18
|
+
const worker = store.getGlobalRole("worker");
|
|
19
|
+
if (worker === null) {
|
|
20
|
+
return {
|
|
21
|
+
status: "unavailable",
|
|
22
|
+
source: "global-worker",
|
|
23
|
+
reason: "Global Role worker is not configured."
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const binding = worker.agentBindings[worker.activeAgentId];
|
|
27
|
+
if (binding === undefined) {
|
|
28
|
+
return {
|
|
29
|
+
status: "unavailable",
|
|
30
|
+
source: "global-worker",
|
|
31
|
+
workerRevision: worker.launchRevision,
|
|
32
|
+
reason: `Global Role worker active Agent is not bound: ${worker.activeAgentId}.`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const agent = store.getConfiguredAgent(binding.agentId);
|
|
36
|
+
if (agent === null) {
|
|
37
|
+
return {
|
|
38
|
+
status: "unavailable",
|
|
39
|
+
source: "global-worker",
|
|
40
|
+
workerRevision: worker.launchRevision,
|
|
41
|
+
reason: `Global Role worker Agent is not configured: ${binding.agentId}.`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (agent.adapterId !== binding.adapterId) {
|
|
45
|
+
return {
|
|
46
|
+
status: "unavailable",
|
|
47
|
+
source: "global-worker",
|
|
48
|
+
workerRevision: worker.launchRevision,
|
|
49
|
+
reason: `Global Role worker Agent adapter does not match its binding: ${binding.agentId}.`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
status: "resolved",
|
|
54
|
+
source: "global-worker",
|
|
55
|
+
workerRevision: worker.launchRevision,
|
|
56
|
+
binding: createRoleAgentBinding(agent, binding.config)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const agent = store.getConfiguredAgent(profile.runtime.agentId);
|
|
60
|
+
if (agent === null) {
|
|
61
|
+
return {
|
|
62
|
+
status: "unavailable",
|
|
63
|
+
source: "explicit",
|
|
64
|
+
reason: `Configured Agent not found: ${profile.runtime.agentId}.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const worker = store.getGlobalRole("worker");
|
|
68
|
+
const workerBinding = worker?.agentBindings[agent.id];
|
|
69
|
+
if (workerBinding !== undefined && workerBinding.adapterId !== agent.adapterId) {
|
|
70
|
+
return {
|
|
71
|
+
status: "unavailable",
|
|
72
|
+
source: "explicit",
|
|
73
|
+
reason: `Global Role worker Agent adapter does not match its binding: ${agent.id}.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const base = workerBinding === undefined
|
|
77
|
+
? createRoleAgentBinding(agent)
|
|
78
|
+
: createRoleAgentBinding(agent, workerBinding.config);
|
|
79
|
+
const config = structuredClone(base.config);
|
|
80
|
+
if (profile.runtime.model === undefined)
|
|
81
|
+
delete config.model;
|
|
82
|
+
else
|
|
83
|
+
config.model = profile.runtime.model;
|
|
84
|
+
if (profile.runtime.effort === undefined)
|
|
85
|
+
delete config.effort;
|
|
86
|
+
else
|
|
87
|
+
config.effort = profile.runtime.effort;
|
|
88
|
+
return {
|
|
89
|
+
status: "resolved",
|
|
90
|
+
source: "explicit",
|
|
91
|
+
binding: createRoleAgentBinding(agent, config)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function requireResolvedAgentProfileRuntime(profile, store) {
|
|
95
|
+
const runtime = resolveAgentProfileRuntime(profile, store);
|
|
96
|
+
if (runtime.status === "unavailable") {
|
|
97
|
+
throw new Error(`Agent Profile ${profile.id} runtime is unavailable: ${runtime.reason}`);
|
|
98
|
+
}
|
|
99
|
+
return runtime;
|
|
100
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
-
import { accessSync, constants } from "node:fs";
|
|
4
2
|
import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
5
3
|
import { tmpdir } from "node:os";
|
|
6
|
-
import {
|
|
7
|
-
import { promisify } from "node:util";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
5
|
import { runUpdate } from "../cli/updateOrchestrator.js";
|
|
9
6
|
import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
|
|
10
7
|
import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
@@ -14,25 +11,9 @@ import { isConcreteVersion } from "../domain/validation.js";
|
|
|
14
11
|
import { resolveProject } from "../repository/project.js";
|
|
15
12
|
import { resolveVerificationGate } from "../verification/verificationGateService.js";
|
|
16
13
|
import { findL2ArtifactForCommit } from "../verification/gateArtifactStore.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { stdout, stderr } = await exec(command, args, {
|
|
21
|
-
cwd,
|
|
22
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
23
|
-
...(env === undefined ? {} : { env: { ...process.env, ...env } })
|
|
24
|
-
});
|
|
25
|
-
return { code: 0, stdout, stderr };
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
const failure = error;
|
|
29
|
-
return {
|
|
30
|
-
code: typeof failure.code === "number" ? failure.code : 1,
|
|
31
|
-
stdout: failure.stdout ?? "",
|
|
32
|
-
stderr: failure.stderr ?? failure.message ?? String(error)
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
};
|
|
14
|
+
import { createExecFileCommandRunner, createPinnedCommandRunner, resolveExecutable } from "../external/pinnedCommandRunner.js";
|
|
15
|
+
export { resolveExecutable } from "../external/pinnedCommandRunner.js";
|
|
16
|
+
const defaultRunCommand = createExecFileCommandRunner();
|
|
36
17
|
/**
|
|
37
18
|
* The external commands the production adapter shells out to. Each one is
|
|
38
19
|
* resolved to an absolute path once, at adapter construction time, so a PATH
|
|
@@ -47,24 +28,6 @@ const PINNED_EXTERNAL_COMMANDS = ["gh", "git", "npm", "tar", "sh"];
|
|
|
47
28
|
* command that cannot be resolved returns undefined. Callers must fail closed
|
|
48
29
|
* without invoking a mutable PATH fallback.
|
|
49
30
|
*/
|
|
50
|
-
export function resolveExecutable(command, environmentPath) {
|
|
51
|
-
if (isAbsolute(command))
|
|
52
|
-
return command;
|
|
53
|
-
const pathValue = environmentPath ?? "";
|
|
54
|
-
for (const directory of pathValue.split(delimiter)) {
|
|
55
|
-
if (directory.length === 0)
|
|
56
|
-
continue;
|
|
57
|
-
const candidate = resolve(directory, command);
|
|
58
|
-
try {
|
|
59
|
-
accessSync(candidate, constants.X_OK);
|
|
60
|
-
return candidate;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Keep walking PATH.
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
31
|
/**
|
|
69
32
|
* Wrap the default runner so every external command is pinned to the
|
|
70
33
|
* absolute path resolved at construction. Injected runners (tests) are used
|
|
@@ -75,33 +38,7 @@ export function resolveExecutable(command, environmentPath) {
|
|
|
75
38
|
* unresolvable for the adapter's lifetime.
|
|
76
39
|
*/
|
|
77
40
|
export function createPinnedRunner(base) {
|
|
78
|
-
|
|
79
|
-
for (const name of PINNED_EXTERNAL_COMMANDS) {
|
|
80
|
-
pinned.set(name, resolveExecutable(name, process.env.PATH));
|
|
81
|
-
}
|
|
82
|
-
return (command, args, cwd, env) => {
|
|
83
|
-
// P2 (rr24): `Map.get` returns undefined both for a missing key and for
|
|
84
|
-
// a key whose resolved value is undefined (a command that was not on
|
|
85
|
-
// PATH at construction). Use `Map.has` to distinguish them so the
|
|
86
|
-
// negative cache holds: a command resolved as missing stays missing even
|
|
87
|
-
// if it appears on PATH later in the adapter's lifetime.
|
|
88
|
-
let resolved;
|
|
89
|
-
if (pinned.has(command)) {
|
|
90
|
-
resolved = pinned.get(command);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
resolved = isAbsolute(command) ? command : resolveExecutable(command, process.env.PATH);
|
|
94
|
-
pinned.set(command, resolved);
|
|
95
|
-
}
|
|
96
|
-
if (resolved === undefined) {
|
|
97
|
-
return Promise.resolve({
|
|
98
|
-
code: 127,
|
|
99
|
-
stdout: "",
|
|
100
|
-
stderr: `Unable to resolve trusted executable: ${command}`
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return base(resolved, args, cwd, env);
|
|
104
|
-
};
|
|
41
|
+
return createPinnedCommandRunner(base, PINNED_EXTERNAL_COMMANDS);
|
|
105
42
|
}
|
|
106
43
|
/**
|
|
107
44
|
* The real adapter. It is NEVER exercised by the deterministic test suite
|