@ran-sh/dsh-crew 0.3.6 → 0.3.7

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 (78) hide show
  1. package/.claude-plugin/plugin.json +8 -8
  2. package/.mcp.json +8 -8
  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 +125 -180
  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 +125 -180
  19. package/agents/ds-flash.md +26 -26
  20. package/agents/ds-pro.md +32 -32
  21. package/agents/ds-reviewer.md +23 -23
  22. package/agents/ds-worker.md +22 -22
  23. package/codex/agents/ds-flash.toml +30 -30
  24. package/codex/agents/ds-pro.toml +31 -31
  25. package/codex/agents/ds-reviewer.toml +28 -28
  26. package/codex/agents/ds-worker.toml +28 -28
  27. package/codex/prompts/dsh-config.md +3 -3
  28. package/codex/prompts/dsh-status.md +1 -1
  29. package/commands/config.md +11 -11
  30. package/commands/off.md +5 -5
  31. package/commands/on.md +5 -5
  32. package/commands/status.md +5 -5
  33. package/cordis.patch.yml +4 -4
  34. package/lib/client.js +3446 -2765
  35. package/package.json +131 -131
  36. package/scripts/build-client.mjs +28 -28
  37. package/scripts/live-crew-smoke.mjs +39 -39
  38. package/scripts/live-policy-matrix.mjs +177 -177
  39. package/scripts/policy-probe.mjs +101 -101
  40. package/scripts/setup.mjs +284 -284
  41. package/scripts/smoke-real.mjs +110 -110
  42. package/scripts/smoke.mjs +78 -78
  43. package/scripts/verify-installer-fix.mjs +26 -26
  44. package/src/adaptive-routing.mjs +260 -260
  45. package/src/client/activation-summary.tsx +64 -64
  46. package/src/client/collapsible-sections.mjs +55 -0
  47. package/src/client/entry.tsx +236 -236
  48. package/src/client/index.tsx +1213 -1120
  49. package/src/config-readiness.mjs +59 -59
  50. package/src/delivery.mjs +205 -205
  51. package/src/dsh-cli-runtime.mjs +239 -239
  52. package/src/failure-classification.mjs +172 -172
  53. package/src/hub/entry.mjs +98 -98
  54. package/src/hub-client.mjs +132 -132
  55. package/src/hub-compatibility.mjs +49 -49
  56. package/src/i18n.mjs +19 -19
  57. package/src/install/cli.mjs +28 -28
  58. package/src/install/install-legacy.mjs +462 -462
  59. package/src/install/install.mjs +451 -451
  60. package/src/install/npx-lifecycle.mjs +1055 -1055
  61. package/src/mcp-runtime.mjs +257 -257
  62. package/src/model-catalog.mjs +173 -173
  63. package/src/model-routing.mjs +391 -391
  64. package/src/policy.mjs +197 -197
  65. package/src/readiness-matrix.mjs +169 -169
  66. package/src/runtime-controls.mjs +90 -90
  67. package/src/runtime-identity.mjs +108 -108
  68. package/src/server.mjs +477 -477
  69. package/src/status-shard.mjs +52 -52
  70. package/src/structured-error-code.mjs +38 -38
  71. package/src/vision-route.mjs +138 -138
  72. package/src/workflow-runtime.mjs +573 -573
  73. package/src/workflow.mjs +160 -160
  74. package/src/workspace-audit.mjs +231 -231
  75. package/src/workspace-isolation.mjs +365 -365
  76. package/statusline/statusline.sh +14 -14
  77. package/statusline/worker-segment.sh +35 -35
  78. package/worker.cordis.yml +77 -77
