@ran-sh/dsh-crew 1.10.7 → 1.10.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-crew",
3
- "version": "1.10.7",
3
+ "version": "1.10.9",
4
4
  "description": "Dispatch subtasks to DeepSeek Harness (DSH) agents as native subagents with live progress",
5
5
  "author": {
6
6
  "name": "ZSeven-W"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ran-sh/dsh-crew",
3
- "version": "1.10.7",
3
+ "version": "1.10.9",
4
4
  "type": "module",
5
5
  "main": "./src/hub/entry.mjs",
6
6
  "bin": {
@@ -1,8 +1,9 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { pathToFileURL } from 'node:url';
6
+ import { renameTree } from './tree-move.mjs';
6
7
 
7
8
  function plan(home, root) {
8
9
  const bridge = join(root, 'official-web-bridge');
@@ -61,13 +62,17 @@ export function installOfficialFrontendAssets({ home = homedir(), root } = {}) {
61
62
  mkdirSync(dirname(file), { recursive: true });
62
63
  writeFileSync(file, bytes);
63
64
  }
64
- try { renameSync(stage, p.snapshotRoot); stage = null; }
65
+ // Windows can refuse this rename for a moment when something else holds a
66
+ // handle under the destination, and the refusal is not about the rename:
67
+ // the same call succeeds shortly after. Producing the snapshot first in a
68
+ // concurrent installer is the other outcome this tolerates.
69
+ try { renameTree(stage, p.snapshotRoot); stage = null; }
65
70
  catch (error) { if (!matches(p)) throw error; }
66
71
  }
67
72
  if (!matches(p)) throw new Error('frontend snapshot conflicts with its revision');
68
73
  temp = `${p.overlayFile}.${randomUUID()}.tmp`;
69
74
  writeFileSync(temp, p.overlay, { flag: 'wx', mode: 0o600 });
70
- renameSync(temp, p.overlayFile); temp = null;
75
+ renameTree(temp, p.overlayFile); temp = null;
71
76
  return { ok: true, changed: true, revision: p.revision, snapshotRoot: p.snapshotRoot, overlayFile: p.overlayFile };
72
77
  } catch (error) {
73
78
  return { ok: false, code: 'OFFICIAL_FRONTEND_ASSETS_FAILED', error: error.message };
@@ -222,6 +222,15 @@ export function buildMcpWorkflowRuntime(deps) {
222
222
  source,
223
223
  preset,
224
224
  delivery,
225
+ // Isolation belongs to this client: it already allocated the workspace
226
+ // and `spec.cwd` is the result. Saying so keeps the hub executing in that
227
+ // cwd instead of resolving an isolation of its own and allocating a
228
+ // second workspace — which put the work somewhere the client was not
229
+ // looking and captured no candidate for.
230
+ requested_isolation: 'shared',
231
+ // The same authorization the client was given, so the hub judges the
232
+ // workspace evidence against the same task contract.
233
+ allow_no_changes: spec.allow_no_changes === true,
225
234
  });
226
235
  spec.onAttemptStarted?.(spawned.id);
227
236
 
@@ -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 = '1.10.7';
39
+ export const RUNTIME_VERSION = '1.10.9';
40
40
  export const HUB_PROTOCOL_VERSION = 1;
41
41
 
42
42
  export const HUB_CAPABILITIES = Object.freeze([
@@ -38,8 +38,23 @@ function lines(value) {
38
38
  return value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
39
39
  }
40
40
 
41
+ // A reviewer writes its verdict as one line, and it decorates that line the way
42
+ // prose is decorated: bolded, bulleted, quoted, or introduced by a label. The
43
+ // contract asks for "approved / needs changes / rejected", and every one of
44
+ // those decorations used to fall through to `inconclusive` — which blocks
45
+ // acceptance, so turning on the automatic reviewer could reject correct work
46
+ // because the reviewer wrote **Approved** instead of Approved.
47
+ function stripVerdictDecoration(value) {
48
+ return String(value ?? '')
49
+ .replace(/^[\s>*_`#-]+/, '')
50
+ .replace(/^\**\s*(?:final\s+)?verdict\s*[:\-—]\s*/i, '')
51
+ .replace(/^[\s>*_`#-]+/, '')
52
+ .replace(/[\s*_`]+$/, '')
53
+ .trim();
54
+ }
55
+
41
56
  export function normalizeReviewVerdict(value) {
42
- const s = String(value ?? '').trim().toLowerCase();
57
+ const s = stripVerdictDecoration(value).toLowerCase();
43
58
  if (/^(approve|approved|pass)\b/.test(s) || s === 'pass' || s === 'approve') return 'approve';
44
59
  if (/request.*chang|chang.*request|reject|needs changes/i.test(s)) return 'request_changes';
45
60
  return 'inconclusive';
@@ -382,6 +397,10 @@ export function createWorkflowRuntime(adapters, {
382
397
  cwd: job.execution_cwd,
383
398
  effort: job.effort,
384
399
  timeout_seconds: job.timeout_seconds,
400
+ // Passed through so the executor judges the workspace evidence against
401
+ // the same task contract this workflow was given, instead of against a
402
+ // default that would refuse an authorization the caller granted.
403
+ allow_no_changes: job.allow_no_changes === true,
385
404
  policy,
386
405
  source: job.source,
387
406
  model_class_hint: job.model_class_hint,
package/src/workflow.mjs CHANGED
@@ -81,12 +81,18 @@ const NO_CHANGE_ASSERTION_RE = new RegExp(
81
81
  function isNoChangeAssertion(line) {
82
82
  const normalized = line
83
83
  .replace(/^(?:[-*+]\s+)+/, '')
84
- .replace(/[`"'“”‘’]/g, '')
84
+ .replace(/[`"'“”‘’*_]/g, '')
85
85
  .replace(/[.!。!]+$/, '')
86
86
  .trim()
87
87
  .toLowerCase();
88
88
  if (normalized === '') return false;
89
- return NO_CHANGE_ASSERTION_RE.test(normalized);
89
+ // The assertion is usually a whole line, but a report may also end with it —
90
+ // "**Final state: no files changed.**" is the same declaration stated as a
91
+ // summary. Testing each sentence and clause is what finds it without matching
92
+ // a phrase that merely appears inside an unrelated one.
93
+ return normalized
94
+ .split(/\s*(?:[.:;,—–()\[\]]|->)\s*/)
95
+ .some((fragment) => fragment !== '' && NO_CHANGE_ASSERTION_RE.test(fragment.trim()));
90
96
  }
91
97
 
92
98
  // Whether the report claims the workspace holds changes. A report that asserts