@ran-sh/dsh-crew 1.1.1 → 1.2.0-rc.5
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 -17
- package/.claude-plugin/plugin.json +7 -1
- package/README.md +44 -15
- package/README.zh.md +37 -12
- package/agents/ds-flash.md +16 -18
- package/agents/ds-pro.md +16 -24
- package/agents/ds-reviewer.md +15 -15
- package/agents/ds-worker.md +16 -16
- package/bin/dsh-crew.mjs +16 -12
- package/codex/AGENTS.md +37 -85
- package/codex/agents/ds-flash.toml +18 -14
- package/codex/agents/ds-pro.toml +18 -15
- package/codex/agents/ds-reviewer.toml +17 -13
- package/codex/agents/ds-worker.toml +17 -13
- package/codex/prompts/dsh-config.md +17 -1
- package/codex/prompts/dsh-status.md +5 -1
- package/commands/config.md +16 -4
- package/commands/status.md +6 -2
- package/docs/installation.md +54 -9
- package/docs/readiness-matrix.md +14 -6
- package/docs/ui-surfaces.md +32 -17
- package/lib/client.js +769 -177
- package/official-web-bridge/lib/client.js +297 -209
- package/official-web-bridge/overlay-entry.mjs +59 -0
- package/package.json +6 -3
- package/scripts/build-client.mjs +2 -0
- package/scripts/setup.mjs +8 -7
- package/scripts/verify-crew-ui-polish.mjs +145 -0
- package/scripts/verify-history-ui.mjs +97 -0
- package/scripts/verify-npm-install.mjs +1 -1
- package/src/client/history-panel.tsx +108 -0
- package/src/client/host-readiness.mjs +15 -6
- package/src/client/index.tsx +91 -69
- package/src/client/model-callability-view.mjs +17 -0
- package/src/client/panel-chrome.tsx +40 -0
- package/src/client/quick-panel.tsx +88 -73
- package/src/client/readiness-envelope.mjs +34 -0
- package/src/client/task-telemetry.mjs +11 -4
- package/src/config-readiness.mjs +181 -115
- package/src/extension-contract.mjs +44 -26
- package/src/history/admission-gate.mjs +51 -0
- package/src/history/archive-store.mjs +261 -0
- package/src/history/cleanup-plan.mjs +64 -0
- package/src/history/http.mjs +32 -0
- package/src/history/operation.mjs +86 -0
- package/src/history/runner.mjs +51 -0
- package/src/history/runtime.mjs +28 -0
- package/src/history/service.mjs +137 -0
- package/src/history/state.mjs +27 -0
- package/src/hub/index.mjs +141 -24
- package/src/hub-client.mjs +17 -15
- package/src/image-output.mjs +71 -0
- package/src/install/install-legacy.mjs +126 -23
- package/src/install/npx-lifecycle.mjs +373 -64
- package/src/install/official-frontend-assets.mjs +78 -0
- package/src/install/payload-content.mjs +88 -0
- package/src/install/windows-startup.mjs +107 -19
- package/src/install/windows-supervisor-adapter.mjs +443 -0
- package/src/install/windows-supervisor-lifecycle.mjs +782 -0
- package/src/install/zcode.mjs +53 -14
- package/src/job-contracts.mjs +37 -31
- package/src/jobs.mjs +2 -2
- package/src/mcp-runtime.mjs +74 -9
- package/src/model-callability-contract.mjs +79 -0
- package/src/multimodal.mjs +0 -0
- package/src/official-web-bridge.mjs +0 -2
- package/src/provider-health.mjs +130 -124
- package/src/readiness-matrix.mjs +20 -15
- package/src/runtime-identity-contract.mjs +34 -0
- package/src/runtime-identity.mjs +14 -4
- package/src/runtime-readiness-snapshot.mjs +285 -87
- package/src/server.mjs +86 -76
- package/src/standalone-sdk.mjs +23 -0
- package/src/supervisor/restart-request.mjs +64 -10
- package/src/workflow-runtime.mjs +28 -13
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +327 -51
- package/windows/supervisor-control.ps1 +467 -0
- package/zcode/AGENTS.md +4 -1
- package/zcode/agents/ds-reviewer.md +31 -36
- package/zcode/agents/ds-worker.md +31 -39
- package/zcode/commands/dsh-config.md +17 -6
- package/zcode/commands/dsh-status.md +5 -4
- package/.mcp.json +0 -8
package/lib/client.js
CHANGED
|
@@ -163,14 +163,28 @@ function ActivationSummary({ activation, locale }) {
|
|
|
163
163
|
]
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
|
+
function nonEmpty(value) {
|
|
167
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
168
|
+
}
|
|
169
|
+
function isCompleteRuntimeIdentity(identity) {
|
|
170
|
+
return Boolean(identity && typeof identity === "object" && nonEmpty(identity.execution_plane) && nonEmpty(identity.profile) && Number.isInteger(identity.listen_port) && identity.listen_port > 0 && nonEmpty(identity.runtime_id));
|
|
171
|
+
}
|
|
172
|
+
function sameCompleteRuntimeIdentity(left, right) {
|
|
173
|
+
return isCompleteRuntimeIdentity(left) && isCompleteRuntimeIdentity(right) && left.execution_plane === right.execution_plane && left.profile === right.profile && left.listen_port === right.listen_port && left.runtime_id === right.runtime_id;
|
|
174
|
+
}
|
|
175
|
+
function isExactNativeCrewIdentity(identity) {
|
|
176
|
+
return isCompleteRuntimeIdentity(identity) && identity.execution_plane === "hub-3210" && identity.profile === "dsh-crew" && identity.listen_port === 3210;
|
|
177
|
+
}
|
|
166
178
|
//#endregion
|
|
167
179
|
//#region src/client/host-readiness.mjs
|
|
168
180
|
const READINESS_STATES = Object.freeze({
|
|
169
181
|
READY: "READY",
|
|
170
182
|
DEGRADED: "DEGRADED",
|
|
171
183
|
UNAVAILABLE: "UNAVAILABLE",
|
|
172
|
-
UNKNOWN: "UNKNOWN"
|
|
184
|
+
UNKNOWN: "UNKNOWN",
|
|
185
|
+
NOT_APPLICABLE: "NOT_APPLICABLE"
|
|
173
186
|
});
|
|
187
|
+
const MAX_RUNTIME_SNAPSHOT_AGE_MS = 3e5;
|
|
174
188
|
function componentState(status, keys, { aligned = false } = {}) {
|
|
175
189
|
if (status === void 0 || status === null || typeof status !== "object") return READINESS_STATES.UNKNOWN;
|
|
176
190
|
if (status.installed === false) return READINESS_STATES.UNAVAILABLE;
|
|
@@ -182,7 +196,11 @@ function componentState(status, keys, { aligned = false } = {}) {
|
|
|
182
196
|
function runtimeState(runtime, readinessSnapshot) {
|
|
183
197
|
if (readinessSnapshot && typeof readinessSnapshot === "object") {
|
|
184
198
|
const identity = readinessSnapshot.runtime;
|
|
185
|
-
if (!(identity
|
|
199
|
+
if (!isExactNativeCrewIdentity(identity)) return READINESS_STATES.UNAVAILABLE;
|
|
200
|
+
if (runtime && (!isCompleteRuntimeIdentity(runtime) || !sameCompleteRuntimeIdentity(runtime, identity))) return READINESS_STATES.UNKNOWN;
|
|
201
|
+
if (!Number.isFinite(readinessSnapshot.captured_at)) return READINESS_STATES.UNKNOWN;
|
|
202
|
+
if (readinessSnapshot.captured_at > Date.now() + 3e4) return READINESS_STATES.UNKNOWN;
|
|
203
|
+
if (Date.now() - readinessSnapshot.captured_at > MAX_RUNTIME_SNAPSHOT_AGE_MS) return READINESS_STATES.DEGRADED;
|
|
186
204
|
const row = Array.isArray(readinessSnapshot.readiness_matrix?.rows) ? readinessSnapshot.readiness_matrix.rows.find((entry) => entry?.id === "hub_compatibility") : void 0;
|
|
187
205
|
if (row?.status === "PASS") return READINESS_STATES.READY;
|
|
188
206
|
if (row?.status === "FAIL") return READINESS_STATES.UNAVAILABLE;
|
|
@@ -191,11 +209,11 @@ function runtimeState(runtime, readinessSnapshot) {
|
|
|
191
209
|
}
|
|
192
210
|
if (runtime === void 0) return READINESS_STATES.UNKNOWN;
|
|
193
211
|
if (runtime === null) return READINESS_STATES.UNAVAILABLE;
|
|
194
|
-
return runtime?.ok === true && runtime
|
|
212
|
+
return runtime?.ok === true && isExactNativeCrewIdentity(runtime) ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN;
|
|
195
213
|
}
|
|
196
214
|
function bridgeState(surface) {
|
|
197
215
|
if (surface === "official-bridge") return READINESS_STATES.READY;
|
|
198
|
-
if (surface === "native-crew-harness") return READINESS_STATES.
|
|
216
|
+
if (surface === "native-crew-harness") return READINESS_STATES.NOT_APPLICABLE;
|
|
199
217
|
return READINESS_STATES.UNKNOWN;
|
|
200
218
|
}
|
|
201
219
|
/** Project only structured, non-secret installer/runtime evidence. */
|
|
@@ -248,6 +266,135 @@ function projectHostReadiness({ installStatus, runtime, surface, readinessSnapsh
|
|
|
248
266
|
}
|
|
249
267
|
];
|
|
250
268
|
}
|
|
269
|
+
const ROLES = ["worker", "reviewer"];
|
|
270
|
+
const STATES = /* @__PURE__ */ new Set([
|
|
271
|
+
"CALLABLE",
|
|
272
|
+
"NOT_CALLABLE",
|
|
273
|
+
"STALE",
|
|
274
|
+
"UNKNOWN",
|
|
275
|
+
"NOT_APPLICABLE"
|
|
276
|
+
]);
|
|
277
|
+
const SOURCES = /* @__PURE__ */ new Set(["provider_health", "execution"]);
|
|
278
|
+
const MAX_EVIDENCE_TTL_MS = 864e5;
|
|
279
|
+
function exactKeys(value, keys) {
|
|
280
|
+
return value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
281
|
+
}
|
|
282
|
+
function route(value) {
|
|
283
|
+
return typeof value?.provider === "string" && value.provider.trim() && typeof value?.model === "string" && value.model.trim() ? {
|
|
284
|
+
provider: value.provider.trim(),
|
|
285
|
+
model: value.model.trim()
|
|
286
|
+
} : null;
|
|
287
|
+
}
|
|
288
|
+
function validEvidence(role, now) {
|
|
289
|
+
if (!SOURCES.has(role.source) || !Number.isFinite(role.observed_at) || role.observed_at > now || !Number.isFinite(role.expires_at) || role.expires_at <= now || role.expires_at - role.observed_at > MAX_EVIDENCE_TTL_MS) return false;
|
|
290
|
+
if (role.source === "execution") {
|
|
291
|
+
if (!role.last_success || typeof role.last_success !== "object" || typeof role.last_success.job_id !== "string" || !role.last_success.job_id.trim() || role.last_success.observed_at !== role.observed_at || role.last_success.expires_at !== role.expires_at) return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
function validateModelCallabilityV2({ projection, runtime, expectedEnabledRoles, expectedSelections, now = Date.now() } = {}) {
|
|
296
|
+
const fail = (reason_code) => ({
|
|
297
|
+
ok: false,
|
|
298
|
+
reason_code,
|
|
299
|
+
state: "UNKNOWN"
|
|
300
|
+
});
|
|
301
|
+
if (!projection || typeof projection !== "object" || projection.schema_version !== 2) return fail("MODEL_CALLABILITY_SCHEMA_INVALID");
|
|
302
|
+
if (!isExactNativeCrewIdentity(runtime) || !isExactNativeCrewIdentity(projection.runtime_identity) || !sameCompleteRuntimeIdentity(runtime, projection.runtime_identity) || projection.current_runtime_id !== runtime.runtime_id) return fail("MODEL_CALLABILITY_RUNTIME_MISMATCH");
|
|
303
|
+
if (!Number.isFinite(projection.captured_at) || projection.captured_at > now) return fail("MODEL_CALLABILITY_CAPTURE_INVALID");
|
|
304
|
+
if (!exactKeys(projection.enabled_roles, ROLES) || ROLES.some((role) => typeof projection.enabled_roles[role] !== "boolean")) return fail("MODEL_CALLABILITY_ROLES_INVALID");
|
|
305
|
+
if (expectedEnabledRoles && (!exactKeys(expectedEnabledRoles, ROLES) || ROLES.some((role) => projection.enabled_roles[role] !== expectedEnabledRoles[role]))) return fail("MODEL_CALLABILITY_ROLE_CONFIG_MISMATCH");
|
|
306
|
+
if (expectedSelections && !exactKeys(expectedSelections, ROLES)) return fail("MODEL_CALLABILITY_EXPECTED_SELECTIONS_INVALID");
|
|
307
|
+
if (!exactKeys(projection.roles, ROLES)) return fail("MODEL_CALLABILITY_ROLE_EVIDENCE_MISSING");
|
|
308
|
+
for (const roleName of ROLES) {
|
|
309
|
+
const role = projection.roles[roleName];
|
|
310
|
+
if (!role || !STATES.has(role.state)) return fail("MODEL_CALLABILITY_ROLE_STATE_INVALID");
|
|
311
|
+
const selected = route(role.selected);
|
|
312
|
+
if (role.selected !== void 0 && role.selected !== null && !selected) return fail("MODEL_CALLABILITY_SELECTION_INVALID");
|
|
313
|
+
if (expectedSelections) {
|
|
314
|
+
const expectedRaw = expectedSelections[roleName];
|
|
315
|
+
const expected = expectedRaw === null ? null : route(expectedRaw);
|
|
316
|
+
if (expectedRaw !== null && !expected) return fail("MODEL_CALLABILITY_EXPECTED_SELECTION_INVALID");
|
|
317
|
+
if (expected === null !== (selected === null) || expected && (expected.provider !== selected.provider || expected.model !== selected.model)) return fail("MODEL_CALLABILITY_SELECTION_MISMATCH");
|
|
318
|
+
}
|
|
319
|
+
if (projection.enabled_roles[roleName] === false) {
|
|
320
|
+
if (role.state !== "NOT_APPLICABLE" || role.reason_code !== "ROLE_DISABLED") return fail("MODEL_CALLABILITY_DISABLED_ROLE_INVALID");
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (["CALLABLE", "NOT_CALLABLE"].includes(role.state) && !selected) return fail("MODEL_CALLABILITY_SELECTION_INVALID");
|
|
324
|
+
if (projection.enabled_roles[roleName] === true && role.state === "NOT_APPLICABLE") return fail("MODEL_CALLABILITY_ENABLED_ROLE_NOT_APPLICABLE");
|
|
325
|
+
if (["CALLABLE", "NOT_CALLABLE"].includes(role.state)) {
|
|
326
|
+
if (!validEvidence(role, now)) return fail("MODEL_CALLABILITY_EVIDENCE_INVALID");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const activeRoles = ROLES.map((role) => projection.roles[role]).filter((_, index) => projection.enabled_roles[ROLES[index]] === true);
|
|
330
|
+
const activeStates = activeRoles.map((role) => role.state);
|
|
331
|
+
const state = activeStates.includes("NOT_CALLABLE") ? "NOT_CALLABLE" : activeStates.length > 0 && activeStates.every((value) => value === "CALLABLE") ? "CALLABLE" : activeStates.includes("STALE") ? "STALE" : "UNKNOWN";
|
|
332
|
+
if (projection.overall !== state) return fail("MODEL_CALLABILITY_OVERALL_CONTRADICTION");
|
|
333
|
+
const evidenceExpirations = activeRoles.map((role) => role.expires_at).filter((value) => Number.isFinite(value));
|
|
334
|
+
const expectedExpires = evidenceExpirations.length ? Math.min(...evidenceExpirations) : null;
|
|
335
|
+
if (projection.expires_at !== expectedExpires) return fail("MODEL_CALLABILITY_EXPIRY_CONTRADICTION");
|
|
336
|
+
if (state === "CALLABLE" && (!Number.isFinite(projection.expires_at) || projection.expires_at <= now)) return fail("MODEL_CALLABILITY_EXPIRED");
|
|
337
|
+
return {
|
|
338
|
+
ok: true,
|
|
339
|
+
state,
|
|
340
|
+
reason_code: "MODEL_CALLABILITY_VALID"
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/client/model-callability-view.mjs
|
|
345
|
+
function modelCallabilityState(projection, runtime = null, now = Date.now()) {
|
|
346
|
+
if (!projection || typeof projection !== "object" || typeof projection.overall !== "string") return READINESS_STATES.UNKNOWN;
|
|
347
|
+
const validation = validateModelCallabilityV2({
|
|
348
|
+
projection,
|
|
349
|
+
runtime,
|
|
350
|
+
now
|
|
351
|
+
});
|
|
352
|
+
if (!validation.ok) return READINESS_STATES.UNKNOWN;
|
|
353
|
+
if (validation.state === "CALLABLE") return READINESS_STATES.READY;
|
|
354
|
+
if (validation.state === "NOT_CALLABLE") return READINESS_STATES.UNAVAILABLE;
|
|
355
|
+
if (validation.state === "STALE") return READINESS_STATES.DEGRADED;
|
|
356
|
+
return READINESS_STATES.UNKNOWN;
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/client/readiness-envelope.mjs
|
|
360
|
+
function readinessExpiryDelay(expiresAt, now = Date.now()) {
|
|
361
|
+
return Number.isFinite(expiresAt) ? Math.max(0, expiresAt - now + 10) : null;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Apply one extension response to the atomic runtime/readiness envelope.
|
|
365
|
+
* Older responses are ignored so a restart cannot be rolled back by a slow
|
|
366
|
+
* request that started before the new runtime identity was observed.
|
|
367
|
+
*/
|
|
368
|
+
function acceptReadinessResponse(response, { generation, latestGeneration } = {}) {
|
|
369
|
+
if (generation !== latestGeneration) return {
|
|
370
|
+
accepted: false,
|
|
371
|
+
envelope: null
|
|
372
|
+
};
|
|
373
|
+
const extensionRuntime = response?.extension?.runtime;
|
|
374
|
+
const snapshot = response?.extension?.readiness_snapshot;
|
|
375
|
+
const valid = response?.ok === true && isExactNativeCrewIdentity(extensionRuntime) && isExactNativeCrewIdentity(snapshot?.runtime) && sameCompleteRuntimeIdentity(extensionRuntime, snapshot.runtime);
|
|
376
|
+
const expectedEnabledRoles = {
|
|
377
|
+
worker: response?.extension?.capabilities?.["deepseek.worker"] === true,
|
|
378
|
+
reviewer: response?.extension?.capabilities?.["deepseek.reviewer"] === true
|
|
379
|
+
};
|
|
380
|
+
const projection = valid ? validateModelCallabilityV2({
|
|
381
|
+
projection: snapshot.model_callability,
|
|
382
|
+
runtime: extensionRuntime,
|
|
383
|
+
expectedEnabledRoles,
|
|
384
|
+
expectedSelections: {
|
|
385
|
+
worker: snapshot.worker?.selected ?? null,
|
|
386
|
+
reviewer: snapshot.reviewer?.selected ?? null
|
|
387
|
+
}
|
|
388
|
+
}) : { ok: false };
|
|
389
|
+
return {
|
|
390
|
+
accepted: true,
|
|
391
|
+
envelope: valid && projection.ok ? {
|
|
392
|
+
runtime: extensionRuntime,
|
|
393
|
+
snapshot,
|
|
394
|
+
expiresAt: snapshot.model_callability.expires_at
|
|
395
|
+
} : {}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
251
398
|
//#endregion
|
|
252
399
|
//#region src/client/surface-detection.mjs
|
|
253
400
|
const CREW_UI_SURFACES = Object.freeze({
|
|
@@ -308,9 +455,11 @@ function hasInvocationEvidence(job) {
|
|
|
308
455
|
return Number(job?.turn ?? 0) > 0 || Number(job?.toolCalls ?? 0) > 0 || tokenCount > 0;
|
|
309
456
|
}
|
|
310
457
|
/**
|
|
311
|
-
* Build a bounded, presentation-only model activity summary from
|
|
312
|
-
* Hub already exposes.
|
|
313
|
-
*
|
|
458
|
+
* Build a bounded, presentation-only, job-level model activity summary from
|
|
459
|
+
* the jobs the Hub already exposes. Each qualifying job contributes one row
|
|
460
|
+
* count for its selected provider/model; the count is not an inferred number
|
|
461
|
+
* of underlying model turns. No prompts, results, credentials, or new
|
|
462
|
+
* persistence are introduced here.
|
|
314
463
|
*/
|
|
315
464
|
function aggregateModelInvocations(jobs = []) {
|
|
316
465
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -332,7 +481,7 @@ function aggregateModelInvocations(jobs = []) {
|
|
|
332
481
|
current.count += 1;
|
|
333
482
|
current.taskSources.add(clean(job?.source, "api"));
|
|
334
483
|
current.selectionSources.add(clean(job?.selection_source, "unknown"));
|
|
335
|
-
|
|
484
|
+
if (job?.role === "worker" || job?.role === "reviewer") current.roles.add(job.role);
|
|
336
485
|
const calledAt = validTimestamp(job?.startedAt);
|
|
337
486
|
if (calledAt && (!current.lastCalledAt || Date.parse(calledAt) > Date.parse(current.lastCalledAt))) current.lastCalledAt = calledAt;
|
|
338
487
|
groups.set(key, current);
|
|
@@ -350,10 +499,474 @@ function aggregateModelInvocations(jobs = []) {
|
|
|
350
499
|
}));
|
|
351
500
|
}
|
|
352
501
|
//#endregion
|
|
502
|
+
//#region src/client/panel-chrome.tsx
|
|
503
|
+
function PanelStyles() {
|
|
504
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
|
|
505
|
+
.dsh-crew-ui { --crew-line: rgba(128,128,128,.24); --crew-soft: rgba(128,128,128,.045); --crew-accent: #2563eb; font-size: 13px; line-height: 1.55; min-width: 0; }
|
|
506
|
+
.dsh-crew-ui *, .dsh-crew-ui *::before, .dsh-crew-ui *::after { box-sizing: border-box; }
|
|
507
|
+
.dsh-crew-ui .crew-panel-header { border: 1px solid var(--crew-line); border-radius: 12px; padding: 16px; background: var(--crew-soft); display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 16px; }
|
|
508
|
+
.dsh-crew-ui .crew-panel-heading { flex: 1 1 260px; min-width: 0; }
|
|
509
|
+
.dsh-crew-ui .crew-eyebrow { font-size: 10.5px; letter-spacing: .055em; opacity: .65; margin-bottom: 4px; }
|
|
510
|
+
.dsh-crew-ui .crew-panel-title { margin: 0; font-size: 19px; font-weight: 680; line-height: 1.3; letter-spacing: -.025em; }
|
|
511
|
+
.dsh-crew-ui .crew-panel-description { margin: 5px 0 0; font-size: 12px; opacity: .72; overflow-wrap: anywhere; }
|
|
512
|
+
.dsh-crew-ui .crew-panel-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
|
513
|
+
.dsh-crew-ui .crew-nav-link { display: inline-flex; align-items: center; justify-content: center; color: var(--crew-accent); background: transparent; border: 1px solid var(--crew-line); border-radius: 8px; padding: 7px 10px; font-size: 12px; font-weight: 600; text-decoration: none; flex-shrink: 0; }
|
|
514
|
+
.dsh-crew-ui button:focus-visible, .dsh-crew-ui a:focus-visible, .dsh-crew-ui input:focus-visible, .dsh-crew-ui summary:focus-visible { outline: 2px solid var(--crew-accent); outline-offset: 3px; }
|
|
515
|
+
.dsh-crew-ui button:disabled { cursor: not-allowed; opacity: .4; }
|
|
516
|
+
.dsh-crew-ui .crew-nav-link:hover, .dsh-crew-ui .crew-section-trigger:hover, .dsh-crew-ui summary:hover { background: rgba(128,128,128,.09); }
|
|
517
|
+
.dsh-crew-ui .crew-section-heading { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
|
|
518
|
+
.dsh-crew-ui .crew-section-title { font-weight: 650; font-size: 13px; }
|
|
519
|
+
.dsh-crew-ui .crew-section-summary { font-size: 11.5px; opacity: .65; overflow-wrap: anywhere; }
|
|
520
|
+
.dsh-crew-ui .crew-config-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin: 6px 0 2px; }
|
|
521
|
+
@media (prefers-color-scheme: dark) { .dsh-crew-ui { --crew-accent: #8ab4ff; } }
|
|
522
|
+
@media (prefers-reduced-motion: reduce) { .dsh-crew-ui * { transition: none !important; } }
|
|
523
|
+
` });
|
|
524
|
+
}
|
|
525
|
+
function PanelHeader({ title, eyebrow, description, href, linkText, children }) {
|
|
526
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
527
|
+
className: "crew-panel-header",
|
|
528
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
529
|
+
className: "crew-panel-heading",
|
|
530
|
+
children: [
|
|
531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
532
|
+
className: "crew-eyebrow",
|
|
533
|
+
children: eyebrow
|
|
534
|
+
}),
|
|
535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
536
|
+
className: "crew-panel-title",
|
|
537
|
+
children: title
|
|
538
|
+
}),
|
|
539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
540
|
+
className: "crew-panel-description",
|
|
541
|
+
children: description
|
|
542
|
+
}),
|
|
543
|
+
children && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
544
|
+
className: "crew-panel-badges",
|
|
545
|
+
children
|
|
546
|
+
})
|
|
547
|
+
]
|
|
548
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
549
|
+
className: "crew-nav-link",
|
|
550
|
+
href,
|
|
551
|
+
target: "_blank",
|
|
552
|
+
rel: "noopener noreferrer",
|
|
553
|
+
children: linkText
|
|
554
|
+
})]
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/client/history-panel.tsx
|
|
559
|
+
const API$1 = "/_dsh/dsh-crew/history/";
|
|
560
|
+
const pending = (phase) => !!phase && ![
|
|
561
|
+
"IDLE",
|
|
562
|
+
"DONE",
|
|
563
|
+
"FAILED",
|
|
564
|
+
"ROLLED_BACK"
|
|
565
|
+
].includes(phase);
|
|
566
|
+
const copy = {
|
|
567
|
+
zh: {
|
|
568
|
+
title: "工作区与会话清理",
|
|
569
|
+
intro: "仅影响 3210 的工作区记录与会话日志;不删除项目文件、附件或 3080 数据。",
|
|
570
|
+
operation: "操作",
|
|
571
|
+
archive: "归档(可恢复)",
|
|
572
|
+
delete: "删除(不可恢复)",
|
|
573
|
+
scope: "时间范围",
|
|
574
|
+
all: "全部",
|
|
575
|
+
before: "指定时间之前",
|
|
576
|
+
time: "创建时间早于",
|
|
577
|
+
timeHint: "按本机时区选择,严格按创建时间筛选;包含新会话的工作区会保留。",
|
|
578
|
+
preview: "预览清理范围",
|
|
579
|
+
confirm: "确认执行",
|
|
580
|
+
acknowledgement: "删除确认:请输入 DELETE",
|
|
581
|
+
idle: "会短暂停止并重启 3210;请先结束或关闭所有正在使用的 3210 会话。",
|
|
582
|
+
consent: "我已确认范围,并同意短暂重启 3210",
|
|
583
|
+
restore: "恢复",
|
|
584
|
+
archives: "已归档批次",
|
|
585
|
+
empty: "暂无可恢复的归档",
|
|
586
|
+
working: "维护中,正在等待 3210 重新连接…",
|
|
587
|
+
refresh: "刷新页面与工作区列表",
|
|
588
|
+
recover: "恢复未完成的维护",
|
|
589
|
+
recovery: "维护尚未完成,请使用恢复按钮;若 3210 无法连接,在终端运行 dsh-crew history recover。",
|
|
590
|
+
cancelQueued: "取消尚未开始的维护",
|
|
591
|
+
cancelQueuedHint: "仅取消尚未开始的维护,不清理任何数据;如果操作已开始,请等待完成。",
|
|
592
|
+
count: (w, s) => `${w} 个工作区 · ${s} 个会话`,
|
|
593
|
+
confirmRestore: "恢复这一批归档?将短暂重启 3210,不会覆盖冲突数据。",
|
|
594
|
+
phase: {
|
|
595
|
+
IDLE: "就绪",
|
|
596
|
+
QUEUED: "已提交",
|
|
597
|
+
STOPPING: "停止后台",
|
|
598
|
+
APPLYING: "处理数据",
|
|
599
|
+
STARTING: "启动后台",
|
|
600
|
+
VERIFYING: "验证结果",
|
|
601
|
+
DONE: "已完成",
|
|
602
|
+
FAILED: "未执行成功",
|
|
603
|
+
ROLLED_BACK: "已回滚",
|
|
604
|
+
RECOVERY_REQUIRED: "需要恢复"
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
en: {
|
|
608
|
+
title: "Workspace & session cleanup",
|
|
609
|
+
intro: "Only 3210 workspace records and session logs. Project files, attachments and 3080 data are untouched.",
|
|
610
|
+
operation: "Action",
|
|
611
|
+
archive: "Archive (restorable)",
|
|
612
|
+
delete: "Delete (permanent)",
|
|
613
|
+
scope: "Time range",
|
|
614
|
+
all: "All",
|
|
615
|
+
before: "Before a date",
|
|
616
|
+
time: "Created before",
|
|
617
|
+
timeHint: "Local timezone; strict creation-time cutoff. Workspaces with newer sessions are kept.",
|
|
618
|
+
preview: "Preview cleanup",
|
|
619
|
+
confirm: "Confirm operation",
|
|
620
|
+
acknowledgement: "Type DELETE to confirm deletion",
|
|
621
|
+
idle: "3210 will briefly stop and restart. End or close all active 3210 conversations first.",
|
|
622
|
+
consent: "I reviewed the scope and agree to restart 3210",
|
|
623
|
+
restore: "Restore",
|
|
624
|
+
archives: "Archived batches",
|
|
625
|
+
empty: "No restorable archives",
|
|
626
|
+
working: "Maintenance in progress; waiting for 3210 to reconnect…",
|
|
627
|
+
refresh: "Reload page and workspace list",
|
|
628
|
+
recover: "Recover unfinished maintenance",
|
|
629
|
+
recovery: "Maintenance is unfinished. Use recovery; if 3210 is unreachable, run dsh-crew history recover in a terminal.",
|
|
630
|
+
cancelQueued: "Cancel unstarted maintenance",
|
|
631
|
+
cancelQueuedHint: "Cancel only if maintenance has not started. No data is cleaned; if already running, wait for completion.",
|
|
632
|
+
count: (w, s) => `${w} workspaces · ${s} sessions`,
|
|
633
|
+
confirmRestore: "Restore this archive? 3210 will briefly restart; conflicting data will not be overwritten.",
|
|
634
|
+
phase: {
|
|
635
|
+
IDLE: "Ready",
|
|
636
|
+
QUEUED: "Queued",
|
|
637
|
+
STOPPING: "Stopping backend",
|
|
638
|
+
APPLYING: "Processing data",
|
|
639
|
+
STARTING: "Starting backend",
|
|
640
|
+
VERIFYING: "Verifying",
|
|
641
|
+
DONE: "Completed",
|
|
642
|
+
FAILED: "Failed",
|
|
643
|
+
ROLLED_BACK: "Rolled back",
|
|
644
|
+
RECOVERY_REQUIRED: "Recovery required"
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
function HistoryPanel({ locale }) {
|
|
649
|
+
const t = copy[locale === "zh" ? "zh" : "en"];
|
|
650
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
651
|
+
const [operation, setOperation] = (0, react.useState)("archive");
|
|
652
|
+
const [scope, setScope] = (0, react.useState)("all");
|
|
653
|
+
const [before, setBefore] = (0, react.useState)("");
|
|
654
|
+
const [preview, setPreview] = (0, react.useState)(null);
|
|
655
|
+
const [status, setStatus] = (0, react.useState)({ phase: "IDLE" });
|
|
656
|
+
const [archives, setArchives] = (0, react.useState)([]);
|
|
657
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
658
|
+
const [consent, setConsent] = (0, react.useState)(false);
|
|
659
|
+
const [ack, setAck] = (0, react.useState)("");
|
|
660
|
+
const [notice, setNotice] = (0, react.useState)("");
|
|
661
|
+
const [connected, setConnected] = (0, react.useState)(false);
|
|
662
|
+
const locked = busy || pending(status.phase);
|
|
663
|
+
const invalidate = () => {
|
|
664
|
+
setPreview(null);
|
|
665
|
+
setConsent(false);
|
|
666
|
+
setAck("");
|
|
667
|
+
};
|
|
668
|
+
async function request(action, body) {
|
|
669
|
+
const res = await fetch(API$1 + action, {
|
|
670
|
+
cache: "no-store",
|
|
671
|
+
...body === void 0 ? {} : {
|
|
672
|
+
method: "POST",
|
|
673
|
+
headers: { "content-type": "application/json" },
|
|
674
|
+
body: JSON.stringify(body)
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
const data = await res.json();
|
|
678
|
+
if (!res.ok || !data.ok) throw Error(data.code ?? `HTTP ${res.status}`);
|
|
679
|
+
return data;
|
|
680
|
+
}
|
|
681
|
+
(0, react.useEffect)(() => {
|
|
682
|
+
if (!open) return;
|
|
683
|
+
let disposed = false;
|
|
684
|
+
const poll = async () => {
|
|
685
|
+
try {
|
|
686
|
+
const [s, a] = await Promise.all([request("status"), request("archives")]);
|
|
687
|
+
if (!disposed) {
|
|
688
|
+
setStatus(s.status);
|
|
689
|
+
setArchives(a.archives);
|
|
690
|
+
setConnected(true);
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
if (!disposed) setConnected(false);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
poll();
|
|
697
|
+
const timer = setInterval(poll, 2500);
|
|
698
|
+
return () => {
|
|
699
|
+
disposed = true;
|
|
700
|
+
clearInterval(timer);
|
|
701
|
+
};
|
|
702
|
+
}, [open]);
|
|
703
|
+
async function act(action, body) {
|
|
704
|
+
setBusy(true);
|
|
705
|
+
setNotice("");
|
|
706
|
+
try {
|
|
707
|
+
const result = await request(action, body);
|
|
708
|
+
if (result.preview) {
|
|
709
|
+
setPreview(result.preview);
|
|
710
|
+
setConsent(false);
|
|
711
|
+
setAck("");
|
|
712
|
+
}
|
|
713
|
+
if (result.status) {
|
|
714
|
+
setStatus(result.status);
|
|
715
|
+
setPreview(null);
|
|
716
|
+
}
|
|
717
|
+
} catch (error) {
|
|
718
|
+
setNotice(String(error.message));
|
|
719
|
+
setPreview(null);
|
|
720
|
+
} finally {
|
|
721
|
+
setBusy(false);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const style = {
|
|
725
|
+
border: "1px solid rgba(128,128,128,.25)",
|
|
726
|
+
borderRadius: 8,
|
|
727
|
+
padding: "7px 10px",
|
|
728
|
+
color: "inherit",
|
|
729
|
+
background: "transparent",
|
|
730
|
+
font: "inherit"
|
|
731
|
+
};
|
|
732
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
733
|
+
style: {
|
|
734
|
+
border: "1px solid rgba(128,128,128,.24)",
|
|
735
|
+
borderRadius: 10,
|
|
736
|
+
padding: "10px 13px"
|
|
737
|
+
},
|
|
738
|
+
onToggle: (e) => setOpen(e.currentTarget.open),
|
|
739
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
740
|
+
style: {
|
|
741
|
+
cursor: "pointer",
|
|
742
|
+
fontWeight: 650
|
|
743
|
+
},
|
|
744
|
+
children: t.title
|
|
745
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
746
|
+
style: {
|
|
747
|
+
display: "flex",
|
|
748
|
+
flexDirection: "column",
|
|
749
|
+
gap: 10,
|
|
750
|
+
marginTop: 12
|
|
751
|
+
},
|
|
752
|
+
children: [
|
|
753
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
754
|
+
style: {
|
|
755
|
+
margin: 0,
|
|
756
|
+
opacity: .75
|
|
757
|
+
},
|
|
758
|
+
children: t.intro
|
|
759
|
+
}),
|
|
760
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
761
|
+
style: {
|
|
762
|
+
display: "flex",
|
|
763
|
+
gap: 12,
|
|
764
|
+
flexWrap: "wrap"
|
|
765
|
+
},
|
|
766
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
767
|
+
t.operation,
|
|
768
|
+
" ",
|
|
769
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
770
|
+
style,
|
|
771
|
+
value: operation,
|
|
772
|
+
disabled: locked,
|
|
773
|
+
onChange: (e) => {
|
|
774
|
+
setOperation(e.target.value);
|
|
775
|
+
invalidate();
|
|
776
|
+
},
|
|
777
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
778
|
+
value: "archive",
|
|
779
|
+
children: t.archive
|
|
780
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
781
|
+
value: "delete",
|
|
782
|
+
children: t.delete
|
|
783
|
+
})]
|
|
784
|
+
})
|
|
785
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
786
|
+
t.scope,
|
|
787
|
+
" ",
|
|
788
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
789
|
+
style,
|
|
790
|
+
value: scope,
|
|
791
|
+
disabled: locked,
|
|
792
|
+
onChange: (e) => {
|
|
793
|
+
setScope(e.target.value);
|
|
794
|
+
invalidate();
|
|
795
|
+
},
|
|
796
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
797
|
+
value: "all",
|
|
798
|
+
children: t.all
|
|
799
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
800
|
+
value: "before",
|
|
801
|
+
children: t.before
|
|
802
|
+
})]
|
|
803
|
+
})
|
|
804
|
+
] })]
|
|
805
|
+
}),
|
|
806
|
+
scope === "before" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
807
|
+
t.time,
|
|
808
|
+
" ",
|
|
809
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
810
|
+
type: "datetime-local",
|
|
811
|
+
style,
|
|
812
|
+
value: before,
|
|
813
|
+
disabled: locked,
|
|
814
|
+
onChange: (e) => {
|
|
815
|
+
setBefore(e.target.value);
|
|
816
|
+
invalidate();
|
|
817
|
+
}
|
|
818
|
+
})
|
|
819
|
+
] }),
|
|
820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
821
|
+
style: {
|
|
822
|
+
fontSize: 11.5,
|
|
823
|
+
opacity: .7
|
|
824
|
+
},
|
|
825
|
+
children: t.timeHint
|
|
826
|
+
}),
|
|
827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
828
|
+
type: "button",
|
|
829
|
+
style,
|
|
830
|
+
disabled: locked || !connected || scope === "before" && !Number.isFinite(new Date(before).getTime()),
|
|
831
|
+
onClick: () => void act("preview", {
|
|
832
|
+
operation,
|
|
833
|
+
scope,
|
|
834
|
+
...scope === "before" ? { before: new Date(before).toISOString() } : {}
|
|
835
|
+
}),
|
|
836
|
+
children: t.preview
|
|
837
|
+
}),
|
|
838
|
+
preview && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
839
|
+
style: {
|
|
840
|
+
...style,
|
|
841
|
+
display: "flex",
|
|
842
|
+
flexDirection: "column",
|
|
843
|
+
gap: 9
|
|
844
|
+
},
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.count(preview.counts.workspaces, preview.counts.sessions) }),
|
|
847
|
+
preview.before && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
848
|
+
t.time,
|
|
849
|
+
": ",
|
|
850
|
+
new Date(preview.before).toLocaleString()
|
|
851
|
+
] }),
|
|
852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [
|
|
853
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t.preview }),
|
|
854
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: preview.items?.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
855
|
+
item.title,
|
|
856
|
+
" ",
|
|
857
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: item.id })
|
|
858
|
+
] }, item.id)) }),
|
|
859
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
860
|
+
style: {
|
|
861
|
+
overflowWrap: "anywhere",
|
|
862
|
+
fontSize: 11
|
|
863
|
+
},
|
|
864
|
+
children: [preview.sessionIds?.slice(0, 100).join(", "), preview.sessionIds?.length > 100 ? " …" : ""]
|
|
865
|
+
})
|
|
866
|
+
] }),
|
|
867
|
+
!preview.executable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
868
|
+
role: "alert",
|
|
869
|
+
children: preview.blockedReason
|
|
870
|
+
}),
|
|
871
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t.idle }),
|
|
872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
873
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
874
|
+
type: "checkbox",
|
|
875
|
+
checked: consent,
|
|
876
|
+
disabled: locked,
|
|
877
|
+
onChange: (e) => setConsent(e.target.checked)
|
|
878
|
+
}),
|
|
879
|
+
" ",
|
|
880
|
+
t.consent
|
|
881
|
+
] }),
|
|
882
|
+
operation === "delete" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
883
|
+
t.acknowledgement,
|
|
884
|
+
" ",
|
|
885
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
886
|
+
style,
|
|
887
|
+
value: ack,
|
|
888
|
+
disabled: locked,
|
|
889
|
+
onChange: (e) => setAck(e.target.value)
|
|
890
|
+
})
|
|
891
|
+
] }),
|
|
892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
893
|
+
type: "button",
|
|
894
|
+
style,
|
|
895
|
+
disabled: locked || !consent || !preview.executable || operation === "delete" && ack !== "DELETE",
|
|
896
|
+
onClick: () => void act("execute", {
|
|
897
|
+
planId: preview.planId,
|
|
898
|
+
confirm: true,
|
|
899
|
+
acknowledgement: ack
|
|
900
|
+
}),
|
|
901
|
+
children: t.confirm
|
|
902
|
+
})
|
|
903
|
+
]
|
|
904
|
+
}),
|
|
905
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
906
|
+
role: "status",
|
|
907
|
+
"aria-live": "polite",
|
|
908
|
+
children: [
|
|
909
|
+
t.phase[status.phase] ?? status.phase,
|
|
910
|
+
status.code ? ` · ${status.code}` : "",
|
|
911
|
+
notice ? ` · ${notice}` : ""
|
|
912
|
+
]
|
|
913
|
+
}),
|
|
914
|
+
!connected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
915
|
+
role: "status",
|
|
916
|
+
children: [pending(status.phase) ? t.working : "3210 history API unavailable", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t.recovery })]
|
|
917
|
+
}),
|
|
918
|
+
["RECOVERY_REQUIRED", "QUEUED"].includes(status.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: status.phase === "QUEUED" ? t.cancelQueuedHint : t.recovery }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
919
|
+
type: "button",
|
|
920
|
+
style,
|
|
921
|
+
disabled: busy,
|
|
922
|
+
onClick: () => {
|
|
923
|
+
if (window.confirm(status.phase === "QUEUED" ? t.cancelQueuedHint : t.recovery)) act("recover", { confirm: true });
|
|
924
|
+
},
|
|
925
|
+
children: status.phase === "QUEUED" ? t.cancelQueued : t.recover
|
|
926
|
+
})] }),
|
|
927
|
+
["DONE", "ROLLED_BACK"].includes(status.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
928
|
+
type: "button",
|
|
929
|
+
style,
|
|
930
|
+
onClick: () => window.location.reload(),
|
|
931
|
+
children: t.refresh
|
|
932
|
+
}),
|
|
933
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.archives }),
|
|
934
|
+
archives.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
935
|
+
style: { opacity: .6 },
|
|
936
|
+
children: t.empty
|
|
937
|
+
}),
|
|
938
|
+
archives.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
939
|
+
style: {
|
|
940
|
+
...style,
|
|
941
|
+
display: "flex",
|
|
942
|
+
gap: 8,
|
|
943
|
+
flexWrap: "wrap",
|
|
944
|
+
alignItems: "center"
|
|
945
|
+
},
|
|
946
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
947
|
+
style: { flex: 1 },
|
|
948
|
+
children: a.invalid ? `${a.id} · ${a.code}` : `${new Date(a.createdAt).toLocaleString()} · ${t.count(a.workspaces, a.sessions)}`
|
|
949
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
950
|
+
type: "button",
|
|
951
|
+
style,
|
|
952
|
+
disabled: locked || a.invalid,
|
|
953
|
+
onClick: () => {
|
|
954
|
+
if (window.confirm(t.confirmRestore)) act("restore", {
|
|
955
|
+
archiveId: a.id,
|
|
956
|
+
confirm: true
|
|
957
|
+
});
|
|
958
|
+
},
|
|
959
|
+
children: t.restore
|
|
960
|
+
})]
|
|
961
|
+
}, a.id))
|
|
962
|
+
]
|
|
963
|
+
})]
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
//#endregion
|
|
353
967
|
//#region src/client/index.tsx
|
|
354
968
|
const inject$1 = ["slots", "locale"];
|
|
355
969
|
const API = "/_dsh/dsh-crew";
|
|
356
|
-
const CREW_HARNESS_URL = "http://127.0.0.1:3210/";
|
|
357
970
|
const CREW_CONTROL_PLANE_URL = "http://127.0.0.1:3080/";
|
|
358
971
|
const DEFAULT_ADAPTIVE = {
|
|
359
972
|
enabled: false,
|
|
@@ -377,11 +990,17 @@ const COPY = {
|
|
|
377
990
|
zh: {
|
|
378
991
|
label: "DSH Crew",
|
|
379
992
|
title: "DSH Crew",
|
|
380
|
-
|
|
993
|
+
openOfficial: "打开 3080 官方界面 →",
|
|
994
|
+
intro: "管理工作流、模型路由与宿主集成。展开分区查看和修改设置。",
|
|
995
|
+
surfaceLabel: "3210 / 高级配置",
|
|
381
996
|
integrations: "集成",
|
|
382
997
|
installed: "已安装",
|
|
383
998
|
notInstalled: "未安装",
|
|
384
|
-
ready: "
|
|
999
|
+
ready: "集成就绪",
|
|
1000
|
+
integrationReady: "集成就绪",
|
|
1001
|
+
modelCallable: "模型可调用",
|
|
1002
|
+
modelNotCallable: "模型未验证",
|
|
1003
|
+
modelUnknown: "模型未知",
|
|
385
1004
|
notReady: "未就绪",
|
|
386
1005
|
hud: "HUD 段",
|
|
387
1006
|
install: "安装",
|
|
@@ -408,8 +1027,7 @@ const COPY = {
|
|
|
408
1027
|
providers: "多模态适配器",
|
|
409
1028
|
jobs: "任务状态"
|
|
410
1029
|
},
|
|
411
|
-
|
|
412
|
-
harnessHint: "底层 Provider、Harness Models 与运行时配置",
|
|
1030
|
+
harnessHint: "DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置",
|
|
413
1031
|
hostReadiness: "宿主集成就绪度",
|
|
414
1032
|
hostReadinessHint: "只使用结构化安装与运行时证据;缺少证据不会显示 READY。",
|
|
415
1033
|
readinessLabels: {
|
|
@@ -418,14 +1036,15 @@ const COPY = {
|
|
|
418
1036
|
ds_reviewer: "ds-reviewer",
|
|
419
1037
|
claude_plugin: "Claude plugin",
|
|
420
1038
|
zcode_mcp: "ZCode MCP",
|
|
421
|
-
crew_harness: "Crew
|
|
1039
|
+
crew_harness: "Crew plugin profile",
|
|
422
1040
|
official_bridge: "Official bridge"
|
|
423
1041
|
},
|
|
424
1042
|
readinessStates: {
|
|
425
1043
|
READY: "READY",
|
|
426
1044
|
DEGRADED: "DEGRADED",
|
|
427
1045
|
UNAVAILABLE: "UNAVAILABLE",
|
|
428
|
-
UNKNOWN: "UNKNOWN"
|
|
1046
|
+
UNKNOWN: "UNKNOWN",
|
|
1047
|
+
NOT_APPLICABLE: "N/A"
|
|
429
1048
|
},
|
|
430
1049
|
globalHint: "修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。",
|
|
431
1050
|
orchestration: "Agent 编排",
|
|
@@ -524,7 +1143,7 @@ const COPY = {
|
|
|
524
1143
|
visionModel: "视觉模型",
|
|
525
1144
|
imagegenProvider: "生图 provider",
|
|
526
1145
|
harnessProviders: "Harness Providers",
|
|
527
|
-
harnessProviderHint: "
|
|
1146
|
+
harnessProviderHint: "DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
|
|
528
1147
|
providerState: (state) => `状态:${state}`,
|
|
529
1148
|
providerModels: (count) => `${count} 个模型`,
|
|
530
1149
|
providerJobs: (count) => `${count} 个运行任务`,
|
|
@@ -672,10 +1291,10 @@ const COPY = {
|
|
|
672
1291
|
adaptiveWindow: "健康窗口",
|
|
673
1292
|
adaptiveSamples: "最少样本",
|
|
674
1293
|
adaptiveBoundary: "下一个工作流生效",
|
|
675
|
-
modelActivity: "
|
|
676
|
-
modelActivityHint: "聚合本 Hub 内存中最近 500
|
|
677
|
-
noModelActivity: "
|
|
678
|
-
calls: "
|
|
1294
|
+
modelActivity: "模型活动概览",
|
|
1295
|
+
modelActivityHint: "聚合本 Hub 内存中最近 500 个含真实调用证据的任务;每个模型按任务计数(不是推测底层轮次),最多显示 50 个模型。不保存提示词、结果或凭据。",
|
|
1296
|
+
noModelActivity: "还没有真实模型活动。",
|
|
1297
|
+
calls: "任务数",
|
|
679
1298
|
invocationSource: "任务来源",
|
|
680
1299
|
routingSource: "路由来源",
|
|
681
1300
|
lastCalled: "最近调用",
|
|
@@ -715,11 +1334,17 @@ const COPY = {
|
|
|
715
1334
|
en: {
|
|
716
1335
|
label: "DSH Crew",
|
|
717
1336
|
title: "DSH Crew",
|
|
718
|
-
intro: "
|
|
1337
|
+
intro: "Manage workflows, model routing and host integrations. Expand a section to edit its settings.",
|
|
1338
|
+
surfaceLabel: "3210 / ADVANCED SETTINGS",
|
|
1339
|
+
openOfficial: "Open official frontend (3080) →",
|
|
719
1340
|
integrations: "Integrations",
|
|
720
1341
|
installed: "installed",
|
|
721
1342
|
notInstalled: "not installed",
|
|
722
|
-
ready: "ready",
|
|
1343
|
+
ready: "integration ready",
|
|
1344
|
+
integrationReady: "integration ready",
|
|
1345
|
+
modelCallable: "model callable",
|
|
1346
|
+
modelNotCallable: "model not verified",
|
|
1347
|
+
modelUnknown: "model unknown",
|
|
723
1348
|
notReady: "not ready",
|
|
724
1349
|
hud: "HUD segment",
|
|
725
1350
|
install: "Install",
|
|
@@ -746,8 +1371,7 @@ const COPY = {
|
|
|
746
1371
|
providers: "Multimodal adapters",
|
|
747
1372
|
jobs: "Task status"
|
|
748
1373
|
},
|
|
749
|
-
|
|
750
|
-
harnessHint: "Low-level providers, Harness Models, and runtime configuration",
|
|
1374
|
+
harnessHint: "The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration",
|
|
751
1375
|
hostReadiness: "Host integration readiness",
|
|
752
1376
|
hostReadinessHint: "Uses structured installer and runtime evidence only; missing evidence is never READY.",
|
|
753
1377
|
readinessLabels: {
|
|
@@ -756,14 +1380,15 @@ const COPY = {
|
|
|
756
1380
|
ds_reviewer: "ds-reviewer",
|
|
757
1381
|
claude_plugin: "Claude plugin",
|
|
758
1382
|
zcode_mcp: "ZCode MCP",
|
|
759
|
-
crew_harness: "Crew
|
|
1383
|
+
crew_harness: "Crew plugin profile",
|
|
760
1384
|
official_bridge: "Official bridge"
|
|
761
1385
|
},
|
|
762
1386
|
readinessStates: {
|
|
763
1387
|
READY: "READY",
|
|
764
1388
|
DEGRADED: "DEGRADED",
|
|
765
1389
|
UNAVAILABLE: "UNAVAILABLE",
|
|
766
|
-
UNKNOWN: "UNKNOWN"
|
|
1390
|
+
UNKNOWN: "UNKNOWN",
|
|
1391
|
+
NOT_APPLICABLE: "N/A"
|
|
767
1392
|
},
|
|
768
1393
|
globalHint: "Changes save instantly to ~/.config/dsh-crew/config.json; new CC / Codex sessions pick them up as defaults (override per session with /dsh-crew:config).",
|
|
769
1394
|
orchestration: "Agent orchestration",
|
|
@@ -862,7 +1487,7 @@ const COPY = {
|
|
|
862
1487
|
visionModel: "Vision model",
|
|
863
1488
|
imagegenProvider: "Image-gen provider",
|
|
864
1489
|
harnessProviders: "Harness Providers",
|
|
865
|
-
harnessProviderHint: "Live Provider registration and lifecycle in the
|
|
1490
|
+
harnessProviderHint: "Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.",
|
|
866
1491
|
providerState: (state) => `State: ${state}`,
|
|
867
1492
|
providerModels: (count) => `${count} models`,
|
|
868
1493
|
providerJobs: (count) => `${count} running jobs`,
|
|
@@ -1010,10 +1635,10 @@ const COPY = {
|
|
|
1010
1635
|
adaptiveWindow: "Health window",
|
|
1011
1636
|
adaptiveSamples: "Minimum samples",
|
|
1012
1637
|
adaptiveBoundary: "Effective for the next workflow",
|
|
1013
|
-
modelActivity: "Model
|
|
1014
|
-
modelActivityHint: "Aggregates
|
|
1015
|
-
noModelActivity: "No real model
|
|
1016
|
-
calls: "
|
|
1638
|
+
modelActivity: "Model activity overview",
|
|
1639
|
+
modelActivityHint: "Aggregates the latest 500 in-memory Hub jobs with real model-activity evidence; each model is counted once per qualifying job (not an inferred turn count). Shows at most 50 models; prompts, results, and credentials are never stored here.",
|
|
1640
|
+
noModelActivity: "No real model activity yet.",
|
|
1641
|
+
calls: "Jobs",
|
|
1017
1642
|
invocationSource: "Task source",
|
|
1018
1643
|
routingSource: "Routing source",
|
|
1019
1644
|
lastCalled: "Last called",
|
|
@@ -1323,43 +1948,30 @@ function CollapsibleSection({ sectionId, title, summary, expanded, onToggle, chi
|
|
|
1323
1948
|
"data-section-id": sectionId,
|
|
1324
1949
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1325
1950
|
type: "button",
|
|
1951
|
+
className: "crew-section-trigger",
|
|
1326
1952
|
style: S.sectionButton,
|
|
1327
1953
|
"aria-expanded": expanded,
|
|
1328
1954
|
"aria-controls": bodyId,
|
|
1329
1955
|
onClick: onToggle,
|
|
1330
|
-
children: [
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1342
|
-
|
|
1343
|
-
fontWeight: 650,
|
|
1344
|
-
fontSize: 13,
|
|
1345
|
-
minWidth: 92
|
|
1346
|
-
},
|
|
1956
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1957
|
+
"aria-hidden": "true",
|
|
1958
|
+
style: {
|
|
1959
|
+
width: 14,
|
|
1960
|
+
opacity: .62,
|
|
1961
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
1962
|
+
transition: "transform 140ms ease"
|
|
1963
|
+
},
|
|
1964
|
+
children: "›"
|
|
1965
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1966
|
+
className: "crew-section-heading",
|
|
1967
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1968
|
+
className: "crew-section-title",
|
|
1347
1969
|
children: title
|
|
1348
|
-
}),
|
|
1349
|
-
|
|
1350
|
-
title: summary,
|
|
1351
|
-
style: {
|
|
1352
|
-
flex: 1,
|
|
1353
|
-
minWidth: 0,
|
|
1354
|
-
fontSize: 11.5,
|
|
1355
|
-
opacity: .58,
|
|
1356
|
-
overflow: "hidden",
|
|
1357
|
-
textOverflow: "ellipsis",
|
|
1358
|
-
whiteSpace: "nowrap"
|
|
1359
|
-
},
|
|
1970
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1971
|
+
className: "crew-section-summary",
|
|
1360
1972
|
children: summary
|
|
1361
|
-
})
|
|
1362
|
-
]
|
|
1973
|
+
})]
|
|
1974
|
+
})]
|
|
1363
1975
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1364
1976
|
id: bodyId,
|
|
1365
1977
|
hidden: !expanded,
|
|
@@ -1392,7 +2004,7 @@ function formatTimestamp(value, locale) {
|
|
|
1392
2004
|
}).format(new Date(value));
|
|
1393
2005
|
}
|
|
1394
2006
|
function readinessChip(state) {
|
|
1395
|
-
const color = state === READINESS_STATES.READY ? "#3fb950" : state === READINESS_STATES.DEGRADED ? "#c98735" : state === READINESS_STATES.UNAVAILABLE ? "#f85149" : "inherit";
|
|
2007
|
+
const color = state === READINESS_STATES.READY ? "#3fb950" : state === READINESS_STATES.DEGRADED ? "#c98735" : state === READINESS_STATES.UNAVAILABLE ? "#f85149" : state === READINESS_STATES.NOT_APPLICABLE ? "#7d8590" : "inherit";
|
|
1396
2008
|
return {
|
|
1397
2009
|
fontSize: 10.5,
|
|
1398
2010
|
fontWeight: 650,
|
|
@@ -1407,7 +2019,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
|
|
|
1407
2019
|
const zh = locale === "zh";
|
|
1408
2020
|
const native = surface === CREW_UI_SURFACES.NATIVE;
|
|
1409
2021
|
const title = native ? zh ? "DSH Crew Runtime" : "DSH Crew Runtime" : zh ? "DSH Crew Surface 未确认" : "DSH Crew surface not verified";
|
|
1410
|
-
const hint = native ? zh ? "
|
|
2022
|
+
const hint = native ? zh ? "这里是 DeepSeek 官方 Harness 的专用 dsh-crew profile,DSH Crew 作为插件加载其中。请使用 Harness 原生菜单管理 Provider、Harness Models、Agent 预设与底层配置。" : "This is the dedicated dsh-crew profile of the official DeepSeek Harness, with DSH Crew loaded as a plugin. Use the native Harness menus for Providers, Harness Models, Agent presets, and low-level settings." : zh ? "无法用结构化后端证据确认当前界面,因此已保守隐藏 Crew 编排、任务与宿主集成功能。" : "Structured backend evidence could not identify this surface, so Crew orchestration, jobs, and host integrations are conservatively hidden.";
|
|
1411
2023
|
const runtimeReady = runtime?.ok === true && runtime?.service === "dsh-crew-hub";
|
|
1412
2024
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1413
2025
|
style: {
|
|
@@ -1448,7 +2060,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
|
|
|
1448
2060
|
children: runtimeReady ? `Runtime ${runtime.runtime_version ?? "READY"}` : "Runtime UNKNOWN"
|
|
1449
2061
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1450
2062
|
style: readinessChip(native ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN),
|
|
1451
|
-
children: native ? "3210 ·
|
|
2063
|
+
children: native ? "3210 · official profile + Crew plugin" : "surface · unknown"
|
|
1452
2064
|
})]
|
|
1453
2065
|
}),
|
|
1454
2066
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
@@ -1464,7 +2076,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
|
|
|
1464
2076
|
borderColor: "rgba(74,158,255,0.55)",
|
|
1465
2077
|
background: "rgba(74,158,255,0.08)"
|
|
1466
2078
|
},
|
|
1467
|
-
children: zh ? "
|
|
2079
|
+
children: zh ? "打开官方 Harness 界面(3080) →" : "Open official Harness (3080) →"
|
|
1468
2080
|
})
|
|
1469
2081
|
]
|
|
1470
2082
|
})
|
|
@@ -1498,8 +2110,17 @@ function WorkersPanel({ ctx }) {
|
|
|
1498
2110
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
1499
2111
|
const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
|
|
1500
2112
|
const [surface, setSurface] = (0, react.useState)("detecting");
|
|
1501
|
-
const [
|
|
1502
|
-
const
|
|
2113
|
+
const [readinessEnvelope, setReadinessEnvelope] = (0, react.useState)({});
|
|
2114
|
+
const readinessGeneration = (0, react.useRef)(0);
|
|
2115
|
+
const runtimeInfo = readinessEnvelope.runtime;
|
|
2116
|
+
const readinessSnapshot = readinessEnvelope.snapshot;
|
|
2117
|
+
(0, react.useEffect)(() => {
|
|
2118
|
+
const delay = readinessExpiryDelay(readinessEnvelope.expiresAt);
|
|
2119
|
+
if (delay === null) return void 0;
|
|
2120
|
+
const expected = readinessEnvelope;
|
|
2121
|
+
const timer = setTimeout(() => setReadinessEnvelope((current) => current === expected ? {} : current), delay);
|
|
2122
|
+
return () => clearTimeout(timer);
|
|
2123
|
+
}, [readinessEnvelope]);
|
|
1503
2124
|
const toggleSection = (sectionId) => {
|
|
1504
2125
|
setExpandedSections((current) => ({
|
|
1505
2126
|
...current,
|
|
@@ -1544,7 +2165,10 @@ function WorkersPanel({ ctx }) {
|
|
|
1544
2165
|
/** Every request carries the active locale: the panel is the authority on it
|
|
1545
2166
|
* (DSH's setting may be unset), and the hub localizes its own strings from it. */
|
|
1546
2167
|
const withLang = (0, react.useCallback)((path) => `${API}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
|
|
1547
|
-
const get = (0, react.useCallback)(async (path) => readJson(await fetch(withLang(path), {
|
|
2168
|
+
const get = (0, react.useCallback)(async (path, timeoutMs = 8e3) => readJson(await fetch(withLang(path), {
|
|
2169
|
+
cache: "no-store",
|
|
2170
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2171
|
+
}), path), [readJson, withLang]);
|
|
1548
2172
|
const post = (0, react.useCallback)(async (path, body) => readJson(await fetch(withLang(path), {
|
|
1549
2173
|
method: "POST",
|
|
1550
2174
|
headers: { "content-type": "application/json" },
|
|
@@ -1569,7 +2193,6 @@ function WorkersPanel({ ctx }) {
|
|
|
1569
2193
|
}
|
|
1570
2194
|
};
|
|
1571
2195
|
const [bridgeStatus, runtime] = await Promise.all([readOptional("/bridge-status"), readOptional("/runtime")]);
|
|
1572
|
-
setRuntimeInfo(runtime);
|
|
1573
2196
|
setSurface(classifyCrewSurface({
|
|
1574
2197
|
bridgeStatus,
|
|
1575
2198
|
runtime
|
|
@@ -1579,16 +2202,17 @@ function WorkersPanel({ ctx }) {
|
|
|
1579
2202
|
detectSurface();
|
|
1580
2203
|
}, [detectSurface]);
|
|
1581
2204
|
const refreshAll = (0, react.useCallback)(async () => {
|
|
2205
|
+
const generation = ++readinessGeneration.current;
|
|
2206
|
+
setReadinessEnvelope({});
|
|
1582
2207
|
try {
|
|
1583
|
-
const [j, s, c, pr, pi, ph, cr
|
|
2208
|
+
const [j, s, c, pr, pi, ph, cr] = await Promise.all([
|
|
1584
2209
|
get("/jobs"),
|
|
1585
2210
|
get("/install/status"),
|
|
1586
2211
|
get("/config"),
|
|
1587
2212
|
get("/presets"),
|
|
1588
2213
|
get("/providers").catch(() => null),
|
|
1589
2214
|
get("/provider-health").catch(() => null),
|
|
1590
|
-
get("/credential-references").catch(() => null)
|
|
1591
|
-
get("/extension").catch(() => null)
|
|
2215
|
+
get("/credential-references").catch(() => null)
|
|
1592
2216
|
]);
|
|
1593
2217
|
if (j.ok) setJobs(j.jobs ?? []);
|
|
1594
2218
|
if (s.ok) setStatus(s.status);
|
|
@@ -1606,19 +2230,44 @@ function WorkersPanel({ ctx }) {
|
|
|
1606
2230
|
setCredentialRefs(cr.records ?? []);
|
|
1607
2231
|
setCredentialUnverified(cr.unverified_purges ?? []);
|
|
1608
2232
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
2233
|
+
} catch {
|
|
2234
|
+
if (generation === readinessGeneration.current) setReadinessEnvelope({});
|
|
2235
|
+
}
|
|
1611
2236
|
}, [get]);
|
|
1612
2237
|
(0, react.useEffect)(() => {
|
|
1613
2238
|
if (!surfaceResponsibilities(surface).fullControlPlane) return void 0;
|
|
1614
2239
|
refreshAll();
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2240
|
+
let cancelled = false;
|
|
2241
|
+
let timer;
|
|
2242
|
+
const poll = async () => {
|
|
2243
|
+
const generation = ++readinessGeneration.current;
|
|
2244
|
+
try {
|
|
2245
|
+
const ext = await get("/extension", 5e3);
|
|
2246
|
+
if (!cancelled && generation === readinessGeneration.current) {
|
|
2247
|
+
const accepted = acceptReadinessResponse(ext, {
|
|
2248
|
+
generation,
|
|
2249
|
+
latestGeneration: readinessGeneration.current
|
|
2250
|
+
});
|
|
2251
|
+
if (accepted.accepted) setReadinessEnvelope(accepted.envelope);
|
|
2252
|
+
}
|
|
2253
|
+
} catch {
|
|
2254
|
+
if (!cancelled && generation === readinessGeneration.current) setReadinessEnvelope({});
|
|
2255
|
+
}
|
|
2256
|
+
Promise.all([get("/jobs", 5e3).catch(() => null), get("/provider-health", 5e3).catch(() => null)]).then(([j, health]) => {
|
|
2257
|
+
if (cancelled) return;
|
|
2258
|
+
if (j?.ok) setJobs(j.jobs ?? []);
|
|
1618
2259
|
if (health?.ok) setProviderHealth(health.health ?? []);
|
|
1619
2260
|
}).catch(() => {});
|
|
1620
|
-
|
|
1621
|
-
|
|
2261
|
+
if (!cancelled) timer = setTimeout(() => {
|
|
2262
|
+
poll();
|
|
2263
|
+
}, 3e3);
|
|
2264
|
+
};
|
|
2265
|
+
poll();
|
|
2266
|
+
return () => {
|
|
2267
|
+
cancelled = true;
|
|
2268
|
+
readinessGeneration.current += 1;
|
|
2269
|
+
if (timer) clearTimeout(timer);
|
|
2270
|
+
};
|
|
1622
2271
|
}, [
|
|
1623
2272
|
surface,
|
|
1624
2273
|
refreshAll,
|
|
@@ -2252,7 +2901,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2252
2901
|
}),
|
|
2253
2902
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2254
2903
|
style: S.chip(ready),
|
|
2255
|
-
children: ready ? `● ${copy.
|
|
2904
|
+
children: ready ? `● ${copy.integrationReady}` : `○ ${copy.notReady}`
|
|
2256
2905
|
}),
|
|
2257
2906
|
extra,
|
|
2258
2907
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
@@ -2316,6 +2965,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2316
2965
|
} } });
|
|
2317
2966
|
};
|
|
2318
2967
|
const modelActivity = aggregateModelInvocations(jobs);
|
|
2968
|
+
const modelState = modelCallabilityState(readinessSnapshot?.model_callability, runtimeInfo);
|
|
2319
2969
|
const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
|
|
2320
2970
|
const hostReadiness = projectHostReadiness({
|
|
2321
2971
|
installStatus: status,
|
|
@@ -2329,6 +2979,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2329
2979
|
runtime: runtimeInfo
|
|
2330
2980
|
});
|
|
2331
2981
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2982
|
+
className: "dsh-crew-ui",
|
|
2332
2983
|
style: {
|
|
2333
2984
|
display: "flex",
|
|
2334
2985
|
flexDirection: "column",
|
|
@@ -2337,102 +2988,39 @@ function WorkersPanel({ ctx }) {
|
|
|
2337
2988
|
lineHeight: 1.55
|
|
2338
2989
|
},
|
|
2339
2990
|
children: [
|
|
2340
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
},
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
marginTop: 2
|
|
2370
|
-
},
|
|
2371
|
-
children: copy.intro
|
|
2372
|
-
}),
|
|
2373
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2374
|
-
style: {
|
|
2375
|
-
display: "flex",
|
|
2376
|
-
gap: 6,
|
|
2377
|
-
marginTop: 8,
|
|
2378
|
-
flexWrap: "wrap"
|
|
2379
|
-
},
|
|
2380
|
-
children: [
|
|
2381
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2382
|
-
style: S.chip(!!status?.codex?.ready),
|
|
2383
|
-
children: ["Codex ", status?.codex?.ready ? "READY" : "CHECK"]
|
|
2384
|
-
}),
|
|
2385
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2386
|
-
style: S.chip(!!status?.claude?.ready),
|
|
2387
|
-
children: ["Claude ", status?.claude?.ready ? "READY" : "CHECK"]
|
|
2388
|
-
}),
|
|
2389
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2390
|
-
style: S.chip(!!status?.zcode?.ready),
|
|
2391
|
-
children: ["ZCode ", status?.zcode?.ready ? "READY" : "CHECK"]
|
|
2392
|
-
}),
|
|
2393
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2394
|
-
style: S.chip(jobs.some((job) => job.status === "running")),
|
|
2395
|
-
children: [jobs.filter((job) => job.status === "running").length, " running"]
|
|
2396
|
-
})
|
|
2397
|
-
]
|
|
2398
|
-
})
|
|
2399
|
-
]
|
|
2400
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2401
|
-
style: {
|
|
2402
|
-
display: "flex",
|
|
2403
|
-
flexDirection: "column",
|
|
2404
|
-
alignItems: "flex-end",
|
|
2405
|
-
gap: 3
|
|
2406
|
-
},
|
|
2407
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2408
|
-
href: CREW_HARNESS_URL,
|
|
2409
|
-
target: "_blank",
|
|
2410
|
-
rel: "noopener noreferrer",
|
|
2411
|
-
style: {
|
|
2412
|
-
...S.btn,
|
|
2413
|
-
textDecoration: "none",
|
|
2414
|
-
fontWeight: 650,
|
|
2415
|
-
padding: "7px 12px",
|
|
2416
|
-
borderColor: "rgba(74,158,255,0.55)",
|
|
2417
|
-
background: "rgba(74,158,255,0.10)"
|
|
2418
|
-
},
|
|
2419
|
-
children: copy.openHarness
|
|
2420
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2421
|
-
style: {
|
|
2422
|
-
fontSize: 10.5,
|
|
2423
|
-
opacity: .52
|
|
2424
|
-
},
|
|
2425
|
-
children: copy.harnessHint
|
|
2426
|
-
})]
|
|
2427
|
-
})]
|
|
2991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelStyles, {}),
|
|
2992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PanelHeader, {
|
|
2993
|
+
title: copy.title,
|
|
2994
|
+
eyebrow: copy.surfaceLabel,
|
|
2995
|
+
description: copy.intro,
|
|
2996
|
+
href: CREW_CONTROL_PLANE_URL,
|
|
2997
|
+
linkText: copy.openOfficial,
|
|
2998
|
+
children: [
|
|
2999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3000
|
+
style: S.chip(status?.codex?.installed === true),
|
|
3001
|
+
children: `Codex ${status?.codex?.installed === true ? copy.installed : copy.notInstalled}`
|
|
3002
|
+
}),
|
|
3003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3004
|
+
style: S.chip(status?.claude?.installed === true),
|
|
3005
|
+
children: `Claude ${status?.claude?.installed === true ? copy.installed : copy.notInstalled}`
|
|
3006
|
+
}),
|
|
3007
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3008
|
+
style: S.chip(status?.zcode?.installed === true),
|
|
3009
|
+
children: `ZCode ${status?.zcode?.installed === true ? copy.installed : copy.notInstalled}`
|
|
3010
|
+
}),
|
|
3011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3012
|
+
style: readinessChip(modelState),
|
|
3013
|
+
children: modelState === READINESS_STATES.READY ? copy.modelCallable : modelState === READINESS_STATES.UNKNOWN ? copy.modelUnknown : copy.modelNotCallable
|
|
3014
|
+
}),
|
|
3015
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3016
|
+
style: S.chip(jobs.some((job) => job.status === "running")),
|
|
3017
|
+
children: copy.runningCount(jobs.filter((job) => job.status === "running").length)
|
|
3018
|
+
})
|
|
3019
|
+
]
|
|
2428
3020
|
}),
|
|
3021
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryPanel, { locale }),
|
|
2429
3022
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2430
|
-
|
|
2431
|
-
display: "flex",
|
|
2432
|
-
alignItems: "center",
|
|
2433
|
-
gap: 7,
|
|
2434
|
-
marginTop: 5
|
|
2435
|
-
},
|
|
3023
|
+
className: "crew-config-toolbar",
|
|
2436
3024
|
children: [
|
|
2437
3025
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2438
3026
|
style: {
|
|
@@ -2465,7 +3053,11 @@ function WorkersPanel({ ctx }) {
|
|
|
2465
3053
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2466
3054
|
sectionId: "integrations",
|
|
2467
3055
|
title: copy.sectionNames.integrations,
|
|
2468
|
-
summary: sectionSummary(
|
|
3056
|
+
summary: sectionSummary(...[
|
|
3057
|
+
"Codex",
|
|
3058
|
+
"Claude",
|
|
3059
|
+
"ZCode"
|
|
3060
|
+
].map((name) => `${name} ${status?.[name.toLowerCase()]?.installed ? copy.installed : copy.notInstalled}`), modelState === READINESS_STATES.READY ? copy.modelCallable : copy.modelNotCallable),
|
|
2469
3061
|
expanded: !!expandedSections.integrations,
|
|
2470
3062
|
onToggle: () => toggleSection("integrations"),
|
|
2471
3063
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4730,7 +5322,7 @@ function WorkersPanel({ ctx }) {
|
|
|
4730
5322
|
children: [
|
|
4731
5323
|
entry.selection_sources.join(" · "),
|
|
4732
5324
|
" · ",
|
|
4733
|
-
entry.roles.join(" / ")
|
|
5325
|
+
entry.roles.length > 0 ? entry.roles.join(" / ") : copy.never
|
|
4734
5326
|
]
|
|
4735
5327
|
})]
|
|
4736
5328
|
}),
|