@ran-sh/dsh-crew 2.2.8 → 2.2.9
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/package.json
CHANGED
|
@@ -1247,26 +1247,35 @@ export function installHudSegment({ home = homedir() } = {}) {
|
|
|
1247
1247
|
return { ok: true, actions: [...(bak ? [`backup: ${bak}`] : []), 'statusLine: claude-hud now runs worker-segment.sh via --extra-cmd'] };
|
|
1248
1248
|
}
|
|
1249
1249
|
|
|
1250
|
-
// Crew
|
|
1251
|
-
//
|
|
1252
|
-
//
|
|
1253
|
-
//
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1250
|
+
// The paths Crew has recorded as its own in this Claude host: the marketplace records
|
|
1251
|
+
// it wrote (under the current and pre-rename names, in settings and in the CLI's own
|
|
1252
|
+
// cache), the constant an older installer used, and the checkout this module runs
|
|
1253
|
+
// from. Only these are grounds for removing anything: a directory that merely happens
|
|
1254
|
+
// to be named `dsh-crew` belongs to whoever made it, and a path that is not recorded
|
|
1255
|
+
// here cannot be identified as Crew's, so it is left alone.
|
|
1256
|
+
function crewOwnedRoots({ home, settings, markets, installed }) {
|
|
1257
|
+
const roots = [];
|
|
1258
|
+
const push = (value) => { if (typeof value === 'string' && value.trim() && !roots.includes(value.trim())) roots.push(value.trim()); };
|
|
1259
|
+
for (const name of CREW_CLAUDE_MARKETPLACES) {
|
|
1260
|
+
push(settings?.extraKnownMarketplaces?.[name]?.source?.path);
|
|
1261
|
+
push(markets?.[name]?.source?.path);
|
|
1262
|
+
push(markets?.[name]?.installLocation);
|
|
1263
|
+
}
|
|
1264
|
+
for (const key of CREW_CLAUDE_PLUGIN_KEYS) {
|
|
1265
|
+
const entries = Array.isArray(installed?.plugins?.[key]) ? installed.plugins[key] : [installed?.plugins?.[key]];
|
|
1266
|
+
for (const entry of entries) push(entry?.installPath);
|
|
1267
|
+
}
|
|
1268
|
+
push(join(home, '.config', 'dsh-crew', 'marketplace'));
|
|
1269
|
+
push(ROOT);
|
|
1270
|
+
return roots;
|
|
1260
1271
|
}
|
|
1261
1272
|
|
|
1262
|
-
|
|
1263
|
-
// Crew's own script, in Crew's own directory, in the exact shape the installer
|
|
1264
|
-
// writes (`bash <root>/statusline/statusline.sh`). A command that merely mentions
|
|
1265
|
-
// these words belongs to whoever wrote it.
|
|
1266
|
-
function crewOwnedStatusLine(value) {
|
|
1273
|
+
function crewOwnedStatusLine(value, roots) {
|
|
1267
1274
|
const command = typeof value?.command === 'string' ? value.command.trim() : '';
|
|
1268
|
-
const match = /^bash\s+(.+
|
|
1269
|
-
|
|
1275
|
+
const match = /^bash\s+(.+)$/i.exec(command);
|
|
1276
|
+
if (!match) return false;
|
|
1277
|
+
const path = normalizedPath(match[1].trim());
|
|
1278
|
+
return path !== null && roots.some((root) => normalizedPath(join(root, 'statusline', 'statusline.sh')) === path);
|
|
1270
1279
|
}
|
|
1271
1280
|
|
|
1272
1281
|
// The claude CLI records, per marketplace and plugin, what it materialized, in
|
|
@@ -1304,16 +1313,19 @@ function removeCrewClaudeCacheRecords({ home, actions }) {
|
|
|
1304
1313
|
export function uninstallClaudeCode({ home = homedir() } = {}) {
|
|
1305
1314
|
const actions = [];
|
|
1306
1315
|
const settingsFile = join(home, '.claude', 'settings.json');
|
|
1316
|
+
const cacheDir = join(home, '.claude', 'plugins');
|
|
1307
1317
|
const settings = readJson(settingsFile, null);
|
|
1318
|
+
const markets = readJson(join(cacheDir, 'known_marketplaces.json'), null);
|
|
1319
|
+
const installed = readJson(join(cacheDir, 'installed_plugins.json'), null);
|
|
1320
|
+
const roots = crewOwnedRoots({ home, settings, markets, installed });
|
|
1308
1321
|
if (settings) {
|
|
1309
1322
|
backup(settingsFile);
|
|
1310
|
-
const mpDir = join(home, '.config', 'dsh-crew', 'marketplace');
|
|
1311
1323
|
if (Array.isArray(settings.extraKnownMarketplaces)) {
|
|
1312
|
-
// Legacy array shape:
|
|
1313
|
-
//
|
|
1314
|
-
//
|
|
1324
|
+
// Legacy array shape: an entry is removed only when it IS one of the paths Crew
|
|
1325
|
+
// recorded as its own. What the path is spelled with is not evidence — a
|
|
1326
|
+
// marketplace under someone else's `dsh-crew` directory stays.
|
|
1315
1327
|
settings.extraKnownMarketplaces = settings.extraKnownMarketplaces
|
|
1316
|
-
.filter((entry) => !
|
|
1328
|
+
.filter((entry) => !roots.some((root) => normalizedPath(entry?.path) === normalizedPath(root)));
|
|
1317
1329
|
} else if (settings.extraKnownMarketplaces) {
|
|
1318
1330
|
for (const name of CREW_CLAUDE_MARKETPLACES) delete settings.extraKnownMarketplaces[name];
|
|
1319
1331
|
}
|
|
@@ -1326,7 +1338,7 @@ export function uninstallClaudeCode({ home = homedir() } = {}) {
|
|
|
1326
1338
|
settings.permissions.allow = settings.permissions.allow.filter((rule) => typeof rule !== 'string'
|
|
1327
1339
|
|| !CREW_CLAUDE_PERMISSION_PREFIXES.some((prefix) => rule.startsWith(prefix)));
|
|
1328
1340
|
}
|
|
1329
|
-
if (crewOwnedStatusLine(settings.statusLine)) {
|
|
1341
|
+
if (crewOwnedStatusLine(settings.statusLine, roots)) {
|
|
1330
1342
|
delete settings.statusLine;
|
|
1331
1343
|
actions.push('statusLine: removed (Crew-installed)');
|
|
1332
1344
|
}
|
package/src/log-prune.mjs
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
// new dsh-crew-dsh-crew-3210-<stamp>.out/.err.log pair, every desktop launch a
|
|
3
3
|
// dsh-crew-web-*, every official launch a dsh-official-web-*, and the launcher
|
|
4
4
|
// appends to dsh-crew-launcher.log forever. They are the only post-mortem evidence
|
|
5
|
-
// for a crash-before-readiness, so the rule
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// for a crash-before-readiness, so the rule bounds them instead of deleting them all.
|
|
6
|
+
// A run survives when it is BOTH within its family's newest `keep` AND younger than
|
|
7
|
+
// the age bound; exceeding either one removes it, unless it was written within the
|
|
8
|
+
// recency guard (a running process owns its own log files). The newest `keep` runs
|
|
9
|
+
// therefore do not survive unconditionally — only a run that is also inside the age
|
|
10
|
+
// bound does, which is what keeps a burst of starts from filling the directory.
|
|
8
11
|
|
|
9
12
|
import { readdirSync, rmSync, statSync } from 'node:fs';
|
|
10
13
|
import { join } from 'node:path';
|
package/src/readiness-matrix.mjs
CHANGED
|
@@ -76,8 +76,17 @@ function normalizeEvidence(row, evidence) {
|
|
|
76
76
|
// flip the row is how "this route is unused" would turn into "this route passed".
|
|
77
77
|
// What was reported is kept as metadata rather than thrown away.
|
|
78
78
|
if (row.status === 'NOT_APPLICABLE') {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// The same sanitized fields an applied record keeps, so "the observation is not
|
|
80
|
+
// lost" is a statement the code supports rather than a claim about three of five.
|
|
81
|
+
const source = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
82
|
+
const reported = {
|
|
83
|
+
status: evidence.status,
|
|
84
|
+
...(source(evidence.reason_code) ? { reason_code: source(evidence.reason_code) } : {}),
|
|
85
|
+
...(source(evidence.evidence_source) ? { evidence_source: source(evidence.evidence_source) } : {}),
|
|
86
|
+
...(source(evidence.evidence_ref) ? { evidence_ref: source(evidence.evidence_ref) } : {}),
|
|
87
|
+
...(source(evidence.detail_code) && /^[A-Z][A-Z0-9_]{0,127}$/u.test(evidence.detail_code) ? { detail_code: evidence.detail_code } : {}),
|
|
88
|
+
};
|
|
89
|
+
return { ...row, reported_evidence: reported };
|
|
81
90
|
}
|
|
82
91
|
const reason = typeof evidence.reason_code === 'string' && evidence.reason_code.trim()
|
|
83
92
|
? evidence.reason_code.trim()
|
|
@@ -195,7 +204,7 @@ export function buildReadinessMatrix({
|
|
|
195
204
|
const selectionKnown = typeof workerSelection?.provider === 'string' && workerSelection.provider.length > 0;
|
|
196
205
|
const deepseekRouteSelected = workerProviderMode === 'deepseek-official'
|
|
197
206
|
|| workerSelection?.provider === 'deepseek-official';
|
|
198
|
-
if (workerProviderMode
|
|
207
|
+
if (workerProviderMode === 'follow-dsh' && selectionKnown && !deepseekRouteSelected) {
|
|
199
208
|
for (const id of POLICY_NOT_APPLICABLE_ROWS) {
|
|
200
209
|
rows[id] = baseRow(id, 'real-execution', 'NOT_APPLICABLE', READINESS_REASON_CODES.WORKER_PROVIDER_FOLLOWS_DSH, 'runtime-policy');
|
|
201
210
|
}
|
package/src/runtime-identity.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export {
|
|
|
36
36
|
// included in the identity contract.
|
|
37
37
|
const RUNTIME_ID = randomUUID();
|
|
38
38
|
|
|
39
|
-
export const RUNTIME_VERSION = '2.2.
|
|
39
|
+
export const RUNTIME_VERSION = '2.2.9';
|
|
40
40
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
41
41
|
|
|
42
42
|
export const HUB_CAPABILITIES = Object.freeze([
|
|
@@ -108,12 +108,14 @@ function projectModelRole({ role, selected, health, jobs, runtime, now, executio
|
|
|
108
108
|
if (healthCurrent && matchingHealth.state === 'callable') {
|
|
109
109
|
return { state: 'CALLABLE', reason_code: matchingHealth.reason_code ?? 'PROVIDER_CALLABLE', selected, observed_at: matchingHealth.observed_at ?? null, expires_at: expiresAt, source: 'provider_health', last_success: null };
|
|
110
110
|
}
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
111
|
+
// Callability asks for positive proof that the provider answered, so the ledger
|
|
112
|
+
// fact that proves it is the one that says the execution ended normally: a job the
|
|
113
|
+
// worker recorded as `done` whose `execution_status` is `completed`. The task-level
|
|
114
|
+
// verdict is deliberately NOT part of this — a `partial` task still had a provider
|
|
115
|
+
// response — and a job whose execution failed or was never recorded is not evidence
|
|
116
|
+
// of a response, however its task was later classified. A recovery probe or a real
|
|
117
|
+
// run is what a route without such a job needs.
|
|
118
|
+
const responded = (job) => job?.execution_status === 'completed';
|
|
117
119
|
const completed = (Array.isArray(jobs) ? jobs : [])
|
|
118
120
|
.filter((job) => job?.role === role && job?.provider === selected.provider && job?.model === selected.model
|
|
119
121
|
&& job?.status === 'done' && responded(job)
|