better-dsh 0.2.2-a → 0.2.2-c
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/docs/50_test-reports/2026-09-06-write/345/267/245/345/205/267sandbox/345/215/207/347/272/247/351/200/217/344/274/240bug/345/244/215/345/217/221/345/217/212/346/214/202/350/265/267-/344/272/213/344/273/266/346/212/245/345/221/212.md +43 -0
- package/docs/50_test-reports/v0.2.4-ios-focus-zoom-suppression/345/256/236/346/265/213/346/212/245/345/221/212.md +158 -0
- package/docs/60_exploration-and-research/01-cordis-runtime/bun-compile-cordis-runtime-bootstrap-research.md +536 -0
- package/docs/60_exploration-and-research/01-cordis-runtime/cordis-customization-and-override-mechanics.md +418 -0
- package/docs/60_exploration-and-research/01-cordis-runtime/cordis-research.md +350 -0
- package/docs/60_exploration-and-research/01-cordis-runtime/dsh-cordis-hotplug-mcp-patch-research.md +265 -0
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-web-profile-package-map.md +186 -0
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-web-ui-slot-system-research.md +310 -0
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-backend-data-inventory.md +633 -0
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-strip-boundary-research.md +300 -0
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-wire-appendix.md +3729 -0
- package/docs/60_exploration-and-research/02-dsh-webui/web-frontend-composability-research.md +191 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/capture-live-turn.json +1 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/observed-endpoints.json +224 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/remote-inventory.json +110954 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/served-index-sample.html +47 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/session-events.json +9411 -0
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/ws-frame-examples.json +20 -0
- package/docs/60_exploration-and-research/03-mobile-ios/dsh-mobile-spa-ios-input-experience-research.md +160 -0
- package/docs/60_exploration-and-research/03-mobile-ios/ios-chat-app-bridge-research.md +324 -0
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-jsonl-mapping.md +1532 -0
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode-failed.json +46 -0
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode1.json +1124 -0
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode2.json +1240 -0
- package/docs/60_exploration-and-research/05-dashr-dev/plugin-development.md +148 -0
- package/docs/60_exploration-and-research/05-dashr-dev/upstream-alignment.md +102 -0
- package/docs/60_exploration-and-research/README.md +87 -0
- package/docs/60_exploration-and-research/bun-compile-cordis-runtime-bootstrap-research.md +348 -0
- package/docs/60_exploration-and-research/dsh-mobile-spa-ios-input-experience-research.md +160 -0
- package/lib/index.d.ts +9 -1
- package/lib/index.js +258 -6
- package/package.json +1 -1
- package/docs/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +0 -110
- package/docs/adr/0001-bridge-tool-layer-not-service-layer.md +0 -14
- package/docs/adr/0002-masking-is-presentation-only.md +0 -15
- package/docs/plans/A2A-messaging-channel-test-archive.md +0 -256
- package/docs/plans/code-mode-vs-rlm-ipython-comparison.md +0 -137
- package/docs/plans/dashr-blueprint-review.md +0 -201
- package/docs/plans/dashr-blueprint.md +0 -561
- package/docs/plans/dashr-compaction-window-and-archive.md +0 -307
- package/docs/plans/dashr-profile-layer-feasibility.md +0 -367
- package/docs/plans/dashr-sandbox-escalation-semantics-gap.md +0 -171
- package/docs/plans/dashr-security-sandbox-analysis.md +0 -187
- package/docs/plans/dashr-surface-invariant-and-omp-imports.md +0 -97
- package/docs/plans/ipython-kernel-interactive-interface-test-report.md +0 -152
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +0 -146
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +0 -50
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +0 -79
- package/docs/plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +0 -138
- package/docs/plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +0 -161
- package/docs/plans/kernel-refactoring/V0.1.5-development-plan.md +0 -109
- package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +0 -50
- package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +0 -113
- package/docs/plans/recallable-compaction.md +0 -147
- package/docs/plans/spike-tag-repro.mjs +0 -102
- package/docs/plans/upstream-analysis.md +0 -128
- package/docs/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -142
- package/docs/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -193
- package/docs/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -96
- package/docs/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -127
- package/docs/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -150
- package/docs/v0.1.8d_artifacts/README.md +0 -138
- package/docs/v0.1.8d_artifacts/code-mode-repl-only.observation.md +0 -74
- package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +0 -3890
- package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +0 -544
- package/docs/v0.1.8d_artifacts/functions.json +0 -592
- package/docs/v0.1.8d_artifacts/skills-catalog.snapshot.md +0 -30
- package/docs/v0.1.8d_artifacts/tools-sdk.output-schemas.json +0 -1236
- package/docs/v0.1.8d_artifacts/tools-sdk.python.txt +0 -592
- package/docs/v0.1.8d_artifacts/tools-sdk.typescript.txt +0 -516
- package/docs/v0.1.8d_artifacts/wire-vs-transcription.diff.md +0 -54
- package/docs/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -224
- package/docs/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -168
- package/docs/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -123
- package/docs/v0.2.0b_artifacts/f2probe/Cargo.lock +0 -7
- package/docs/v0.2.0b_artifacts/f2probe/Cargo.toml +0 -6
- package/docs/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +0 -8
- package/docs/v0.2.0b_artifacts/f2probe/src/main.rs +0 -4
- package/docs/v0.2.0b_artifacts/hashline-probe.md +0 -5
- package/docs/v0.2.0b_artifacts/slowprobe/Cargo.lock +0 -7
- package/docs/v0.2.0b_artifacts/slowprobe/Cargo.toml +0 -7
- package/docs/v0.2.0b_artifacts/slowprobe/build.rs +0 -4
- package/docs/v0.2.0b_artifacts/slowprobe/src/main.rs +0 -13
- package/docs/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -110
- package/docs/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -86
- package/docs/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +0 -66
package/lib/index.js
CHANGED
|
@@ -8319,7 +8319,7 @@ async function defaultSchemeWrite(scheme, path$1, content) {
|
|
|
8319
8319
|
* write definition with args and exec passed through untouched.
|
|
8320
8320
|
*/
|
|
8321
8321
|
function createWriteTool(deps) {
|
|
8322
|
-
const { nativeWrite, postWrite, preWriteFormat } = deps;
|
|
8322
|
+
const { nativeWrite, postWrite, preWriteFormat, sandbox } = deps;
|
|
8323
8323
|
const writeScheme = deps.writeScheme ?? defaultSchemeWrite;
|
|
8324
8324
|
return defineTool({
|
|
8325
8325
|
name: "write",
|
|
@@ -8334,7 +8334,8 @@ function createWriteTool(deps) {
|
|
|
8334
8334
|
type: "string",
|
|
8335
8335
|
required: true,
|
|
8336
8336
|
description: "Full new content (an empty string writes an empty file)."
|
|
8337
|
-
}
|
|
8337
|
+
},
|
|
8338
|
+
...sandbox !== void 0 && sandbox.escalationModes.length > 0 ? sandbox.schemaFields() : {}
|
|
8338
8339
|
},
|
|
8339
8340
|
output: {
|
|
8340
8341
|
schema: {
|
|
@@ -9832,6 +9833,7 @@ function installAgentTools(rootCtx, agent, resolver) {
|
|
|
9832
9833
|
agent.ctx.effect(async () => {
|
|
9833
9834
|
const native = captureNativeTools(rootCtx, agent);
|
|
9834
9835
|
const disposers = [];
|
|
9836
|
+
const hashlineSandbox = new FsSandboxController(rootCtx);
|
|
9835
9837
|
disposers.push(agent.ctx.tools.register(createReadTool({
|
|
9836
9838
|
resolver,
|
|
9837
9839
|
fs: rootCtx.fs,
|
|
@@ -9839,10 +9841,10 @@ function installAgentTools(rootCtx, agent, resolver) {
|
|
|
9839
9841
|
})));
|
|
9840
9842
|
disposers.push(agent.ctx.tools.register(createWriteTool({
|
|
9841
9843
|
nativeWrite: native.write,
|
|
9844
|
+
sandbox: hashlineSandbox,
|
|
9842
9845
|
...buildLspWriteFeedback()
|
|
9843
9846
|
})));
|
|
9844
9847
|
const hashlineIo = ctxFsIO(rootCtx.fs, rootCtx);
|
|
9845
|
-
const hashlineSandbox = new FsSandboxController(rootCtx);
|
|
9846
9848
|
disposers.push(registerEditTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
|
|
9847
9849
|
disposers.push(registerUndoTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
|
|
9848
9850
|
disposers.push(registerWriteHook(rootCtx, agent.ctx, hashlineIo));
|
|
@@ -10707,6 +10709,251 @@ function installFailover(ctx, config = defaultFailoverConfig) {
|
|
|
10707
10709
|
});
|
|
10708
10710
|
}
|
|
10709
10711
|
|
|
10712
|
+
//#endregion
|
|
10713
|
+
//#region src/mobile/zoom-guard.ts
|
|
10714
|
+
/**
|
|
10715
|
+
* Whether a UA string + touch-capability pair is an iOS-class browser
|
|
10716
|
+
* (design D2, mirroring Discourse `capabilities.isIOS`): iPhone/iPod/iPad,
|
|
10717
|
+
* plus iPadOS 13+ desktop-Safari masquerade (`Macintosh` UA with a
|
|
10718
|
+
* multi-touch digitizer). Deliberately NOT `pointer:coarse` — Android Chrome
|
|
10719
|
+
* never focus-zooms and `maximum-scale` has historical side effects there,
|
|
10720
|
+
* so the gate is tightened to the iOS family. UA-shape drift degrades
|
|
10721
|
+
* benignly: a novel UA simply reads as non-iOS and no rewriting happens.
|
|
10722
|
+
*
|
|
10723
|
+
* @param ua - `navigator.userAgent`.
|
|
10724
|
+
* @param maxTouchPoints - `navigator.maxTouchPoints` (0 when absent).
|
|
10725
|
+
* @returns `true` for iOS-class browsers.
|
|
10726
|
+
*/
|
|
10727
|
+
function isIOSClassUA(ua, maxTouchPoints) {
|
|
10728
|
+
return /iPhone|iPod|iPad/.test(ua) || /Macintosh/.test(ua) && maxTouchPoints > 1;
|
|
10729
|
+
}
|
|
10730
|
+
/**
|
|
10731
|
+
* Token-level merge of viewport directives into a viewport meta `content`
|
|
10732
|
+
* string (design D4): existing tokens keep their positions, a token whose key
|
|
10733
|
+
* (compared case-insensitively) is in `tokens` is REPLACED by the configured
|
|
10734
|
+
* key=value, and configured keys not present are APPENDED in insertion order.
|
|
10735
|
+
* Idempotent by construction — merging always re-derives from the input, so
|
|
10736
|
+
* repeated evaluation cannot accumulate duplicates.
|
|
10737
|
+
*
|
|
10738
|
+
* mergeViewportTokens('width=device-width, initial-scale=1', T)
|
|
10739
|
+
* → 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'
|
|
10740
|
+
* mergeViewportTokens('width=device-width, maximum-scale=5', T)
|
|
10741
|
+
* → 'width=device-width, maximum-scale=1, user-scalable=no' // same-key override, position kept
|
|
10742
|
+
*
|
|
10743
|
+
* @param content - the stock (or stock-equivalent) content string.
|
|
10744
|
+
* @param tokens - the directives to merge, keyed lowercase.
|
|
10745
|
+
* @returns the merged content string.
|
|
10746
|
+
*/
|
|
10747
|
+
function mergeViewportTokens(content, tokens) {
|
|
10748
|
+
var out = [];
|
|
10749
|
+
var seen = {};
|
|
10750
|
+
var parts = content.split(",");
|
|
10751
|
+
for (var i = 0; i < parts.length; i++) {
|
|
10752
|
+
var part = parts[i];
|
|
10753
|
+
var raw = (part === void 0 ? "" : part).replace(/^\s+|\s+$/g, "");
|
|
10754
|
+
if (raw === "") continue;
|
|
10755
|
+
var eq = raw.indexOf("=");
|
|
10756
|
+
var key = eq < 0 ? raw : raw.slice(0, eq).replace(/^\s+|\s+$/g, "");
|
|
10757
|
+
var hit = null;
|
|
10758
|
+
for (var k in tokens) if (tokens.hasOwnProperty(k) && k.toLowerCase() === key.toLowerCase()) {
|
|
10759
|
+
hit = k;
|
|
10760
|
+
break;
|
|
10761
|
+
}
|
|
10762
|
+
if (hit === null) {
|
|
10763
|
+
out[out.length] = raw;
|
|
10764
|
+
continue;
|
|
10765
|
+
}
|
|
10766
|
+
out[out.length] = hit + "=" + tokens[hit];
|
|
10767
|
+
seen[hit] = true;
|
|
10768
|
+
}
|
|
10769
|
+
for (var j in tokens) if (tokens.hasOwnProperty(j) && !seen[j]) out[out.length] = j + "=" + tokens[j];
|
|
10770
|
+
return out.join(", ");
|
|
10771
|
+
}
|
|
10772
|
+
/**
|
|
10773
|
+
* The double gate (design D2/D3 + the `zoomGuard` config): rewrite applies
|
|
10774
|
+
* only on an iOS-class browser AND a narrow viewport AND a non-`off`
|
|
10775
|
+
* `zoomGuard`. An absent payload (or absent key) means the `'meta'` default —
|
|
10776
|
+
* the same default-on posture as the mobile leg itself — so only an explicit
|
|
10777
|
+
* `'off'` disarms the guard. Unknown non-`off` values read as `'meta'`
|
|
10778
|
+
* (schema validation makes them unreachable in production; benign here).
|
|
10779
|
+
*
|
|
10780
|
+
* @param config - the `__DASHR_MOBILE__` payload (only `zoomGuard` is read).
|
|
10781
|
+
* @param isIOS - {@link isIOSClassUA} verdict for this page load.
|
|
10782
|
+
* @param isNarrow - `matchMedia('(max-width: breakpoint-0.02px)')` verdict.
|
|
10783
|
+
* @returns `true` when the viewport rewrite should be applied.
|
|
10784
|
+
*/
|
|
10785
|
+
function shouldApplyZoomGuard(config, isIOS, isNarrow) {
|
|
10786
|
+
if (!isIOS || !isNarrow) return false;
|
|
10787
|
+
if (!config) return true;
|
|
10788
|
+
return config.zoomGuard !== "off";
|
|
10789
|
+
}
|
|
10790
|
+
/**
|
|
10791
|
+
* Standalone display-mode verdict (design D4 of change
|
|
10792
|
+
* `2026-09-03-zoomguard-standalone-font-floor`): standalone PWAs HONOR
|
|
10793
|
+
* `user-scalable=no` (the real-device pinch regression that motivated the
|
|
10794
|
+
* display-mode split), so they must never enter the meta-rewrite path. Two
|
|
10795
|
+
* sources, strict-`true` OR: the `(display-mode: standalone)` media query
|
|
10796
|
+
* (primary) and legacy `navigator.standalone` (older iOS Safari exposes only
|
|
10797
|
+
* the latter). Anything else — `undefined` (engine without the source),
|
|
10798
|
+
* `false`, or truthy junk — reads as NOT standalone.
|
|
10799
|
+
*
|
|
10800
|
+
* @param mqMatchesStandalone - `matchMedia('(display-mode: standalone)').matches`.
|
|
10801
|
+
* @param navStandalone - `navigator.standalone === true`.
|
|
10802
|
+
* @returns `true` in standalone display mode (either source).
|
|
10803
|
+
*/
|
|
10804
|
+
function isStandaloneDisplay(mqMatchesStandalone, navStandalone) {
|
|
10805
|
+
return mqMatchesStandalone === true || navStandalone === true;
|
|
10806
|
+
}
|
|
10807
|
+
/**
|
|
10808
|
+
* The 16px font floor stylesheet (design D3): iOS auto-zooms on focus into
|
|
10809
|
+
* ANY control whose computed font-size is below 16px, so in standalone mode
|
|
10810
|
+
* — where the meta rewrite is off the table (pinch must stay
|
|
10811
|
+
* engine-available) — the floor IS the suppression. Scoped by the same
|
|
10812
|
+
* breakpoint band the meta leg uses and derived the same way
|
|
10813
|
+
* (`breakpoint - 0.02`: 768 → 767.98, 900 → 899.98); `!important` beats the
|
|
10814
|
+
* 13/14px CSS module rules and the body inline var chain;
|
|
10815
|
+
* `[contenteditable="true"]` matches only the editable state (React
|
|
10816
|
+
* serializes `contentEditable={bool}` to the literal attribute value
|
|
10817
|
+
* "true"/"false").
|
|
10818
|
+
*
|
|
10819
|
+
* @param breakpointPx - the configured `mobile.breakpoint` (raw — this
|
|
10820
|
+
* function derives the `-0.02` band itself, do NOT pre-subtract).
|
|
10821
|
+
* @returns the complete stylesheet text.
|
|
10822
|
+
*/
|
|
10823
|
+
function buildFontFloorCss(breakpointPx) {
|
|
10824
|
+
return "@media (max-width:" + (breakpointPx - .02) + "px){ input,textarea,select,[contenteditable=\"true\"]{ font-size:16px !important } }";
|
|
10825
|
+
}
|
|
10826
|
+
/**
|
|
10827
|
+
* Build the boot script's zoomGuard section: the five decision predicates
|
|
10828
|
+
* embedded via `toString()` plus the DOM state machine that applies them.
|
|
10829
|
+
* The machine implements design D3/D4 with one discovered refinement —
|
|
10830
|
+
*
|
|
10831
|
+
* **The standalone fork (2026-09-03, change
|
|
10832
|
+
* `2026-09-03-zoomguard-standalone-font-floor`).** Real-device datum:
|
|
10833
|
+
* standalone (Add to Home Screen) iOS HONORS `user-scalable=no`, so the
|
|
10834
|
+
* meta rewrite that is benign in-browser (pinch stays engine-controlled)
|
|
10835
|
+
* kills pinch in the PWA — the user's primary surface. The section
|
|
10836
|
+
* therefore forks right after the iOS gate, BEFORE any meta machinery: a
|
|
10837
|
+
* one-shot startup verdict (display-mode never flips mid-session) via the
|
|
10838
|
+
* `(display-mode: standalone)` MQ or legacy `navigator.standalone` (D4).
|
|
10839
|
+
* Standalone → inject the `ios-zoom-font-floor` style element into the
|
|
10840
|
+
* head (16px floor, media-query-scoped to the same breakpoint band, D3)
|
|
10841
|
+
* and RETURN — no provisional meta, no reconcile observer, no ladder
|
|
10842
|
+
* timer, no listeners, byte-identical stock meta (D1). The width dimension
|
|
10843
|
+
* lives inside the stylesheet's media query, so the injection itself is
|
|
10844
|
+
* NOT width-gated: a wide standalone load still gets the element; the rule
|
|
10845
|
+
* simply does not match above the breakpoint. Browser mode runs the v0.2.4
|
|
10846
|
+
* machinery unchanged.
|
|
10847
|
+
* **Why reconciliation exists.** The webserver splices head injections
|
|
10848
|
+
* immediately after the opening `<head>` tag, i.e. BEFORE the stock
|
|
10849
|
+
* `<meta name="viewport">` in upstream's `index.html`. At boot-script time
|
|
10850
|
+
* the stock meta is therefore NOT yet parsed, so the "meta missing"
|
|
10851
|
+
* branch is the NORMAL path on the served shell, not an edge case. A
|
|
10852
|
+
* provisional meta is created right away (the timing requirement: the
|
|
10853
|
+
* suppressive tokens are live before any application bundle can focus an
|
|
10854
|
+
* input), and a MutationObserver watches for the parser to insert the stock
|
|
10855
|
+
* meta; the moment it appears, the guard records the true stock content,
|
|
10856
|
+
* rewrites the STOCK meta (token merge, D4's actual semantics — restore
|
|
10857
|
+
* later writes these exact bytes back), and removes the provisional one —
|
|
10858
|
+
* leaving a single-meta document instead of relying on engines'
|
|
10859
|
+
* unspecified multi-meta merge behavior. Engines without MutationObserver
|
|
10860
|
+
* keep the provisional meta for the page's lifetime (degraded but still
|
|
10861
|
+
* suppressive); the observer is never started once the stock meta was
|
|
10862
|
+
* found synchronously.
|
|
10863
|
+
*
|
|
10864
|
+
* **Why the re-evaluation ladder exists (2026-09-03 rework, lead CDP
|
|
10865
|
+
* finding on the 4999 instance).** The boot script runs during head parsing,
|
|
10866
|
+
* BEFORE the engine applies the stock viewport meta — the layout viewport at
|
|
10867
|
+
* first evaluation is the pre-meta engine default (980px observed), so
|
|
10868
|
+
* `mq.matches` reads false on an initial narrow load, and the narrowing that
|
|
10869
|
+
* follows meta application does NOT reliably dispatch `resize` to the page
|
|
10870
|
+
* during load (headless Chrome: never). A ~10ms poll ladder therefore
|
|
10871
|
+
* re-evaluates until the guard IS applied, `document.readyState` reaches
|
|
10872
|
+
* `'complete'`, or ~2s elapse — the first layout always precedes deferred
|
|
10873
|
+
* scripts, so the ladder lands the guard before any application bundle can
|
|
10874
|
+
* focus an input. Steady-state ticks are idempotent no-ops; a wide load
|
|
10875
|
+
* idles to the readyState stop and leaves no timer behind (a pending tick
|
|
10876
|
+
* is also cancelled the moment the guard lands). MediaQuery `change` events
|
|
10877
|
+
* (with the legacy `addListener` fallback) form a second re-evaluation
|
|
10878
|
+
* channel alongside window `resize`.
|
|
10879
|
+
*
|
|
10880
|
+
* State machine (ES5, zero dependencies, synchronous first evaluation):
|
|
10881
|
+
* `meta` is the element under management (provisional or reconciled stock),
|
|
10882
|
+
* `stock` its recorded stock content, `mine` whether we created it,
|
|
10883
|
+
* `applied` the last applied gate verdict. `apply(on)` is idempotent per
|
|
10884
|
+
* state (no-op when the verdict is unchanged); merges always re-derive
|
|
10885
|
+
* from the recorded stock, never from the live content, so resize storms
|
|
10886
|
+
* cannot accumulate tokens. Non-iOS pages never wire any listener.
|
|
10887
|
+
*
|
|
10888
|
+
* Emitted only when the mobile payload exists and `zoomGuard !== 'off'`.
|
|
10889
|
+
*
|
|
10890
|
+
* @returns the inline section text (truthy `var` declarations + IIFE).
|
|
10891
|
+
*/
|
|
10892
|
+
function buildZoomGuardSection() {
|
|
10893
|
+
return [
|
|
10894
|
+
`var ZI=${isIOSClassUA.toString()};`,
|
|
10895
|
+
`var ZM=${mergeViewportTokens.toString()};`,
|
|
10896
|
+
`var ZS=${shouldApplyZoomGuard.toString()};`,
|
|
10897
|
+
`var ZD=${isStandaloneDisplay.toString()};`,
|
|
10898
|
+
`var ZF=${buildFontFloorCss.toString()};`,
|
|
10899
|
+
"(function(){",
|
|
10900
|
+
"var m=window.__DASHR_MOBILE__;",
|
|
10901
|
+
"var ios=ZI(navigator.userAgent,(navigator.maxTouchPoints||0));",
|
|
10902
|
+
"if(!ios)return;",
|
|
10903
|
+
"var bpx=(m&&typeof m.breakpoint==='number'&&m.breakpoint>0)?m.breakpoint:768;",
|
|
10904
|
+
"var bp=bpx-0.02;",
|
|
10905
|
+
"var sa=ZD((window.matchMedia&&window.matchMedia('(display-mode: standalone)').matches),(navigator.standalone===true));",
|
|
10906
|
+
"if(sa){var f=document.createElement('style');f.setAttribute('id','ios-zoom-font-floor');f.setAttribute('data-plugin','better-dsh');f.setAttribute('data-plugin-css','better-dsh/zoom-font-floor');f.textContent=ZF(bpx);headEl().appendChild(f);return}",
|
|
10907
|
+
"var mq=(typeof window.matchMedia==='function')?window.matchMedia('(max-width:'+bp+'px)'):null;",
|
|
10908
|
+
"var T={'maximum-scale':'1','user-scalable':'no'};",
|
|
10909
|
+
"var meta=null,stock=null,mine=false,applied=false,obs=null,lad=null,lN=0;",
|
|
10910
|
+
"function findStock(){var s=document.getElementsByTagName('meta');for(var i=0;i<s.length;i++){var e=s[i];if(e!==meta&&(e.getAttribute('name')||'').toLowerCase()==='viewport')return e}return null}",
|
|
10911
|
+
"function headEl(){return document.head||document.getElementsByTagName('head')[0]||document.documentElement}",
|
|
10912
|
+
"function stopWatch(){if(obs){obs.disconnect();obs=null}}",
|
|
10913
|
+
"function stopLadder(){if(lad){clearTimeout(lad);lad=null}}",
|
|
10914
|
+
"function dropMine(){if(meta&&mine){if(meta.parentNode)meta.parentNode.removeChild(meta);meta=null;mine=false}}",
|
|
10915
|
+
"function apply(on){",
|
|
10916
|
+
"if(on===applied)return;",
|
|
10917
|
+
"applied=on;",
|
|
10918
|
+
"if(on)stopLadder();",
|
|
10919
|
+
"if(on){",
|
|
10920
|
+
"var el=findStock();",
|
|
10921
|
+
"if(el){meta=el;mine=false;stock=el.getAttribute('content')||''}",
|
|
10922
|
+
"else if(!meta){meta=document.createElement('meta');meta.setAttribute('name','viewport');mine=true;stock='';headEl().appendChild(meta)}",
|
|
10923
|
+
"meta.setAttribute('content',ZM(stock,T));",
|
|
10924
|
+
"if(mine&&typeof MutationObserver==='function'&&!obs){obs=new MutationObserver(check);obs.observe(document.documentElement,{childList:true,subtree:true})}",
|
|
10925
|
+
"}else{",
|
|
10926
|
+
"stopWatch();",
|
|
10927
|
+
"if(!meta)return;",
|
|
10928
|
+
"if(mine)dropMine();",
|
|
10929
|
+
"else meta.setAttribute('content',stock);",
|
|
10930
|
+
"}",
|
|
10931
|
+
"}",
|
|
10932
|
+
"function check(){",
|
|
10933
|
+
"var el=findStock();",
|
|
10934
|
+
"if(!el)return;",
|
|
10935
|
+
"stopWatch();",
|
|
10936
|
+
"stock=el.getAttribute('content')||'';",
|
|
10937
|
+
"if(applied){dropMine();meta=el;mine=false;meta.setAttribute('content',ZM(stock,T))}",
|
|
10938
|
+
"else{dropMine();meta=null;stock=null}",
|
|
10939
|
+
"}",
|
|
10940
|
+
"function re(){apply(ZS(m,true,mq?mq.matches:false))}",
|
|
10941
|
+
"function tick(){",
|
|
10942
|
+
"lad=null;",
|
|
10943
|
+
"re();",
|
|
10944
|
+
"if(applied)return;",
|
|
10945
|
+
"lN++;",
|
|
10946
|
+
"if(document.readyState==='complete'||lN>=200)return;",
|
|
10947
|
+
"lad=setTimeout(tick,10);",
|
|
10948
|
+
"}",
|
|
10949
|
+
"re();",
|
|
10950
|
+
"window.addEventListener('resize',re);",
|
|
10951
|
+
"if(mq&&mq.addEventListener)mq.addEventListener('change',re);else if(mq&&mq.addListener)mq.addListener(re);",
|
|
10952
|
+
"if(!applied&&typeof setTimeout==='function')lad=setTimeout(tick,10);",
|
|
10953
|
+
"})()"
|
|
10954
|
+
].join("");
|
|
10955
|
+
}
|
|
10956
|
+
|
|
10710
10957
|
//#endregion
|
|
10711
10958
|
//#region src/web-trust.ts
|
|
10712
10959
|
/**
|
|
@@ -10752,12 +10999,16 @@ function buildBootScript(config) {
|
|
|
10752
10999
|
...mobile?.dominanceRatio !== void 0 ? { dominanceRatio: mobile.dominanceRatio } : {},
|
|
10753
11000
|
...mobile?.leftEdgeBandPx !== void 0 ? { leftEdgeBandPx: mobile.leftEdgeBandPx } : {},
|
|
10754
11001
|
...mobile?.rightZoneRatio !== void 0 ? { rightZoneRatio: mobile.rightZoneRatio } : {},
|
|
10755
|
-
...mobile?.swipeVelocityPxPerMs !== void 0 ? { swipeVelocityPxPerMs: mobile.swipeVelocityPxPerMs } : {}
|
|
11002
|
+
...mobile?.swipeVelocityPxPerMs !== void 0 ? { swipeVelocityPxPerMs: mobile.swipeVelocityPxPerMs } : {},
|
|
11003
|
+
...mobile?.zoomGuard !== void 0 ? { zoomGuard: mobile.zoomGuard } : {}
|
|
10756
11004
|
} : void 0;
|
|
10757
11005
|
if (authorities.length === 0 && mobilePayload === void 0) return void 0;
|
|
10758
11006
|
const parts = ["(function(){try{"];
|
|
10759
11007
|
if (authorities.length > 0) parts.push("if(!window.__DSH_TRANSPORT__){", `var h=location.hostname,a=${JSON.stringify(authorities)};`, "for(var i=0;i<a.length;i++)if(a[i]===h){window.__DSH_TRANSPORT__={ownsHost:true};break}", "}");
|
|
10760
|
-
if (mobilePayload !== void 0)
|
|
11008
|
+
if (mobilePayload !== void 0) {
|
|
11009
|
+
parts.push(`window.__DASHR_MOBILE__=${JSON.stringify(mobilePayload)};`);
|
|
11010
|
+
if (mobile?.zoomGuard !== "off") parts.push(buildZoomGuardSection());
|
|
11011
|
+
}
|
|
10761
11012
|
parts.push("}catch(e){}})();");
|
|
10762
11013
|
return parts.join("");
|
|
10763
11014
|
}
|
|
@@ -10802,7 +11053,8 @@ const MOBILE_CONFIG = z.object({
|
|
|
10802
11053
|
dominanceRatio: z.number().min(1).default(1.3),
|
|
10803
11054
|
leftEdgeBandPx: z.natural().min(8).default(120),
|
|
10804
11055
|
rightZoneRatio: z.number().min(.05).max(.9).default(.25),
|
|
10805
|
-
swipeVelocityPxPerMs: z.number().min(0).default(.15)
|
|
11056
|
+
swipeVelocityPxPerMs: z.number().min(0).default(.15),
|
|
11057
|
+
zoomGuard: z.union(["meta", "off"]).default("meta")
|
|
10806
11058
|
});
|
|
10807
11059
|
/** Runtime schema. */
|
|
10808
11060
|
const Config = z.intersect([DashrRuntime.Config, z.object({
|
package/package.json
CHANGED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# REPL 工具调用截断诊断(v0.2.1b 实测中发现,2026-09-01)
|
|
2
|
-
|
|
3
|
-
## 结论先行
|
|
4
|
-
|
|
5
|
-
**任何从 REPL(`eval` cell)内部发起的工具调用(`tool.<name>(...)`)都会让 dsh web daemon 进程崩溃并自动重启;会话日志在 `tool/code-dispatch-start` 处撕裂,恢复时 repair 机制合成「interrupted-tool-result」,模型看到的即「工具调用被截断、结果未知」。** 4 次探针 4 次复现,与 journald 守护进程崩溃记录逐条对应。
|
|
6
|
-
|
|
7
|
-
这不是 REPL 同步/异步的问题,也不是 subagent 特有问题——是 dashr 子分派桥接层的结构性缺陷 + 部署运行态注册表视图缺 symbol 的直接原因叠加。
|
|
8
|
-
|
|
9
|
-
## 一、复现矩阵(第一人称)
|
|
10
|
-
|
|
11
|
-
| # | cell 内容 | 结果 |
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| 1 | `await tool.subagent({… run_in_background: false })` | 截断(daemon 崩溃 17:39:17) |
|
|
14
|
-
| 2 | `await tool.subagent({… run_in_background: true })` | 截断(daemon 崩溃 17:41:15) |
|
|
15
|
-
| 3 | `await tool.bash({… run_in_background: true })` | 截断(daemon 崩溃 17:57:05) |
|
|
16
|
-
| 4 | `await tool.read({path: "dashr/package.json"})` | 截断(daemon 崩溃 18:02:27) |
|
|
17
|
-
|
|
18
|
-
对照:纯 Python cell(`dir(tool)`、顶层 `return` 判错、`await asyncio.sleep(0)`)全部正常。**凡含 `tool.*` RPC 的 cell 必崩,与工具、与 background 与否无关。**
|
|
19
|
-
|
|
20
|
-
## 二、截断面定位(三层证据)
|
|
21
|
-
|
|
22
|
-
### 1. 会话日志(`~/.dsh/sessions/…/session.jsonl.zstd`)
|
|
23
|
-
4 次截断的共同指纹:`tool/call (eval)` → `tool/code-dispatch-start`(**已持久化**)→ 日志撕裂(无 `tool/code-dispatch`、无 `step/end`/`turn/end`)→ 重载时 `packages/core/session/src/repair.ts` 的 `interruptedTurnClosers` 合成 `interrupted-tool-result-<callId>-<seq>`(`TOOL_OUTCOME_UNKNOWN`)。会话内 **0 条 error 事件**——错误根本没走到会话层。
|
|
24
|
-
|
|
25
|
-
### 2. 守护进程日志(`journalctl --user -u dsh.service`)——铁证
|
|
26
|
-
```
|
|
27
|
-
Sep 01 17:39:17 pnpm[414449]: dsh: fatal load failure: TypeError: Cannot read properties of undefined (reading 'prepare')
|
|
28
|
-
at Object.start (…/better-dsh/lib/index.js:11281:41)
|
|
29
|
-
at <anonymous> (…/lib/index.js:11191:21)
|
|
30
|
-
at drive (…/lib/index.js:11207:7)
|
|
31
|
-
at outcome (…/lib/index.js:11310:6)
|
|
32
|
-
at new Promise (<anonymous>)
|
|
33
|
-
at toolFunctions.<computed> (…/lib/index.js:11320:72)
|
|
34
|
-
at Proxy.dispatchHostRequest (…/lib/index.js:2180:21)
|
|
35
|
-
Sep 01 17:39:19 systemd: dsh.service: Main process exited, code=exited, status=1/FAILURE
|
|
36
|
-
Sep 01 17:39:23 systemd: dsh.service: Scheduled restart job, restart counter is at 1.
|
|
37
|
-
```
|
|
38
|
-
17:39:17 / 17:41:15 / 17:57:05 / 18:02:27 四次崩溃,栈完全一致。`scheduler.prepare` 中 `scheduler` 为 `undefined`——即 `registry[TOOL_RUNTIME_SCHEDULER]` 在部署运行态取不到值。
|
|
39
|
-
|
|
40
|
-
### 3. 源码链(`dashr/src/index.ts`)
|
|
41
|
-
- kernel 侧:`tool.<name>(...)` → `binding.call` RPC(`src/bootstrap.ts` `_dashr_callable`)。
|
|
42
|
-
- host 侧:`dispatchHostRequest`(`src/runtime.ts:545`)→ `await fn(args)`(`src/index.ts` `binding()`)。
|
|
43
|
-
- `binding()` 内部(`src/index.ts:666`):`const scheduler = registry[TOOL_RUNTIME_SCHEDULER]` → 单驱动道 `drive()`(`src/index.ts:591-621`)→ `start()` 内 `await scheduler.prepare(input)`(`src/index.ts:712`)。
|
|
44
|
-
- **结构性缺陷**:`drive()` 的 async IIFE 是 `try { … } finally { … }`,**没有 catch**;调用处是 `void drive()`。任何 `start()`/`commit()` 抛错(如 `scheduler.prepare` TypeError)都会变成 **unhandled rejection**——binding promise 既不 resolve 也不 reject,cell 永久挂起,错误外泄给进程。
|
|
45
|
-
**完整链条**:cell 工具调用 → dispatch 启动(`code-dispatch-start` 已入日志)→ `registry[TOOL_RUNTIME_SCHEDULER]` 为 undefined → `scheduler.prepare` TypeError → driver 无 catch → unhandled rejection → `installFailLoud` 判定 fatal → daemon exit(1) → systemd 重启 → 会话重载 repair 合成「interrupted-tool-result」→ 模型收到截断消息。**截断面:binding 桥 → registry symbol 查询 → driver 的未处理拒绝。**
|
|
46
|
-
|
|
47
|
-
## 三、命名澄清:REPL dispatch log 与 code-dispatch 是两个不同层(重要)
|
|
48
|
-
|
|
49
|
-
容易混淆,先分清:
|
|
50
|
-
|
|
51
|
-
| 名字 | 层级 | 是谁 | 本次 change 是否改名 |
|
|
52
|
-
|---|---|---|---|
|
|
53
|
-
| `tool/code-dispatch-start` / `tool/code-dispatch` | **会话持久日志事件**(durable log,`agent.session.append`) | dashr `src/index.ts:689,712`;harness `known-event-types.ts:63-64` 注册 | **否**——v0.1.x 就有,历史遗留名 |
|
|
54
|
-
| `dashr/repl-dispatch-log` | **waterfall 扩展点**(内容整形监听事件) | dashr 自注册(`src/index.ts:458`) | **是**——v0.2.1 从上游 `tools/code-dispatch-log`/`tools/ptc-dispatch-log` 改名为「自己的 REPL dispatch log」 |
|
|
55
|
-
| `tools/ptc-dispatch-log` | waterfall(上游 PTC 用) | harness `src/ptc.ts:282` + spill policy 监听(`spill-policy/src/index.ts:217`) | dashr 已不再 emit(仅注释提及) |
|
|
56
|
-
|
|
57
|
-
**结论**:
|
|
58
|
-
- 「自己的 REPL dispatch log」`dashr/repl-dispatch-log` **在代码里存在且自注册**——目标形态已就位。它之所以「没被触发」,是因为崩溃发生在它之前的 `scheduler.prepare`(它在 settle 之后才跑)——**是崩溃的结果,不是原因**。
|
|
59
|
-
- 会话日志里我用作定位证据的 `tool/code-dispatch-start` 是**持久日志事件名**,与 waterfall 改名无关,只是「撕裂点之前最后一条已写入事件」的定位标记。
|
|
60
|
-
- 改名引入了一个**真实但非崩溃的副作用**:harness 的 spill policy 仍监听上游 `tools/ptc-dispatch-log`,dashr 不再 emit → 溢出限幅(oversized 结果的 preview+locator 替换)对 eval 子调用日志**静默失效**。**已补(2026-09-01 晚,见六)**:dashr `shapeDispatchLog` 跑完 `dashr/repl-dispatch-log` 后,再把同一 dispatch(载荷结构与上游 `PtcDispatchLog` 完全一致)喂进 `tools/ptc-dispatch-log` 载体,spill 臂恢复生效。改动在 `dashr/src/index.ts`,lib 已重建(`f0febba7…`),**部署位同步 + daemon 重启待用户执行**。
|
|
61
|
-
|
|
62
|
-
## 四、为什么 `registry[TOOL_RUNTIME_SCHEDULER]` 是 undefined(直接原因)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
- 已排除:dsh-tools 双副本 symbol 不一致(实测插件与 daemon 均解析到同一 realpath `/home/u1/workspaces/dsh-alpha/packages/core/tools`,symbol 严格相等);dashr 分派代码改动(266d232/06e516a/HEAD 逐字节一致);dsh-tools alpha.1 vs alpha.3 差异(仅 import 整理/ToolCallId brand 等外观差异)。
|
|
66
|
-
- 关键观察:`classify()` 调用的 `registry.executionMode(input)` **正常**(`code-dispatch-start` 已写入日志,证明 `start()` 之前的分派逻辑跑通),而 `registry[TOOL_RUNTIME_SCHEDULER]` **取不到**。`executionMode` 是 ToolRuntime 类方法、`TOOL_RUNTIME_SCHEDULER` 是类实例 symbol 字段——即部署运行态 `runtimeCtx.tools` 返回的**不是原生 ToolRuntime 实例**,而是一个有方法、缺实例 symbol 字段的服务视图(cordis 作用域服务解析 / plugin tree 挂载路径差异)。
|
|
67
|
-
- 测试为什么全绿:dashr 测试组合(`test/helpers.ts`)把 `ctx.tools` 解析到**根作用域的原生 ToolRuntime 实例**,symbol 在位;生产组合经 harness plugin tree / scope 层解析,拿到的是视图。
|
|
68
|
-
- **何时坏的(重要修正)**:v0.1.8 实测报告(2026-08-24)实证 DASHR 自己的 `eval` 桥当时**正常工作**——同 cell 并发 `tool.read` + `tool.bash` 完成。2026-08-28 会话用的是 harness 自带 PTC `run_cell`(`tools.bash`,复数命名空间 = PTC SDK 形态;该 PTC 核心经 `@deepseek-ai/dsh-code-runtime-python` 执行 **Python**,非 TS),不能代表 dashr 桥状态。**崩溃由 2026-09-01 的 harness alpha(0.1.2-alpha.1)升级引入**——升级后 plugin tree 里 `runtimeCtx.tools` 变成缺实例 symbol 字段的服务视图;dashr v0.2.1 的 dispatch-log 改名不是崩溃原因。
|
|
69
|
-
|
|
70
|
-
## 五、REPL 同步/异步问题的回答(顺带闭环)
|
|
71
|
-
|
|
72
|
-
- 模型层:`eval` 是同步工具调用,模型等 cell 结果——这是设计使然,与缺陷无关。
|
|
73
|
-
- cell 内:`tool.<name>(...)` 是 kernel→host 的 `binding.call` RPC,被 cell `await`;host 经与原生调用同一 registry 分派器执行。`run_in_background: true` 的工具实现**快速返回 id**(subagent `{kind:'continuable', subagentId}`、bash 返回 job id),background 语义本应保留、子任务与 cell 结束无关。
|
|
74
|
-
- 但**当前运行时该路径整体坏掉**:任何子分派在 `scheduler.prepare` 即 TypeError,与 background 无关——所以「在 REPL 里 background 是否异步」目前是伪问题,路径不通;修好后 background 语义会按上述设计成立。
|
|
75
|
-
|
|
76
|
-
## 六、修复方向(分层)
|
|
77
|
-
|
|
78
|
-
1. **dashr 立即修(阻断 daemon 崩溃)**:`drive()` 加 catch / `start()`、`commit()` 失败必须 settle binding promise 为错误结果——cell 内工具调用失败应变成可见的 `ToolCallError`,**绝不允许以 unhandled rejection 外泄杀 daemon**。这是把「任何分派错误」放大成「整个运行时崩溃」的放大器,无论直接原因是什么都必须先修。
|
|
79
|
-
2. **dashr 防御**:`registry[TOOL_RUNTIME_SCHEDULER]` 为 undefined 时抛带上下文的 loud 错误(cell 可见),而不是裸 `undefined.prepare` TypeError。
|
|
80
|
-
3. **根因定位(harness 接线)**:在活体 daemon 上探针确认 plugin tree 中 `runtimeCtx.tools` 的实际对象(cordis 作用域服务视图 / 第二个 tools 实例),修正挂载使插件拿到原生 ToolRuntime(symbol 在位)。这一步需要 daemon 侧可写环境(本会话 bwrap 沙箱只读宿主,无法直接探查活体)。
|
|
81
|
-
4. **测试补强(部分完成)**:已加「`tools/ptc-dispatch-log` 载体回喂」回归用例(`test/bridge.spec.ts`,全量 396 绿)。仍缺「生产形态」挂载测试——按 harness 的 plugin-tree/scope 方式挂载插件,断言 `ctx.tools[TOOL_RUNTIME_SCHEDULER]` 已定义 + 一个真实工具 RPC 从 cell 完成;防止此环境差异回归。
|
|
82
|
-
5. **回归核验基线**:修复后重跑本文档第一节的 4 探针矩阵(4/4 应全部返回结果而非截断),并观察 `journalctl --user -u dsh.service` 无新崩溃。
|
|
83
|
-
|
|
84
|
-
## 附:证据文件
|
|
85
|
-
- 会话日志:`~/.dsh/sessions/--home-u1-workspaces-dashr--/session-ad9ac311-3400-4304-b501-e7303da850df/session.jsonl.zstd`
|
|
86
|
-
- 守护进程日志:`journalctl --user -u dsh.service`(17:39:17 / 17:41:15 / 17:57:05 / 18:02:27 四条 fatal load failure)
|
|
87
|
-
- 对照工作会话:`06ac8b2c-fb2e-458f-80d6-a0d28e50f193`(2026-08-28,9 对完整 code-dispatch,PTC run_cell 路径)
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## 七、v0.2.1c 修复执行记录(2026-09-01,change v0-2-1c-repl-tool-crash-fix)
|
|
92
|
-
|
|
93
|
-
### 六.1 + 六.2 已实现(dashr 侧)
|
|
94
|
-
|
|
95
|
-
- `drive()` async IIFE 补 `catch`:lane 级 backstop——任何逃逸 start()/commit() 自身包裹的错误(或 classify() 抛错)settle 所有 queued-but-unsettled 分派为可见错误、清空队列、复位 exclusive、记 warn,**绝不外泄 unhandled rejection**。
|
|
96
|
-
- `PendingDispatch` 新增 `fail(error)`:把 binding promise settle 为 `{ isError: true, message }`(经 `settleError` → `settle`,kernel 转 `ToolCallError`)。
|
|
97
|
-
- `start()` / `commit()` 各自 try/catch:prepare 失败、dispatch body rejection、finalize/finish 失败都 settle 自己的 binding,不再向 lane 抛。
|
|
98
|
-
- `binding()` 内 `registry[TOOL_RUNTIME_SCHEDULER]` 判空:undefined 时抛带上下文的 loud 错误(点名 symbol、指向 harness 挂载接线),替代裸 `undefined.prepare` TypeError。
|
|
99
|
-
- 回归测试 3 个(`test/bridge.spec.ts`,全量 399 绿含 3 新):view 缺 symbol → cell 收到含 `TOOL_RUNTIME_SCHEDULER` 的 loud 错误;`scheduler.prepare` 抛错 → binding settle 为可见错误;classify 抛错 → lane backstop settle 且进程存活。
|
|
100
|
-
|
|
101
|
-
### 六.3 根因探针(本轮结论,待活体 daemon 复核)
|
|
102
|
-
|
|
103
|
-
- **根因已锁定并更正(2026-09-02 活体探针 + 受控实验;推翻本节 2026-09-01 的 shadow 分支推断)**:真实根因是**部署拓扑 dual-copy**——崩溃期(2026-09-01 17:39–18:02)部署位插件嵌套 `node_modules/@deepseek-ai/*` 的 17 条 symlink(已退役 dev 接线)指向 dsh-alpha 源码树,插件 import 的 `TOOL_RUNTIME_SCHEDULER` 来自 alpha 副本的 dsh-tools 模块实例;daemon 的 `ToolRuntime` 实例由 host vendored 副本构建,其 symbol 字段以 host 副本 symbol 为键。两 symbol 为不同 `Symbol()` 实例:跨副本读实例字段 → `undefined`(`executionMode` 等字符串键方法不受影响、照常工作——症状逐字吻合)。
|
|
104
|
-
- 证据链(2026-09-02):① 活体 4 探针(`tool.bash` fg/bg、`tool.read`、`tool.subagent` bg:false/bg:true)经 `eval` cell 路径 4/4 真实结果——现行生产形态 symbol 在位;② 部署位 `require.resolve('@deepseek-ai/dsh-tools')` = `/home/u1/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-tools/lib/index.js`(host vendored,③→④ 层);③ 受控实验:host 与 z_dsh-alpha 两副本 `TOOL_RUNTIME_SCHEDULER` 严格不等,host 键实例经 alpha symbol 读 → `undefined`、经 host symbol 读 → object;④ 时间线闭环:改名 `dsh-alpha → z_dsh-alpha`(2026-09-01 22:29)→ symlink 悬空 → 2026-09-02 清理删除 → daemon 重启 → 自愈。
|
|
105
|
-
- shadow 分支理论为何被否:它与症状吻合但从未被活体证实,且无法解释「拓扑清理 + 重启后自愈」;dual-copy 机制同时解释故障与恢复。v0.2.1c 报告引「symbol 严格相等」的排除测量与崩溃期拓扑矛盾(疑测同一副本两次或取于改名后),以直接证据为准。
|
|
106
|
-
- cordis traceable proxy 本身无嫌疑(symbol 转发分支在位;现行活体 plain read 即返回带 symbol 原生实例)。
|
|
107
|
-
- dashr 侧交付边界:六.1+六.2(不崩溃 + loud 错误)继续有效;dual-copy 已由部署拓扑收敛消除(部署纪律:插件 `node_modules/@deepseek-ai/` 只留 `schemastery`+`cosmokit`);v0.2.1d 给 guard 错误附 dsh-tools 解析路径实现事件自诊断。
|
|
108
|
-
### 六.4 生产形态挂载测试(说明)
|
|
109
|
-
|
|
110
|
-
- 三个回归用例以「缺 symbol 视图」「prepare 抛错」「classify 抛错」三种形态驱动 `createRunCellTool`,覆盖六.1+六.2 全部防御面;测试组合(根作用域原生实例)本就 symbol 在位,断言 `runtimeCtx.tools[TOOL_RUNTIME_SCHEDULER]` 定义的用例属于测试组合自身形态(根挂载原生实例),无法复现生产 realm 差异——该差异须在活体 daemon 上复核(六.5)。
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Bridge the tool layer, not the service layer
|
|
2
|
-
|
|
3
|
-
DASHR's `rlm()` family dispatches upstream delegation tools through the tool registry (nested sub-dispatch), not `ctx.subagents` service methods. The tool layer carries the deployment's enforcement surface — approval pipeline, sandbox policy, per-instance config (maxDepth, backgroundMode, persona) — that a direct service call would silently bypass. The cost: per-call model selection is impossible, because the tool schema exposes no `model` parameter; `rlm(model=...)` from 0.1.4 is dropped, and `subagentModel` degrades to a static `agentOptions.model` in the preset patch.
|
|
4
|
-
|
|
5
|
-
## Considered Options
|
|
6
|
-
|
|
7
|
-
- **Service layer direct** (0.1.4's approach): `ctx.subagents.start()` with a hand-built request. Full control over request fields (including `agentOptions.model` and `maxDepth`), but every policy the tool instance would have applied must be re-implemented or lost.
|
|
8
|
-
- **Tool layer nested dispatch** (chosen): `rlm("spawn")` executes the registry's `subagent` tool with a parent token. Upstream policy is inherited wholesale; the schema boundary is the tool's own contract.
|
|
9
|
-
|
|
10
|
-
## Consequences
|
|
11
|
-
|
|
12
|
-
- `rlm(mode, prompt, *, label, run_in_background)` — no `model` kwarg. Parent-model inheritance is the default; a different child model requires a preset patch, not a call argument.
|
|
13
|
-
- Depth enforcement comes from the tool instance's `maxDepth` config, patched to 10 in the preset (see `dev/kernel-refactoring/V0.1.5-development-plan.md` Q22/Q23).
|
|
14
|
-
- The upstream delegation tools (`subagent`, `subagent_fork`, `interrupt_agent`) must stay registered and executable even though the model never sees their names — which is why masking is presentation-only (ADR-0002).
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Masking is presentation-only
|
|
2
|
-
|
|
3
|
-
Hiding exactly two upstream A2A tool names (`send_message` — the parent→child downlink — and `report` — the child→parent uplink) from the model is done by excluding them from the generated Tool Catalog text and from the kernel binding names — nothing else. The two tools stay registered, executable, and dispatchable; the single `send_message` bridge dispatches them internally. Every OTHER upstream delegation tool (`subagent`, `subagent_fork`, `list_agents`, `interrupt_agent`, `workflow`, `ralph`) is exposed directly as a native `tool.*` member — no re-wrapping (v0.1.9).
|
|
4
|
-
|
|
5
|
-
## Considered Options
|
|
6
|
-
|
|
7
|
-
- **`restrict()` at runtime**: hides names in the registry's model-facing view, but validates against the live view at call time — ordering hazards against late tool registration can fail the whole preset mount.
|
|
8
|
-
- **`disabled: true` include patches**: physically unregisters the tool, which also removes the bridge's dispatch target. Masking must not break the bridge.
|
|
9
|
-
- **Presentation-only exclusion** (chosen): the registry is never touched. The model's surface (wire schema collapse to `eval`, Tool Catalog text, kernel bindings) is entirely DASHR-generated, so exclusion happens at the two points DASHR owns.
|
|
10
|
-
|
|
11
|
-
## Consequences
|
|
12
|
-
|
|
13
|
-
- The masked tools remain in the registry and are reachable via nested sub-dispatch with a parent token, which passes the model-direct guard.
|
|
14
|
-
- **Field-verified (v0.1.8d, `both` presentation mode)**: reachable by MODEL-DIRECT native call too — no parent token needed. A probe calling the masked `skill({"name":…})` through the API function-call surface executed in full. Cause: the mask registers no visibility filter, so the name stays in `view(scope).visible`, and `resolveExecution` collapses model-direct calls only under `code` mode. The mask is an ADVERTISING cut, not an enforcement point; the hard gate is the REPL binding allowlist (`unknown binding`). If a deployment ever needs true model-direct rejection, the mechanisms are a visibility-layer `restrict()` (rejected here for ordering hazards) or a `code`-mode collapse — both are deployment-level decisions, not mask-level ones.
|
|
15
|
-
- Zero upstream mutation means zero interference with host-plane modules that enumerate or interact with the delegation tools.
|