@@ -1,172 +1,172 @@
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
- 'CANDIDATE_CAPTURE_FAILED',
73
- 'ATTEMPT_INFRA_FAILURE',
74
- 'HUB_REQUEST_FAILED',
75
- ]);
76
-
77
- function normalizedCode(value) {
78
- return typeof value === 'string' && value.trim() ? value.trim() : null;
79
- }
80
-
81
- function result(category, reasonCode, { sourceCode = null, terminalReason = null } = {}) {
82
- return {
83
- schema_version: 1,
84
- category,
85
- reason_code: reasonCode,
86
- ...(sourceCode ? { source_code: sourceCode } : {}),
87
- ...(terminalReason ? { terminal_reason: terminalReason } : {}),
88
- };
89
- }
90
-
91
- function verificationRoot({ outcome, review } = {}) {
92
- if (review?.verdict === 'request_changes') return FAILURE_REASON_CODES.REVIEW_CHANGES_REQUESTED;
93
- if (review?.verdict === 'inconclusive' && review?.status === 'failed') return FAILURE_REASON_CODES.REVIEW_INCONCLUSIVE;
94
- if (outcome?.tests_status === 'FAIL') return FAILURE_REASON_CODES.TESTS_FAILED;
95
- if (outcome?.delivery?.complete === false) return FAILURE_REASON_CODES.DELIVERY_INCOMPLETE;
96
- if (outcome?.workspace_evidence_ok === false) return FAILURE_REASON_CODES.WORKSPACE_MISMATCH;
97
- if (outcome?.task_status === 'blocked') return FAILURE_REASON_CODES.TASK_BLOCKED;
98
- if (outcome?.tests_status === 'NOT RUN') return FAILURE_REASON_CODES.TESTS_NOT_RUN;
99
- if (outcome?.task_status === 'partial') return FAILURE_REASON_CODES.TASK_PARTIAL;
100
- return null;
101
- }
102
-
103
- /**
104
- * Classify one workflow/result snapshot.
105
- *
106
- * Precedence is intentional:
107
- * 1) cancellation is explicit user/runtime intent;
108
- * 2) stable compatibility/provider/policy codes identify the failing boundary;
109
- * 3) timeout/runtime execution failures outrank verification;
110
- * 4) verification uses the business evidence that caused escalation/failure;
111
- * 5) otherwise the result is `none`.
112
- *
113
- * `decision.reason` is retained only as a bounded terminal reason (for example
114
- * max_attempts_reached); it never replaces the underlying verification cause.
115
- */
116
- export function classifyFailure({
117
- phase = null,
118
- status = null,
119
- errorCode = null,
120
- outcome = null,
121
- decision = null,
122
- review = null,
123
- childAttempts = [],
124
- } = {}) {
125
- const code = normalizedCode(errorCode);
126
- const terminalReason = normalizedCode(decision?.reason);
127
-
128
- if (phase === 'cancelled' || status === 'cancelled' || code === 'WORKFLOW_CANCELLED') {
129
- return result('cancelled', FAILURE_REASON_CODES.CANCELLED, {
130
- sourceCode: code === 'WORKFLOW_CANCELLED' ? code : null,
131
- terminalReason,
132
- });
133
- }
134
-
135
- if (code && COMPATIBILITY_CODES.has(code)) {
136
- return result('compatibility', code, { sourceCode: code, terminalReason });
137
- }
138
- if (code && PROVIDER_CODES.has(code)) {
139
- return result('provider', FAILURE_REASON_CODES.PROVIDER_UNAVAILABLE, { sourceCode: code, terminalReason });
140
- }
141
- if (code && POLICY_CODES.has(code)) {
142
- return result('policy', FAILURE_REASON_CODES.POLICY_REJECTED, { sourceCode: code, terminalReason });
143
- }
144
-
145
- const attempts = Array.isArray(childAttempts) ? childAttempts : [];
146
- if (attempts.some((attempt) => attempt?.timed_out === true || attempt?.stopReason === 'timeout')) {
147
- return result('runtime', FAILURE_REASON_CODES.ATTEMPT_TIMEOUT, { terminalReason });
148
- }
149
- if (code && RUNTIME_CODES.has(code)) {
150
- return result('runtime', code, { sourceCode: code, terminalReason });
151
- }
152
- if (outcome?.execution_status === 'failed') {
153
- return result('runtime', FAILURE_REASON_CODES.EXECUTION_FAILED, { terminalReason });
154
- }
155
-
156
- const verification = verificationRoot({ outcome, review });
157
- if (verification) return result('verification', verification, { terminalReason });
158
-
159
- if (phase === 'failed' || status === 'failed') {
160
- return result('runtime', FAILURE_REASON_CODES.RUNTIME_FAILURE, {
161
- sourceCode: code,
162
- terminalReason,
163
- });
164
- }
165
-
166
- return result('none', FAILURE_REASON_CODES.NONE);
167
- }
168
-
169
- /** Classify an immediate MCP rejection before a workflow exists. */
170
- export function classifyFailureCode(code) {
171
- return classifyFailure({ status: 'failed', errorCode: code });
172
- }
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
+ 'CANDIDATE_CAPTURE_FAILED',
73
+ 'ATTEMPT_INFRA_FAILURE',
74
+ 'HUB_REQUEST_FAILED',
75
+ ]);
76
+
77
+ function normalizedCode(value) {
78
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
79
+ }
80
+
81
+ function result(category, reasonCode, { sourceCode = null, terminalReason = null } = {}) {
82
+ return {
83
+ schema_version: 1,
84
+ category,
85
+ reason_code: reasonCode,
86
+ ...(sourceCode ? { source_code: sourceCode } : {}),
87
+ ...(terminalReason ? { terminal_reason: terminalReason } : {}),
88
+ };
89
+ }
90
+
91
+ function verificationRoot({ outcome, review } = {}) {
92
+ if (review?.verdict === 'request_changes') return FAILURE_REASON_CODES.REVIEW_CHANGES_REQUESTED;
93
+ if (review?.verdict === 'inconclusive' && review?.status === 'failed') return FAILURE_REASON_CODES.REVIEW_INCONCLUSIVE;
94
+ if (outcome?.tests_status === 'FAIL') return FAILURE_REASON_CODES.TESTS_FAILED;
95
+ if (outcome?.delivery?.complete === false) return FAILURE_REASON_CODES.DELIVERY_INCOMPLETE;
96
+ if (outcome?.workspace_evidence_ok === false) return FAILURE_REASON_CODES.WORKSPACE_MISMATCH;
97
+ if (outcome?.task_status === 'blocked') return FAILURE_REASON_CODES.TASK_BLOCKED;
98
+ if (outcome?.tests_status === 'NOT RUN') return FAILURE_REASON_CODES.TESTS_NOT_RUN;
99
+ if (outcome?.task_status === 'partial') return FAILURE_REASON_CODES.TASK_PARTIAL;
100
+ return null;
101
+ }
102
+
103
+ /**
104
+ * Classify one workflow/result snapshot.
105
+ *
106
+ * Precedence is intentional:
107
+ * 1) cancellation is explicit user/runtime intent;
108
+ * 2) stable compatibility/provider/policy codes identify the failing boundary;
109
+ * 3) timeout/runtime execution failures outrank verification;
110
+ * 4) verification uses the business evidence that caused escalation/failure;
111
+ * 5) otherwise the result is `none`.
112
+ *
113
+ * `decision.reason` is retained only as a bounded terminal reason (for example
114
+ * max_attempts_reached); it never replaces the underlying verification cause.
115
+ */
116
+ export function classifyFailure({
117
+ phase = null,
118
+ status = null,
119
+ errorCode = null,
120
+ outcome = null,
121
+ decision = null,
122
+ review = null,
123
+ childAttempts = [],
124
+ } = {}) {
125
+ const code = normalizedCode(errorCode);
126
+ const terminalReason = normalizedCode(decision?.reason);
127
+
128
+ if (phase === 'cancelled' || status === 'cancelled' || code === 'WORKFLOW_CANCELLED') {
129
+ return result('cancelled', FAILURE_REASON_CODES.CANCELLED, {
130
+ sourceCode: code === 'WORKFLOW_CANCELLED' ? code : null,
131
+ terminalReason,
132
+ });
133
+ }
134
+
135
+ if (code && COMPATIBILITY_CODES.has(code)) {
136
+ return result('compatibility', code, { sourceCode: code, terminalReason });
137
+ }
138
+ if (code && PROVIDER_CODES.has(code)) {
139
+ return result('provider', FAILURE_REASON_CODES.PROVIDER_UNAVAILABLE, { sourceCode: code, terminalReason });
140
+ }
141
+ if (code && POLICY_CODES.has(code)) {
142
+ return result('policy', FAILURE_REASON_CODES.POLICY_REJECTED, { sourceCode: code, terminalReason });
143
+ }
144
+
145
+ const attempts = Array.isArray(childAttempts) ? childAttempts : [];
146
+ if (attempts.some((attempt) => attempt?.timed_out === true || attempt?.stopReason === 'timeout')) {
147
+ return result('runtime', FAILURE_REASON_CODES.ATTEMPT_TIMEOUT, { terminalReason });
148
+ }
149
+ if (code && RUNTIME_CODES.has(code)) {
150
+ return result('runtime', code, { sourceCode: code, terminalReason });
151
+ }
152
+ if (outcome?.execution_status === 'failed') {
153
+ return result('runtime', FAILURE_REASON_CODES.EXECUTION_FAILED, { terminalReason });
154
+ }
155
+
156
+ const verification = verificationRoot({ outcome, review });
157
+ if (verification) return result('verification', verification, { terminalReason });
158
+
159
+ if (phase === 'failed' || status === 'failed') {
160
+ return result('runtime', FAILURE_REASON_CODES.RUNTIME_FAILURE, {
161
+ sourceCode: code,
162
+ terminalReason,
163
+ });
164
+ }
165
+
166
+ return result('none', FAILURE_REASON_CODES.NONE);
167
+ }
168
+
169
+ /** Classify an immediate MCP rejection before a workflow exists. */
170
+ export function classifyFailureCode(code) {
171
+ return classifyFailure({ status: 'failed', errorCode: code });
172
+ }
package/src/hub/entry.mjs CHANGED
@@ -1,98 +1,98 @@
1
- // Thin DSH plugin entry wrapper for v0.3 runtime compatibility discovery.
2
- //
3
- // The legacy /ping endpoint stays reachability-only inside index.mjs. This
4
- // wrapper adds a separate /runtime endpoint carrying the explicit Hub/MCP
5
- // compatibility contract, then delegates all existing Hub behavior unchanged.
6
- // It also owns the v0.3 adaptive-routing observer so the large legacy Hub body
7
- // stays untouched: only jobs that explicitly opted into adaptive routing feed
8
- // bounded process-local health for later selections.
9
-
10
- import { apply as applyHub, inject, name, WorkerRegistry } from './index.mjs';
11
- import { getHubRuntimeIdentity } from '../runtime-identity.mjs';
12
- import { getProcessAdaptiveHealthStore } from '../adaptive-routing.mjs';
13
-
14
- const RUNTIME_PATH = '/_dsh/dsh-crew/runtime';
15
- const ADAPTIVE_OBSERVER_INSTALLED = Symbol.for('@ran-sh/dsh-crew/adaptive-observer-installed');
16
- const ADAPTIVE_JOB_OBSERVED = Symbol.for('@ran-sh/dsh-crew/adaptive-job-observed');
17
-
18
- export { inject, name };
19
-
20
- function sendJson(res, status, value) {
21
- const body = JSON.stringify(value);
22
- res.writeHead(status, {
23
- 'content-type': 'application/json; charset=utf-8',
24
- 'content-length': Buffer.byteLength(body),
25
- 'cache-control': 'no-store',
26
- 'x-content-type-options': 'nosniff',
27
- 'cross-origin-resource-policy': 'same-origin',
28
- });
29
- res.end(body);
30
- }
31
-
32
- export function registerRuntimeEndpoint(ctx) {
33
- return ctx.inject(['webServer'], (webCtx) => webCtx.webServer.register({
34
- kind: 'exact',
35
- path: RUNTIME_PATH,
36
- handler: (_req, res) => sendJson(res, 200, { ok: true, ...getHubRuntimeIdentity() }),
37
- }));
38
- }
39
-
40
- /**
41
- * Record only an opt-in adaptive Hub attempt. The store accepts bounded
42
- * provider/model identifiers plus outcome/latency; arbitrary job errors,
43
- * credentials, quota and provider payloads never cross this boundary.
44
- */
45
- export function recordAdaptiveJobOutcome(job, store = getProcessAdaptiveHealthStore()) {
46
- if (job?.selection_trace?.adaptive?.enabled !== true) return false;
47
- const started = Date.parse(job.startedAt ?? '');
48
- const ended = Date.parse(job.endedAt ?? '');
49
- const latencyMs = Number.isFinite(started) && Number.isFinite(ended)
50
- ? Math.max(0, ended - started)
51
- : undefined;
52
- return store.record(
53
- { provider: job.provider, model: job.model },
54
- {
55
- role: job.role,
56
- status: job.status,
57
- stopReason: job.stopReason,
58
- latencyMs,
59
- },
60
- );
61
- }
62
-
63
- /**
64
- * Wrap WorkerRegistry.spawn once per process. Selection itself still lives in
65
- * the existing Hub implementation; rankAdaptiveCandidates reads the same
66
- * process-local store through adaptive-routing.mjs. Replacing job.promise with
67
- * a chained promise preserves all legacy completion/waiter behavior and runs
68
- * this observer only after the Hub's own finalizer populated endedAt/status.
69
- */
70
- export function installAdaptiveHealthObserver() {
71
- if (WorkerRegistry.prototype[ADAPTIVE_OBSERVER_INSTALLED] === true) return false;
72
- const originalSpawn = WorkerRegistry.prototype.spawn;
73
- Object.defineProperty(WorkerRegistry.prototype, ADAPTIVE_OBSERVER_INSTALLED, {
74
- value: true,
75
- enumerable: false,
76
- configurable: false,
77
- });
78
- WorkerRegistry.prototype.spawn = async function observedAdaptiveSpawn(...args) {
79
- const job = await originalSpawn.apply(this, args);
80
- if (!job || job[ADAPTIVE_JOB_OBSERVED] === true || typeof job.promise?.finally !== 'function') return job;
81
- Object.defineProperty(job, ADAPTIVE_JOB_OBSERVED, {
82
- value: true,
83
- enumerable: false,
84
- configurable: false,
85
- });
86
- job.promise = job.promise.finally(() => {
87
- recordAdaptiveJobOutcome(job);
88
- });
89
- return job;
90
- };
91
- return true;
92
- }
93
-
94
- export async function apply(ctx) {
95
- registerRuntimeEndpoint(ctx);
96
- installAdaptiveHealthObserver();
97
- return applyHub(ctx);
98
- }
1
+ // Thin DSH plugin entry wrapper for v0.3 runtime compatibility discovery.
2
+ //
3
+ // The legacy /ping endpoint stays reachability-only inside index.mjs. This
4
+ // wrapper adds a separate /runtime endpoint carrying the explicit Hub/MCP
5
+ // compatibility contract, then delegates all existing Hub behavior unchanged.
6
+ // It also owns the v0.3 adaptive-routing observer so the large legacy Hub body
7
+ // stays untouched: only jobs that explicitly opted into adaptive routing feed
8
+ // bounded process-local health for later selections.
9
+
10
+ import { apply as applyHub, inject, name, WorkerRegistry } from './index.mjs';
11
+ import { getHubRuntimeIdentity } from '../runtime-identity.mjs';
12
+ import { getProcessAdaptiveHealthStore } from '../adaptive-routing.mjs';
13
+
14
+ const RUNTIME_PATH = '/_dsh/dsh-crew/runtime';
15
+ const ADAPTIVE_OBSERVER_INSTALLED = Symbol.for('@ran-sh/dsh-crew/adaptive-observer-installed');
16
+ const ADAPTIVE_JOB_OBSERVED = Symbol.for('@ran-sh/dsh-crew/adaptive-job-observed');
17
+
18
+ export { inject, name };
19
+
20
+ function sendJson(res, status, value) {
21
+ const body = JSON.stringify(value);
22
+ res.writeHead(status, {
23
+ 'content-type': 'application/json; charset=utf-8',
24
+ 'content-length': Buffer.byteLength(body),
25
+ 'cache-control': 'no-store',
26
+ 'x-content-type-options': 'nosniff',
27
+ 'cross-origin-resource-policy': 'same-origin',
28
+ });
29
+ res.end(body);
30
+ }
31
+
32
+ export function registerRuntimeEndpoint(ctx) {
33
+ return ctx.inject(['webServer'], (webCtx) => webCtx.webServer.register({
34
+ kind: 'exact',
35
+ path: RUNTIME_PATH,
36
+ handler: (_req, res) => sendJson(res, 200, { ok: true, ...getHubRuntimeIdentity() }),
37
+ }));
38
+ }
39
+
40
+ /**
41
+ * Record only an opt-in adaptive Hub attempt. The store accepts bounded
42
+ * provider/model identifiers plus outcome/latency; arbitrary job errors,
43
+ * credentials, quota and provider payloads never cross this boundary.
44
+ */
45
+ export function recordAdaptiveJobOutcome(job, store = getProcessAdaptiveHealthStore()) {
46
+ if (job?.selection_trace?.adaptive?.enabled !== true) return false;
47
+ const started = Date.parse(job.startedAt ?? '');
48
+ const ended = Date.parse(job.endedAt ?? '');
49
+ const latencyMs = Number.isFinite(started) && Number.isFinite(ended)
50
+ ? Math.max(0, ended - started)
51
+ : undefined;
52
+ return store.record(
53
+ { provider: job.provider, model: job.model },
54
+ {
55
+ role: job.role,
56
+ status: job.status,
57
+ stopReason: job.stopReason,
58
+ latencyMs,
59
+ },
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Wrap WorkerRegistry.spawn once per process. Selection itself still lives in
65
+ * the existing Hub implementation; rankAdaptiveCandidates reads the same
66
+ * process-local store through adaptive-routing.mjs. Replacing job.promise with
67
+ * a chained promise preserves all legacy completion/waiter behavior and runs
68
+ * this observer only after the Hub's own finalizer populated endedAt/status.
69
+ */
70
+ export function installAdaptiveHealthObserver() {
71
+ if (WorkerRegistry.prototype[ADAPTIVE_OBSERVER_INSTALLED] === true) return false;
72
+ const originalSpawn = WorkerRegistry.prototype.spawn;
73
+ Object.defineProperty(WorkerRegistry.prototype, ADAPTIVE_OBSERVER_INSTALLED, {
74
+ value: true,
75
+ enumerable: false,
76
+ configurable: false,
77
+ });
78
+ WorkerRegistry.prototype.spawn = async function observedAdaptiveSpawn(...args) {
79
+ const job = await originalSpawn.apply(this, args);
80
+ if (!job || job[ADAPTIVE_JOB_OBSERVED] === true || typeof job.promise?.finally !== 'function') return job;
81
+ Object.defineProperty(job, ADAPTIVE_JOB_OBSERVED, {
82
+ value: true,
83
+ enumerable: false,
84
+ configurable: false,
85
+ });
86
+ job.promise = job.promise.finally(() => {
87
+ recordAdaptiveJobOutcome(job);
88
+ });
89
+ return job;
90
+ };
91
+ return true;
92
+ }
93
+
94
+ export async function apply(ctx) {
95
+ registerRuntimeEndpoint(ctx);
96
+ installAdaptiveHealthObserver();
97
+ return applyHub(ctx);
98
+ }