@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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +16 -16
  2. package/.claude-plugin/plugin.json +14 -14
  3. package/LICENSE +21 -21
  4. package/README.de.md +359 -359
  5. package/README.es.md +359 -359
  6. package/README.fr.md +359 -359
  7. package/README.hi.md +359 -359
  8. package/README.id.md +359 -359
  9. package/README.ja.md +359 -359
  10. package/README.ko.md +359 -359
  11. package/README.md +126 -126
  12. package/README.pt.md +359 -359
  13. package/README.ru.md +359 -359
  14. package/README.th.md +359 -359
  15. package/README.tr.md +359 -359
  16. package/README.vi.md +359 -359
  17. package/README.zh-TW.md +359 -359
  18. package/README.zh.md +116 -116
  19. package/agents/ds-flash.md +24 -24
  20. package/agents/ds-pro.md +24 -24
  21. package/agents/ds-reviewer.md +23 -23
  22. package/agents/ds-worker.md +23 -23
  23. package/bin/dsh-crew.mjs +16 -16
  24. package/codex/agents/ds-flash.toml +34 -34
  25. package/codex/agents/ds-pro.toml +34 -34
  26. package/codex/agents/ds-reviewer.toml +32 -32
  27. package/codex/agents/ds-worker.toml +32 -32
  28. package/codex/prompts/dsh-config.md +19 -19
  29. package/codex/prompts/dsh-status.md +5 -5
  30. package/commands/config.md +23 -23
  31. package/commands/off.md +5 -5
  32. package/commands/on.md +5 -5
  33. package/commands/status.md +9 -9
  34. package/cordis.patch.yml +4 -4
  35. package/docs/gpt-relay-extension.md +103 -103
  36. package/docs/installation.md +138 -138
  37. package/docs/job-contracts.md +103 -103
  38. package/docs/readiness-matrix.md +85 -85
  39. package/docs/ui-surfaces.md +107 -107
  40. package/official-web-bridge/cordis.patch.yml +4 -4
  41. package/official-web-bridge/entry.mjs +1 -1
  42. package/official-web-bridge/overlay-entry.mjs +59 -59
  43. package/official-web-bridge/package.json +25 -25
  44. package/package.json +3 -2
  45. package/scripts/build-client.mjs +49 -49
  46. package/scripts/live-crew-smoke.mjs +39 -39
  47. package/scripts/live-policy-matrix.mjs +177 -177
  48. package/scripts/policy-probe.mjs +101 -101
  49. package/scripts/remove-legacy-official-bridge.ps1 +89 -89
  50. package/scripts/setup.mjs +393 -393
  51. package/scripts/smoke-real.mjs +110 -110
  52. package/scripts/smoke.mjs +78 -78
  53. package/scripts/verify-crew-ui-polish.mjs +145 -145
  54. package/scripts/verify-history-ui.mjs +97 -97
  55. package/scripts/verify-installer-fix.mjs +26 -26
  56. package/scripts/verify-npm-install.mjs +311 -311
  57. package/scripts/verify-official-bridge-e2e.mjs +192 -192
  58. package/src/adaptive-routing.mjs +260 -260
  59. package/src/client/activation-summary.tsx +64 -64
  60. package/src/client/collapsible-sections.mjs +55 -55
  61. package/src/client/history-panel.tsx +108 -108
  62. package/src/client/host-readiness.mjs +71 -71
  63. package/src/client/index.tsx +1711 -1711
  64. package/src/client/model-callability-view.mjs +17 -17
  65. package/src/client/panel-chrome.tsx +40 -40
  66. package/src/client/quick-entry.tsx +10 -10
  67. package/src/client/quick-panel.tsx +275 -275
  68. package/src/client/readiness-envelope.mjs +34 -34
  69. package/src/client/surface-detection.mjs +43 -43
  70. package/src/config-readiness.mjs +226 -226
  71. package/src/credential-reference.mjs +38 -38
  72. package/src/delivery.mjs +205 -205
  73. package/src/dsh-cli-runtime.mjs +1021 -1021
  74. package/src/dsh-cohort.mjs +20 -20
  75. package/src/extension-contract.mjs +104 -104
  76. package/src/failure-classification.mjs +201 -201
  77. package/src/history/admission-gate.mjs +67 -67
  78. package/src/history/archive-store.mjs +272 -272
  79. package/src/history/cleanup-plan.mjs +89 -89
  80. package/src/history/http.mjs +32 -32
  81. package/src/history/operation.mjs +86 -86
  82. package/src/history/runner-detach.mjs +34 -34
  83. package/src/history/runner.mjs +52 -52
  84. package/src/history/runtime.mjs +36 -36
  85. package/src/history/service.mjs +177 -177
  86. package/src/history/state.mjs +27 -27
  87. package/src/hub/entry.mjs +104 -104
  88. package/src/hub/index.mjs +2694 -2694
  89. package/src/hub-client.mjs +154 -154
  90. package/src/hub-compatibility.mjs +42 -42
  91. package/src/i18n.mjs +19 -19
  92. package/src/information-flow.mjs +67 -67
  93. package/src/install/cli.mjs +28 -28
  94. package/src/install/install-legacy.mjs +711 -711
  95. package/src/install/install.mjs +483 -483
  96. package/src/install/npx-lifecycle.mjs +3456 -3456
  97. package/src/install/official-frontend-assets.mjs +78 -78
  98. package/src/install/official-web.mjs +95 -95
  99. package/src/install/payload-content.mjs +88 -88
  100. package/src/install/windows-startup.mjs +236 -236
  101. package/src/install/windows-supervisor-adapter.mjs +443 -443
  102. package/src/install/windows-supervisor-lifecycle.mjs +782 -782
  103. package/src/install/zcode.mjs +397 -397
  104. package/src/job-contracts.mjs +255 -255
  105. package/src/local-request-guard.mjs +60 -60
  106. package/src/mcp-runtime.mjs +340 -340
  107. package/src/model-callability-contract.mjs +79 -79
  108. package/src/model-catalog.mjs +180 -180
  109. package/src/model-routing.mjs +586 -586
  110. package/src/model-schedule.mjs +207 -207
  111. package/src/official-web-bridge.mjs +447 -447
  112. package/src/policy.mjs +235 -235
  113. package/src/provider-delete-adapters.mjs +1934 -1934
  114. package/src/provider-health.mjs +130 -130
  115. package/src/provider-inventory.mjs +182 -182
  116. package/src/provider-layer-migration-adapters.mjs +759 -759
  117. package/src/provider-layer-migration.mjs +198 -198
  118. package/src/provider-lifecycle-state.mjs +103 -103
  119. package/src/provider-lifecycle.mjs +252 -252
  120. package/src/provider-profile-store.mjs +390 -390
  121. package/src/provider-settings-store.mjs +633 -633
  122. package/src/provider-store-lock.mjs +67 -67
  123. package/src/readiness-matrix.mjs +181 -181
  124. package/src/removable-waiter.mjs +29 -29
  125. package/src/role-profiles.mjs +107 -107
  126. package/src/runtime-controls.mjs +84 -84
  127. package/src/runtime-identity-contract.mjs +34 -34
  128. package/src/runtime-identity.mjs +235 -235
  129. package/src/runtime-readiness-snapshot.mjs +285 -285
  130. package/src/server.mjs +581 -581
  131. package/src/session-origins.mjs +60 -60
  132. package/src/standalone-sdk.mjs +23 -23
  133. package/src/status-shard.mjs +63 -63
  134. package/src/structured-error-code.mjs +38 -38
  135. package/src/supervisor/restart-request.mjs +256 -256
  136. package/src/workflow-runtime.mjs +739 -739
  137. package/src/workflow.mjs +155 -155
  138. package/src/workspace-audit.mjs +231 -231
  139. package/src/workspace-context.mjs +146 -146
  140. package/src/workspace-isolation.mjs +463 -455
  141. package/src/workspace-readiness.mjs +32 -32
  142. package/statusline/statusline.sh +14 -14
  143. package/statusline/worker-segment.sh +35 -35
  144. package/windows/start-dsh-crew.cmd +57 -57
  145. package/windows/start-dsh-crew.ps1 +1302 -1302
  146. package/windows/supervisor-control.ps1 +467 -467
  147. package/worker.cordis.yml +67 -67
  148. package/zcode/agents/ds-reviewer.md +31 -31
  149. package/zcode/agents/ds-worker.md +31 -31
  150. package/zcode/commands/dsh-config.md +17 -17
  151. 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
+ }