@ran-sh/dsh-crew 1.9.0 → 1.10.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/marketplace.json +16 -16
- package/.claude-plugin/plugin.json +14 -14
- package/LICENSE +21 -21
- package/README.de.md +359 -359
- package/README.es.md +359 -359
- package/README.fr.md +359 -359
- package/README.hi.md +359 -359
- package/README.id.md +359 -359
- package/README.ja.md +359 -359
- package/README.ko.md +359 -359
- package/README.md +126 -126
- package/README.pt.md +359 -359
- package/README.ru.md +359 -359
- package/README.th.md +359 -359
- package/README.tr.md +359 -359
- package/README.vi.md +359 -359
- package/README.zh-TW.md +359 -359
- package/README.zh.md +116 -116
- package/agents/ds-flash.md +24 -24
- package/agents/ds-pro.md +24 -24
- package/agents/ds-reviewer.md +23 -23
- package/agents/ds-worker.md +23 -23
- package/bin/dsh-crew.mjs +16 -16
- package/codex/agents/ds-flash.toml +34 -34
- package/codex/agents/ds-pro.toml +34 -34
- package/codex/agents/ds-reviewer.toml +32 -32
- package/codex/agents/ds-worker.toml +32 -32
- package/codex/prompts/dsh-config.md +19 -19
- package/codex/prompts/dsh-status.md +5 -5
- package/commands/config.md +23 -23
- package/commands/off.md +5 -5
- package/commands/on.md +5 -5
- package/commands/status.md +9 -9
- package/cordis.patch.yml +4 -4
- package/docs/gpt-relay-extension.md +103 -103
- package/docs/installation.md +138 -138
- package/docs/job-contracts.md +103 -103
- package/docs/readiness-matrix.md +85 -85
- package/docs/ui-surfaces.md +107 -107
- package/official-web-bridge/cordis.patch.yml +4 -4
- package/official-web-bridge/entry.mjs +1 -1
- package/official-web-bridge/overlay-entry.mjs +59 -59
- package/official-web-bridge/package.json +25 -25
- package/package.json +3 -2
- package/scripts/build-client.mjs +49 -49
- package/scripts/live-crew-smoke.mjs +39 -39
- package/scripts/live-policy-matrix.mjs +177 -177
- package/scripts/policy-probe.mjs +101 -101
- package/scripts/remove-legacy-official-bridge.ps1 +89 -89
- package/scripts/setup.mjs +393 -393
- package/scripts/smoke-real.mjs +110 -110
- package/scripts/smoke.mjs +78 -78
- package/scripts/verify-crew-ui-polish.mjs +145 -145
- package/scripts/verify-history-ui.mjs +97 -97
- package/scripts/verify-installer-fix.mjs +26 -26
- package/scripts/verify-npm-install.mjs +311 -311
- package/scripts/verify-official-bridge-e2e.mjs +192 -192
- package/src/adaptive-routing.mjs +260 -260
- package/src/client/activation-summary.tsx +64 -64
- package/src/client/collapsible-sections.mjs +55 -55
- package/src/client/history-panel.tsx +108 -108
- package/src/client/host-readiness.mjs +71 -71
- package/src/client/index.tsx +1711 -1711
- package/src/client/model-callability-view.mjs +17 -17
- package/src/client/panel-chrome.tsx +40 -40
- package/src/client/quick-entry.tsx +10 -10
- package/src/client/quick-panel.tsx +275 -275
- package/src/client/readiness-envelope.mjs +34 -34
- package/src/client/surface-detection.mjs +43 -43
- package/src/config-readiness.mjs +226 -226
- package/src/credential-reference.mjs +38 -38
- package/src/delivery.mjs +205 -205
- package/src/dsh-cli-runtime.mjs +1021 -1021
- package/src/dsh-cohort.mjs +20 -20
- package/src/extension-contract.mjs +104 -104
- package/src/failure-classification.mjs +201 -201
- package/src/history/admission-gate.mjs +67 -67
- package/src/history/archive-store.mjs +272 -272
- package/src/history/cleanup-plan.mjs +89 -89
- package/src/history/http.mjs +32 -32
- package/src/history/operation.mjs +86 -86
- package/src/history/runner-detach.mjs +34 -34
- package/src/history/runner.mjs +52 -52
- package/src/history/runtime.mjs +36 -36
- package/src/history/service.mjs +177 -177
- package/src/history/state.mjs +27 -27
- package/src/hub/entry.mjs +104 -104
- package/src/hub/index.mjs +2694 -2694
- package/src/hub-client.mjs +154 -154
- package/src/hub-compatibility.mjs +42 -42
- package/src/i18n.mjs +19 -19
- package/src/information-flow.mjs +67 -67
- package/src/install/cli.mjs +28 -28
- package/src/install/install-legacy.mjs +711 -711
- package/src/install/install.mjs +483 -483
- package/src/install/npx-lifecycle.mjs +3456 -3456
- package/src/install/official-frontend-assets.mjs +78 -78
- package/src/install/official-web.mjs +95 -95
- package/src/install/payload-content.mjs +88 -88
- package/src/install/windows-startup.mjs +236 -236
- package/src/install/windows-supervisor-adapter.mjs +443 -443
- package/src/install/windows-supervisor-lifecycle.mjs +782 -782
- package/src/install/zcode.mjs +397 -397
- package/src/job-contracts.mjs +255 -255
- package/src/local-request-guard.mjs +60 -60
- package/src/mcp-runtime.mjs +340 -340
- package/src/model-callability-contract.mjs +79 -79
- package/src/model-catalog.mjs +180 -180
- package/src/model-routing.mjs +586 -586
- package/src/model-schedule.mjs +207 -207
- package/src/official-web-bridge.mjs +447 -447
- package/src/policy.mjs +235 -235
- package/src/provider-delete-adapters.mjs +1934 -1934
- package/src/provider-health.mjs +130 -130
- package/src/provider-inventory.mjs +182 -182
- package/src/provider-layer-migration-adapters.mjs +759 -759
- package/src/provider-layer-migration.mjs +198 -198
- package/src/provider-lifecycle-state.mjs +103 -103
- package/src/provider-lifecycle.mjs +252 -252
- package/src/provider-profile-store.mjs +390 -390
- package/src/provider-settings-store.mjs +633 -633
- package/src/provider-store-lock.mjs +67 -67
- package/src/readiness-matrix.mjs +181 -181
- package/src/removable-waiter.mjs +29 -29
- package/src/role-profiles.mjs +107 -107
- package/src/runtime-controls.mjs +84 -84
- package/src/runtime-identity-contract.mjs +34 -34
- package/src/runtime-identity.mjs +235 -235
- package/src/runtime-readiness-snapshot.mjs +285 -285
- package/src/server.mjs +581 -581
- package/src/session-origins.mjs +60 -60
- package/src/standalone-sdk.mjs +23 -23
- package/src/status-shard.mjs +63 -63
- package/src/structured-error-code.mjs +38 -38
- package/src/supervisor/restart-request.mjs +256 -256
- package/src/workflow-runtime.mjs +739 -739
- package/src/workflow.mjs +155 -155
- package/src/workspace-audit.mjs +231 -231
- package/src/workspace-context.mjs +146 -146
- package/src/workspace-isolation.mjs +463 -455
- package/src/workspace-readiness.mjs +32 -32
- package/statusline/statusline.sh +14 -14
- package/statusline/worker-segment.sh +35 -35
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +1302 -1302
- package/windows/supervisor-control.ps1 +467 -467
- package/worker.cordis.yml +67 -67
- package/zcode/agents/ds-reviewer.md +31 -31
- package/zcode/agents/ds-worker.md +31 -31
- package/zcode/commands/dsh-config.md +17 -17
- package/zcode/commands/dsh-status.md +5 -5
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
// Pure, secret-free failure classification for workflow and MCP results.
|
|
2
|
-
//
|
|
3
|
-
// Classification consumes structured status/codes/outcomes only. It never
|
|
4
|
-
// parses raw exception text, provider responses, credentials, quotas or logs.
|
|
5
|
-
|
|
6
|
-
export const FAILURE_CATEGORIES = Object.freeze([
|
|
7
|
-
'none',
|
|
8
|
-
'policy',
|
|
9
|
-
'compatibility',
|
|
10
|
-
'provider',
|
|
11
|
-
'runtime',
|
|
12
|
-
'verification',
|
|
13
|
-
'cancelled',
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
export const FAILURE_REASON_CODES = Object.freeze({
|
|
17
|
-
NONE: 'NONE',
|
|
18
|
-
CANCELLED: 'CANCELLED',
|
|
19
|
-
ATTEMPT_TIMEOUT: 'ATTEMPT_TIMEOUT',
|
|
20
|
-
RUNTIME_FAILURE: 'RUNTIME_FAILURE',
|
|
21
|
-
EXECUTION_FAILED: 'EXECUTION_FAILED',
|
|
22
|
-
TESTS_FAILED: 'TESTS_FAILED',
|
|
23
|
-
TESTS_NOT_RUN: 'TESTS_NOT_RUN',
|
|
24
|
-
DELIVERY_INCOMPLETE: 'DELIVERY_INCOMPLETE',
|
|
25
|
-
WORKSPACE_MISMATCH: 'WORKSPACE_MISMATCH',
|
|
26
|
-
TASK_BLOCKED: 'TASK_BLOCKED',
|
|
27
|
-
TASK_PARTIAL: 'TASK_PARTIAL',
|
|
28
|
-
REVIEW_CHANGES_REQUESTED: 'REVIEW_CHANGES_REQUESTED',
|
|
29
|
-
REVIEW_INCONCLUSIVE: 'REVIEW_INCONCLUSIVE',
|
|
30
|
-
POLICY_REJECTED: 'POLICY_REJECTED',
|
|
31
|
-
HUB_INCOMPATIBLE: 'HUB_INCOMPATIBLE',
|
|
32
|
-
PROVIDER_UNAVAILABLE: 'PROVIDER_UNAVAILABLE',
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const PROVIDER_CODES = new Set([
|
|
36
|
-
'NO_DSH_PROVIDER_SELECTED',
|
|
37
|
-
'NO_WORKER_MODEL_AVAILABLE',
|
|
38
|
-
'MODEL_CATALOG_UNAVAILABLE',
|
|
39
|
-
'PROVIDER_CATALOG_UNAVAILABLE',
|
|
40
|
-
'PROVIDER_CATALOG_HEALTH_WARNING',
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
const POLICY_CODES = new Set([
|
|
44
|
-
'SUBAGENTS_DISABLED',
|
|
45
|
-
'TIER_DISABLED',
|
|
46
|
-
'NO_AUTO_TIER',
|
|
47
|
-
'NO_WORKER_TIER',
|
|
48
|
-
'PRO_NOT_AUTO',
|
|
49
|
-
'VISION_DISABLED',
|
|
50
|
-
'ROLE_DISABLED',
|
|
51
|
-
'ROLE_NOT_AUTO',
|
|
52
|
-
'ROLE_TIER_CONFLICT',
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
const COMPATIBILITY_CODES = new Set([
|
|
56
|
-
'HUB_UNREACHABLE',
|
|
57
|
-
'HUB_HTTP_ERROR',
|
|
58
|
-
'HUB_SERVICE_MISMATCH',
|
|
59
|
-
'HUB_PROTOCOL_MISSING',
|
|
60
|
-
'HUB_PROTOCOL_MISMATCH',
|
|
61
|
-
'HUB_CAPABILITY_MISSING',
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
const RUNTIME_CODES = new Set([
|
|
65
|
-
'ISOLATION_UNAVAILABLE',
|
|
66
|
-
'NOT_GIT_REPOSITORY',
|
|
67
|
-
'GIT_NOT_FOUND',
|
|
68
|
-
'GIT_TIMEOUT',
|
|
69
|
-
'GIT_ERROR',
|
|
70
|
-
'WORKTREE_LOCKED',
|
|
71
|
-
'WORKTREE_CREATE_FAILED',
|
|
72
|
-
'PROFILE_NOT_FOUND',
|
|
73
|
-
'PROFILE_ROLE_MISMATCH',
|
|
74
|
-
'WORKSPACE_CONTEXT_NOT_FOUND',
|
|
75
|
-
'WORKSPACE_ROOT_MISMATCH',
|
|
76
|
-
'WORKSPACE_CONTEXT_REFS_INVALID',
|
|
77
|
-
'CANDIDATE_CAPTURE_FAILED',
|
|
78
|
-
'ATTEMPT_INFRA_FAILURE',
|
|
79
|
-
'HUB_REQUEST_FAILED',
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
function normalizedCode(value) {
|
|
83
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function failureFamily(category, reasonCode, sourceCode) {
|
|
87
|
-
const code = sourceCode ?? reasonCode ?? '';
|
|
88
|
-
if (category === 'none') return 'NONE';
|
|
89
|
-
if (category === 'compatibility' || code.startsWith('HUB_')) return 'HARNESS';
|
|
90
|
-
if (category === 'provider' || code.startsWith('MODEL_') || code.startsWith('PROVIDER_')) return 'MODEL';
|
|
91
|
-
if (/^(WORK|GIT_|ISOLATION_|CANDIDATE_|PROFILE_)/.test(code)) return 'WORKSPACE';
|
|
92
|
-
if (String(reasonCode).startsWith('REVIEW_')) return 'REVIEW';
|
|
93
|
-
if (category === 'policy') return 'POLICY';
|
|
94
|
-
return 'JOB';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function failureDisposition(category, family) {
|
|
98
|
-
if (category === 'none') return 'none';
|
|
99
|
-
if (category === 'cancelled') return 'terminal';
|
|
100
|
-
if (category === 'compatibility') return 'retry';
|
|
101
|
-
if (category === 'provider') return 'fallback';
|
|
102
|
-
if (category === 'policy' || family === 'WORKSPACE' || family === 'REVIEW') return 'human';
|
|
103
|
-
if (category === 'runtime') return 'retry';
|
|
104
|
-
return 'terminal';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function result(category, reasonCode, { sourceCode = null, terminalReason = null } = {}) {
|
|
108
|
-
const family = failureFamily(category, reasonCode, sourceCode);
|
|
109
|
-
return {
|
|
110
|
-
schema_version: 1,
|
|
111
|
-
category,
|
|
112
|
-
reason_code: reasonCode,
|
|
113
|
-
...(sourceCode ? { source_code: sourceCode } : {}),
|
|
114
|
-
...(terminalReason ? { terminal_reason: terminalReason } : {}),
|
|
115
|
-
family,
|
|
116
|
-
disposition: failureDisposition(category, family),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function verificationRoot({ outcome, review } = {}) {
|
|
121
|
-
if (review?.verdict === 'request_changes') return FAILURE_REASON_CODES.REVIEW_CHANGES_REQUESTED;
|
|
122
|
-
if (review?.verdict === 'inconclusive' && review?.status === 'failed') return FAILURE_REASON_CODES.REVIEW_INCONCLUSIVE;
|
|
123
|
-
if (outcome?.tests_status === 'FAIL') return FAILURE_REASON_CODES.TESTS_FAILED;
|
|
124
|
-
if (outcome?.delivery?.complete === false) return FAILURE_REASON_CODES.DELIVERY_INCOMPLETE;
|
|
125
|
-
if (outcome?.workspace_evidence_ok === false) return FAILURE_REASON_CODES.WORKSPACE_MISMATCH;
|
|
126
|
-
if (outcome?.task_status === 'blocked') return FAILURE_REASON_CODES.TASK_BLOCKED;
|
|
127
|
-
if (outcome?.tests_status === 'NOT RUN') return FAILURE_REASON_CODES.TESTS_NOT_RUN;
|
|
128
|
-
if (outcome?.task_status === 'partial') return FAILURE_REASON_CODES.TASK_PARTIAL;
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Classify one workflow/result snapshot.
|
|
134
|
-
*
|
|
135
|
-
* Precedence is intentional:
|
|
136
|
-
* 1) cancellation is explicit user/runtime intent;
|
|
137
|
-
* 2) stable compatibility/provider/policy codes identify the failing boundary;
|
|
138
|
-
* 3) timeout/runtime execution failures outrank verification;
|
|
139
|
-
* 4) verification uses the business evidence that caused escalation/failure;
|
|
140
|
-
* 5) otherwise the result is `none`.
|
|
141
|
-
*
|
|
142
|
-
* `decision.reason` is retained only as a bounded terminal reason (for example
|
|
143
|
-
* max_attempts_reached); it never replaces the underlying verification cause.
|
|
144
|
-
*/
|
|
145
|
-
export function classifyFailure({
|
|
146
|
-
phase = null,
|
|
147
|
-
status = null,
|
|
148
|
-
errorCode = null,
|
|
149
|
-
outcome = null,
|
|
150
|
-
decision = null,
|
|
151
|
-
review = null,
|
|
152
|
-
childAttempts = [],
|
|
153
|
-
} = {}) {
|
|
154
|
-
const code = normalizedCode(errorCode);
|
|
155
|
-
const terminalReason = normalizedCode(decision?.reason);
|
|
156
|
-
|
|
157
|
-
if (phase === 'cancelled' || status === 'cancelled' || code === 'WORKFLOW_CANCELLED') {
|
|
158
|
-
return result('cancelled', FAILURE_REASON_CODES.CANCELLED, {
|
|
159
|
-
sourceCode: code === 'WORKFLOW_CANCELLED' ? code : null,
|
|
160
|
-
terminalReason,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (code && COMPATIBILITY_CODES.has(code)) {
|
|
165
|
-
return result('compatibility', code, { sourceCode: code, terminalReason });
|
|
166
|
-
}
|
|
167
|
-
if (code && PROVIDER_CODES.has(code)) {
|
|
168
|
-
return result('provider', FAILURE_REASON_CODES.PROVIDER_UNAVAILABLE, { sourceCode: code, terminalReason });
|
|
169
|
-
}
|
|
170
|
-
if (code && POLICY_CODES.has(code)) {
|
|
171
|
-
return result('policy', FAILURE_REASON_CODES.POLICY_REJECTED, { sourceCode: code, terminalReason });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const attempts = Array.isArray(childAttempts) ? childAttempts : [];
|
|
175
|
-
if (attempts.some((attempt) => attempt?.timed_out === true || attempt?.stopReason === 'timeout')) {
|
|
176
|
-
return result('runtime', FAILURE_REASON_CODES.ATTEMPT_TIMEOUT, { terminalReason });
|
|
177
|
-
}
|
|
178
|
-
if (code && RUNTIME_CODES.has(code)) {
|
|
179
|
-
return result('runtime', code, { sourceCode: code, terminalReason });
|
|
180
|
-
}
|
|
181
|
-
if (outcome?.execution_status === 'failed') {
|
|
182
|
-
return result('runtime', FAILURE_REASON_CODES.EXECUTION_FAILED, { terminalReason });
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const verification = verificationRoot({ outcome, review });
|
|
186
|
-
if (verification) return result('verification', verification, { terminalReason });
|
|
187
|
-
|
|
188
|
-
if (phase === 'failed' || status === 'failed') {
|
|
189
|
-
return result('runtime', FAILURE_REASON_CODES.RUNTIME_FAILURE, {
|
|
190
|
-
sourceCode: code,
|
|
191
|
-
terminalReason,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return result('none', FAILURE_REASON_CODES.NONE);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Classify an immediate MCP rejection before a workflow exists. */
|
|
199
|
-
export function classifyFailureCode(code) {
|
|
200
|
-
return classifyFailure({ status: 'failed', errorCode: code });
|
|
201
|
-
}
|
|
1
|
+
// Pure, secret-free failure classification for workflow and MCP results.
|
|
2
|
+
//
|
|
3
|
+
// Classification consumes structured status/codes/outcomes only. It never
|
|
4
|
+
// parses raw exception text, provider responses, credentials, quotas or logs.
|
|
5
|
+
|
|
6
|
+
export const FAILURE_CATEGORIES = Object.freeze([
|
|
7
|
+
'none',
|
|
8
|
+
'policy',
|
|
9
|
+
'compatibility',
|
|
10
|
+
'provider',
|
|
11
|
+
'runtime',
|
|
12
|
+
'verification',
|
|
13
|
+
'cancelled',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const FAILURE_REASON_CODES = Object.freeze({
|
|
17
|
+
NONE: 'NONE',
|
|
18
|
+
CANCELLED: 'CANCELLED',
|
|
19
|
+
ATTEMPT_TIMEOUT: 'ATTEMPT_TIMEOUT',
|
|
20
|
+
RUNTIME_FAILURE: 'RUNTIME_FAILURE',
|
|
21
|
+
EXECUTION_FAILED: 'EXECUTION_FAILED',
|
|
22
|
+
TESTS_FAILED: 'TESTS_FAILED',
|
|
23
|
+
TESTS_NOT_RUN: 'TESTS_NOT_RUN',
|
|
24
|
+
DELIVERY_INCOMPLETE: 'DELIVERY_INCOMPLETE',
|
|
25
|
+
WORKSPACE_MISMATCH: 'WORKSPACE_MISMATCH',
|
|
26
|
+
TASK_BLOCKED: 'TASK_BLOCKED',
|
|
27
|
+
TASK_PARTIAL: 'TASK_PARTIAL',
|
|
28
|
+
REVIEW_CHANGES_REQUESTED: 'REVIEW_CHANGES_REQUESTED',
|
|
29
|
+
REVIEW_INCONCLUSIVE: 'REVIEW_INCONCLUSIVE',
|
|
30
|
+
POLICY_REJECTED: 'POLICY_REJECTED',
|
|
31
|
+
HUB_INCOMPATIBLE: 'HUB_INCOMPATIBLE',
|
|
32
|
+
PROVIDER_UNAVAILABLE: 'PROVIDER_UNAVAILABLE',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const PROVIDER_CODES = new Set([
|
|
36
|
+
'NO_DSH_PROVIDER_SELECTED',
|
|
37
|
+
'NO_WORKER_MODEL_AVAILABLE',
|
|
38
|
+
'MODEL_CATALOG_UNAVAILABLE',
|
|
39
|
+
'PROVIDER_CATALOG_UNAVAILABLE',
|
|
40
|
+
'PROVIDER_CATALOG_HEALTH_WARNING',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const POLICY_CODES = new Set([
|
|
44
|
+
'SUBAGENTS_DISABLED',
|
|
45
|
+
'TIER_DISABLED',
|
|
46
|
+
'NO_AUTO_TIER',
|
|
47
|
+
'NO_WORKER_TIER',
|
|
48
|
+
'PRO_NOT_AUTO',
|
|
49
|
+
'VISION_DISABLED',
|
|
50
|
+
'ROLE_DISABLED',
|
|
51
|
+
'ROLE_NOT_AUTO',
|
|
52
|
+
'ROLE_TIER_CONFLICT',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const COMPATIBILITY_CODES = new Set([
|
|
56
|
+
'HUB_UNREACHABLE',
|
|
57
|
+
'HUB_HTTP_ERROR',
|
|
58
|
+
'HUB_SERVICE_MISMATCH',
|
|
59
|
+
'HUB_PROTOCOL_MISSING',
|
|
60
|
+
'HUB_PROTOCOL_MISMATCH',
|
|
61
|
+
'HUB_CAPABILITY_MISSING',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
const RUNTIME_CODES = new Set([
|
|
65
|
+
'ISOLATION_UNAVAILABLE',
|
|
66
|
+
'NOT_GIT_REPOSITORY',
|
|
67
|
+
'GIT_NOT_FOUND',
|
|
68
|
+
'GIT_TIMEOUT',
|
|
69
|
+
'GIT_ERROR',
|
|
70
|
+
'WORKTREE_LOCKED',
|
|
71
|
+
'WORKTREE_CREATE_FAILED',
|
|
72
|
+
'PROFILE_NOT_FOUND',
|
|
73
|
+
'PROFILE_ROLE_MISMATCH',
|
|
74
|
+
'WORKSPACE_CONTEXT_NOT_FOUND',
|
|
75
|
+
'WORKSPACE_ROOT_MISMATCH',
|
|
76
|
+
'WORKSPACE_CONTEXT_REFS_INVALID',
|
|
77
|
+
'CANDIDATE_CAPTURE_FAILED',
|
|
78
|
+
'ATTEMPT_INFRA_FAILURE',
|
|
79
|
+
'HUB_REQUEST_FAILED',
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function normalizedCode(value) {
|
|
83
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function failureFamily(category, reasonCode, sourceCode) {
|
|
87
|
+
const code = sourceCode ?? reasonCode ?? '';
|
|
88
|
+
if (category === 'none') return 'NONE';
|
|
89
|
+
if (category === 'compatibility' || code.startsWith('HUB_')) return 'HARNESS';
|
|
90
|
+
if (category === 'provider' || code.startsWith('MODEL_') || code.startsWith('PROVIDER_')) return 'MODEL';
|
|
91
|
+
if (/^(WORK|GIT_|ISOLATION_|CANDIDATE_|PROFILE_)/.test(code)) return 'WORKSPACE';
|
|
92
|
+
if (String(reasonCode).startsWith('REVIEW_')) return 'REVIEW';
|
|
93
|
+
if (category === 'policy') return 'POLICY';
|
|
94
|
+
return 'JOB';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function failureDisposition(category, family) {
|
|
98
|
+
if (category === 'none') return 'none';
|
|
99
|
+
if (category === 'cancelled') return 'terminal';
|
|
100
|
+
if (category === 'compatibility') return 'retry';
|
|
101
|
+
if (category === 'provider') return 'fallback';
|
|
102
|
+
if (category === 'policy' || family === 'WORKSPACE' || family === 'REVIEW') return 'human';
|
|
103
|
+
if (category === 'runtime') return 'retry';
|
|
104
|
+
return 'terminal';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function result(category, reasonCode, { sourceCode = null, terminalReason = null } = {}) {
|
|
108
|
+
const family = failureFamily(category, reasonCode, sourceCode);
|
|
109
|
+
return {
|
|
110
|
+
schema_version: 1,
|
|
111
|
+
category,
|
|
112
|
+
reason_code: reasonCode,
|
|
113
|
+
...(sourceCode ? { source_code: sourceCode } : {}),
|
|
114
|
+
...(terminalReason ? { terminal_reason: terminalReason } : {}),
|
|
115
|
+
family,
|
|
116
|
+
disposition: failureDisposition(category, family),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function verificationRoot({ outcome, review } = {}) {
|
|
121
|
+
if (review?.verdict === 'request_changes') return FAILURE_REASON_CODES.REVIEW_CHANGES_REQUESTED;
|
|
122
|
+
if (review?.verdict === 'inconclusive' && review?.status === 'failed') return FAILURE_REASON_CODES.REVIEW_INCONCLUSIVE;
|
|
123
|
+
if (outcome?.tests_status === 'FAIL') return FAILURE_REASON_CODES.TESTS_FAILED;
|
|
124
|
+
if (outcome?.delivery?.complete === false) return FAILURE_REASON_CODES.DELIVERY_INCOMPLETE;
|
|
125
|
+
if (outcome?.workspace_evidence_ok === false) return FAILURE_REASON_CODES.WORKSPACE_MISMATCH;
|
|
126
|
+
if (outcome?.task_status === 'blocked') return FAILURE_REASON_CODES.TASK_BLOCKED;
|
|
127
|
+
if (outcome?.tests_status === 'NOT RUN') return FAILURE_REASON_CODES.TESTS_NOT_RUN;
|
|
128
|
+
if (outcome?.task_status === 'partial') return FAILURE_REASON_CODES.TASK_PARTIAL;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Classify one workflow/result snapshot.
|
|
134
|
+
*
|
|
135
|
+
* Precedence is intentional:
|
|
136
|
+
* 1) cancellation is explicit user/runtime intent;
|
|
137
|
+
* 2) stable compatibility/provider/policy codes identify the failing boundary;
|
|
138
|
+
* 3) timeout/runtime execution failures outrank verification;
|
|
139
|
+
* 4) verification uses the business evidence that caused escalation/failure;
|
|
140
|
+
* 5) otherwise the result is `none`.
|
|
141
|
+
*
|
|
142
|
+
* `decision.reason` is retained only as a bounded terminal reason (for example
|
|
143
|
+
* max_attempts_reached); it never replaces the underlying verification cause.
|
|
144
|
+
*/
|
|
145
|
+
export function classifyFailure({
|
|
146
|
+
phase = null,
|
|
147
|
+
status = null,
|
|
148
|
+
errorCode = null,
|
|
149
|
+
outcome = null,
|
|
150
|
+
decision = null,
|
|
151
|
+
review = null,
|
|
152
|
+
childAttempts = [],
|
|
153
|
+
} = {}) {
|
|
154
|
+
const code = normalizedCode(errorCode);
|
|
155
|
+
const terminalReason = normalizedCode(decision?.reason);
|
|
156
|
+
|
|
157
|
+
if (phase === 'cancelled' || status === 'cancelled' || code === 'WORKFLOW_CANCELLED') {
|
|
158
|
+
return result('cancelled', FAILURE_REASON_CODES.CANCELLED, {
|
|
159
|
+
sourceCode: code === 'WORKFLOW_CANCELLED' ? code : null,
|
|
160
|
+
terminalReason,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (code && COMPATIBILITY_CODES.has(code)) {
|
|
165
|
+
return result('compatibility', code, { sourceCode: code, terminalReason });
|
|
166
|
+
}
|
|
167
|
+
if (code && PROVIDER_CODES.has(code)) {
|
|
168
|
+
return result('provider', FAILURE_REASON_CODES.PROVIDER_UNAVAILABLE, { sourceCode: code, terminalReason });
|
|
169
|
+
}
|
|
170
|
+
if (code && POLICY_CODES.has(code)) {
|
|
171
|
+
return result('policy', FAILURE_REASON_CODES.POLICY_REJECTED, { sourceCode: code, terminalReason });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const attempts = Array.isArray(childAttempts) ? childAttempts : [];
|
|
175
|
+
if (attempts.some((attempt) => attempt?.timed_out === true || attempt?.stopReason === 'timeout')) {
|
|
176
|
+
return result('runtime', FAILURE_REASON_CODES.ATTEMPT_TIMEOUT, { terminalReason });
|
|
177
|
+
}
|
|
178
|
+
if (code && RUNTIME_CODES.has(code)) {
|
|
179
|
+
return result('runtime', code, { sourceCode: code, terminalReason });
|
|
180
|
+
}
|
|
181
|
+
if (outcome?.execution_status === 'failed') {
|
|
182
|
+
return result('runtime', FAILURE_REASON_CODES.EXECUTION_FAILED, { terminalReason });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const verification = verificationRoot({ outcome, review });
|
|
186
|
+
if (verification) return result('verification', verification, { terminalReason });
|
|
187
|
+
|
|
188
|
+
if (phase === 'failed' || status === 'failed') {
|
|
189
|
+
return result('runtime', FAILURE_REASON_CODES.RUNTIME_FAILURE, {
|
|
190
|
+
sourceCode: code,
|
|
191
|
+
terminalReason,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result('none', FAILURE_REASON_CODES.NONE);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Classify an immediate MCP rejection before a workflow exists. */
|
|
199
|
+
export function classifyFailureCode(code) {
|
|
200
|
+
return classifyFailure({ status: 'failed', errorCode: code });
|
|
201
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Crew-profile-only runtime adapter. Fence the public agent creation entry,
|
|
3
|
-
* including creations still awaiting publication in the official agent list.
|
|
4
|
-
* Callers must persist their maintenance marker BEFORE asking idle().
|
|
5
|
-
* No original agent is cancelled or disposed by this adapter.
|
|
6
|
-
*/
|
|
7
|
-
const installed = new WeakSet();
|
|
8
|
-
const guarded = Symbol.for('dsh-crew.history.admission');
|
|
9
|
-
function method(object, name) {
|
|
10
|
-
for (let current = object; current; current = Object.getPrototypeOf(current)) {
|
|
11
|
-
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
|
12
|
-
if (descriptor) return descriptor.value;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Whether one registered agent currently owns a running turn.
|
|
18
|
-
*
|
|
19
|
-
* Registration is not activity: the session controller resumes a session into
|
|
20
|
-
* the registry as soon as it opens it, and leaves it there after the turn ends
|
|
21
|
-
* with `status: 'idle'`. Treating a non-empty registry as "in use" therefore
|
|
22
|
-
* wedged cleanup permanently behind ACTIVE_SESSIONS on a machine whose web UI had
|
|
23
|
-
* ever opened a session. Only a `running` status means work is actually in
|
|
24
|
-
* flight; anything else (idle, or a status an older agent does not expose) is
|
|
25
|
-
* treated as not running, and `creating > 0` remains the independent guard for a
|
|
26
|
-
* creation that has not published yet.
|
|
27
|
-
*/
|
|
28
|
-
function isRunning(agent) {
|
|
29
|
-
try { return agent?.status === 'running'; } catch { return false; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function installHistoryAdmissionGate(agents, isMaintenancePending) {
|
|
33
|
-
if (typeof agents?.create !== 'function' || typeof agents?.list !== 'function'
|
|
34
|
-
|| typeof isMaintenancePending !== 'function') throw new Error('HISTORY_ADMISSION_UNAVAILABLE');
|
|
35
|
-
if (installed.has(agents)) throw new Error('HISTORY_ADMISSION_ALREADY_INSTALLED');
|
|
36
|
-
const entries = ['create', 'resume'].filter(name => typeof method(agents, name) === 'function')
|
|
37
|
-
.map(name => ({ name, original: method(agents, name), wrapper: null }));
|
|
38
|
-
if (!entries.some(entry => entry.name === 'create')) throw new Error('HISTORY_ADMISSION_UNAVAILABLE');
|
|
39
|
-
if (entries.some(entry => entry.original[guarded])) throw new Error('HISTORY_ADMISSION_ALREADY_INSTALLED');
|
|
40
|
-
let creating = 0;
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
entry.wrapper = async function(...args) {
|
|
43
|
-
if (isMaintenancePending()) throw new Error('HISTORY_MAINTENANCE_PENDING');
|
|
44
|
-
creating += 1;
|
|
45
|
-
try { return await entry.original.apply(this, args); }
|
|
46
|
-
finally { creating -= 1; }
|
|
47
|
-
};
|
|
48
|
-
entry.wrapper[guarded] = true;
|
|
49
|
-
agents[entry.name] = entry.wrapper;
|
|
50
|
-
}
|
|
51
|
-
installed.add(agents);
|
|
52
|
-
return {
|
|
53
|
-
idle() {
|
|
54
|
-
// Cordis returns fresh traceable callable proxies; compare descriptors,
|
|
55
|
-
// not the per-access wrapper returned by agents.create/resume.
|
|
56
|
-
if (entries.some(entry => method(agents, entry.name) !== entry.wrapper)) return false;
|
|
57
|
-
try {
|
|
58
|
-
const live = agents.list();
|
|
59
|
-
return Array.isArray(live) && creating === 0 && !live.some(isRunning);
|
|
60
|
-
} catch { return false; }
|
|
61
|
-
},
|
|
62
|
-
dispose() {
|
|
63
|
-
for (const entry of entries) if (method(agents, entry.name) === entry.wrapper) agents[entry.name] = entry.original;
|
|
64
|
-
installed.delete(agents);
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Crew-profile-only runtime adapter. Fence the public agent creation entry,
|
|
3
|
+
* including creations still awaiting publication in the official agent list.
|
|
4
|
+
* Callers must persist their maintenance marker BEFORE asking idle().
|
|
5
|
+
* No original agent is cancelled or disposed by this adapter.
|
|
6
|
+
*/
|
|
7
|
+
const installed = new WeakSet();
|
|
8
|
+
const guarded = Symbol.for('dsh-crew.history.admission');
|
|
9
|
+
function method(object, name) {
|
|
10
|
+
for (let current = object; current; current = Object.getPrototypeOf(current)) {
|
|
11
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
|
12
|
+
if (descriptor) return descriptor.value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether one registered agent currently owns a running turn.
|
|
18
|
+
*
|
|
19
|
+
* Registration is not activity: the session controller resumes a session into
|
|
20
|
+
* the registry as soon as it opens it, and leaves it there after the turn ends
|
|
21
|
+
* with `status: 'idle'`. Treating a non-empty registry as "in use" therefore
|
|
22
|
+
* wedged cleanup permanently behind ACTIVE_SESSIONS on a machine whose web UI had
|
|
23
|
+
* ever opened a session. Only a `running` status means work is actually in
|
|
24
|
+
* flight; anything else (idle, or a status an older agent does not expose) is
|
|
25
|
+
* treated as not running, and `creating > 0` remains the independent guard for a
|
|
26
|
+
* creation that has not published yet.
|
|
27
|
+
*/
|
|
28
|
+
function isRunning(agent) {
|
|
29
|
+
try { return agent?.status === 'running'; } catch { return false; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function installHistoryAdmissionGate(agents, isMaintenancePending) {
|
|
33
|
+
if (typeof agents?.create !== 'function' || typeof agents?.list !== 'function'
|
|
34
|
+
|| typeof isMaintenancePending !== 'function') throw new Error('HISTORY_ADMISSION_UNAVAILABLE');
|
|
35
|
+
if (installed.has(agents)) throw new Error('HISTORY_ADMISSION_ALREADY_INSTALLED');
|
|
36
|
+
const entries = ['create', 'resume'].filter(name => typeof method(agents, name) === 'function')
|
|
37
|
+
.map(name => ({ name, original: method(agents, name), wrapper: null }));
|
|
38
|
+
if (!entries.some(entry => entry.name === 'create')) throw new Error('HISTORY_ADMISSION_UNAVAILABLE');
|
|
39
|
+
if (entries.some(entry => entry.original[guarded])) throw new Error('HISTORY_ADMISSION_ALREADY_INSTALLED');
|
|
40
|
+
let creating = 0;
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
entry.wrapper = async function(...args) {
|
|
43
|
+
if (isMaintenancePending()) throw new Error('HISTORY_MAINTENANCE_PENDING');
|
|
44
|
+
creating += 1;
|
|
45
|
+
try { return await entry.original.apply(this, args); }
|
|
46
|
+
finally { creating -= 1; }
|
|
47
|
+
};
|
|
48
|
+
entry.wrapper[guarded] = true;
|
|
49
|
+
agents[entry.name] = entry.wrapper;
|
|
50
|
+
}
|
|
51
|
+
installed.add(agents);
|
|
52
|
+
return {
|
|
53
|
+
idle() {
|
|
54
|
+
// Cordis returns fresh traceable callable proxies; compare descriptors,
|
|
55
|
+
// not the per-access wrapper returned by agents.create/resume.
|
|
56
|
+
if (entries.some(entry => method(agents, entry.name) !== entry.wrapper)) return false;
|
|
57
|
+
try {
|
|
58
|
+
const live = agents.list();
|
|
59
|
+
return Array.isArray(live) && creating === 0 && !live.some(isRunning);
|
|
60
|
+
} catch { return false; }
|
|
61
|
+
},
|
|
62
|
+
dispose() {
|
|
63
|
+
for (const entry of entries) if (method(agents, entry.name) === entry.wrapper) agents[entry.name] = entry.original;
|
|
64
|
+
installed.delete(agents);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|