@nexus-aethra/dsh-box-sandbox 0.1.0 → 0.1.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/README.md +43 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +151 -48
- package/dist/rpc.d.mts +9 -0
- package/dist/rpc.mjs +21 -4
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -81,8 +81,48 @@ container.
|
|
|
81
81
|
|
|
82
82
|
pnpm install
|
|
83
83
|
pnpm typecheck
|
|
84
|
+
pnpm check:outputs # static: schema vs what the daemon returns
|
|
85
|
+
pnpm check:tools # live: every tool called against a running Box
|
|
84
86
|
pnpm build
|
|
85
|
-
node smoke.mjs # 10 checks against a running DSH Box
|
|
86
87
|
|
|
87
|
-
`
|
|
88
|
-
|
|
88
|
+
`check:tools` needs a running daemon: it imports the built plugin with a stub
|
|
89
|
+
host context, calls every tool's `execute` for real, and validates each answer
|
|
90
|
+
against that tool's own output schema — the same check the host applies, at the
|
|
91
|
+
moment the answer comes back. It exists because a tool whose schema disagrees
|
|
92
|
+
with its result does not fail at build time; it fails when someone presses the
|
|
93
|
+
button, and the only way to see that is to press it.
|
|
94
|
+
|
|
95
|
+
`check:tools` is a local check and does not run in CI, which has no daemon.
|
|
96
|
+
The tag pipeline runs `check:outputs` instead, because that one needs nothing
|
|
97
|
+
but the repository.
|
|
98
|
+
|
|
99
|
+
node smoke.mjs # transport-level: ping, stale-port failure, recovery
|
|
100
|
+
|
|
101
|
+
`smoke.mjs` reads the real discovery record and checks the wire contract.
|
|
102
|
+
|
|
103
|
+
## Releasing
|
|
104
|
+
|
|
105
|
+
This package's version is the DSH Box app's version. One tag, one release, and
|
|
106
|
+
`release.yml` checks all four manifests against it before anything is built —
|
|
107
|
+
bumping the app and forgetting this one fails the pipeline rather than
|
|
108
|
+
publishing a plugin whose version claims to match a build it was never part of.
|
|
109
|
+
|
|
110
|
+
To release:
|
|
111
|
+
|
|
112
|
+
1. Bump `version` in this `package.json`, the root `package.json`,
|
|
113
|
+
`src-tauri/tauri.conf.json` and `src-tauri/Cargo.toml` to the same number.
|
|
114
|
+
2. Run `pnpm check:tools` against a real Box. CI cannot — it has no daemon —
|
|
115
|
+
so this is the only place the live check happens.
|
|
116
|
+
3. Commit, tag `v<version>`, push the tag.
|
|
117
|
+
|
|
118
|
+
The `publish-plugin` job runs after the installers are built, so a release
|
|
119
|
+
whose app build failed does not put a version on npm. It typechecks, runs the
|
|
120
|
+
schema reconciliation, builds `dist/` (gitignored, so never restored from the
|
|
121
|
+
commit), prints the tarball contents, skips a version npm already has, refuses
|
|
122
|
+
to publish below what npm has, and publishes with provenance.
|
|
123
|
+
|
|
124
|
+
Auth is npm trusted publishing over OIDC — there is no long-lived token to
|
|
125
|
+
leak. One-time setup on npmjs.com, under the package's Settings → Trusted
|
|
126
|
+
Publishers: provider **GitHub Actions**, account `aethra-tech`, repository
|
|
127
|
+
`DSHBox`, workflow filename `release.yml`. Rename that file and npm has to be
|
|
128
|
+
told again.
|
package/dist/index.d.mts
CHANGED
|
@@ -4,9 +4,9 @@ import { Context } from "@deepseek-ai/cordis";
|
|
|
4
4
|
/** Cordis plugin name used by loader diagnostics and the patch overlay. */
|
|
5
5
|
declare const name = "dsh-box-sandbox";
|
|
6
6
|
/**
|
|
7
|
-
* Services this plugin depends on.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Services this plugin depends on. `tools` to register the box_* surface and
|
|
8
|
+
* `systemPrompt` for the cross-tool workflow section; the client itself is
|
|
9
|
+
* pure transport and needs nothing.
|
|
10
10
|
*/
|
|
11
11
|
declare const inject: string[];
|
|
12
12
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -165,11 +165,6 @@ async function withSession(id, method, params) {
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
function base64Of(value) {
|
|
169
|
-
const data = asRecord(value).data;
|
|
170
|
-
if (typeof data !== "string") throw new Error("screenshot result carried no image data");
|
|
171
|
-
return data;
|
|
172
|
-
}
|
|
173
168
|
/**
|
|
174
169
|
* Register one tool against the host registry.
|
|
175
170
|
*
|
|
@@ -211,6 +206,14 @@ function nextHint(next) {
|
|
|
211
206
|
return "\nnext: " + next;
|
|
212
207
|
}
|
|
213
208
|
/**
|
|
209
|
+
* A string that may legitimately be absent.
|
|
210
|
+
*
|
|
211
|
+
* The daemon reports these as ${Option<String>}: a miss is null, not
|
|
212
|
+
* an empty string, and a schema that says $string alone rejects it — so a
|
|
213
|
+
* click that worked is reported as a failed tool call.
|
|
214
|
+
*/
|
|
215
|
+
const nullable = { oneOf: [{ type: "string" }, { type: "null" }] };
|
|
216
|
+
/**
|
|
214
217
|
* Render a click, distinguishing the two outcomes an agent must not confuse.
|
|
215
218
|
*
|
|
216
219
|
* A dispatched event is not a delivered one. When something covers the target
|
|
@@ -236,7 +239,7 @@ function renderClick(value, target) {
|
|
|
236
239
|
function applyBoxTools(ctx) {
|
|
237
240
|
registerTool(ctx, {
|
|
238
241
|
name: "box_screenshot",
|
|
239
|
-
description: "Save what a running dshbox container currently renders as a PNG, and report where.
|
|
242
|
+
description: "Save what a running dshbox container currently renders as a PNG, and report where.You get the file path and the image size, not the picture: a tool result is assistant-sidecontent and the host sends text only on that side, so an image cannot arrive. Hand the pathto a person to look at, and use box_page_text to find out what is on the page. In practice:describe the page in your answer from box_page_text, and take a screenshot when the layoutor a rendering is what is worth showing rather than describing.",
|
|
240
243
|
parameters: {
|
|
241
244
|
type: "object",
|
|
242
245
|
properties: {
|
|
@@ -258,35 +261,38 @@ function applyBoxTools(ctx) {
|
|
|
258
261
|
additionalProperties: false,
|
|
259
262
|
properties: {
|
|
260
263
|
containerId: { type: "string" },
|
|
261
|
-
|
|
264
|
+
path: { type: "string" },
|
|
265
|
+
width: { type: "number" },
|
|
266
|
+
height: { type: "number" },
|
|
267
|
+
bytes: { type: "number" }
|
|
262
268
|
},
|
|
263
|
-
required: [
|
|
269
|
+
required: [
|
|
270
|
+
"containerId",
|
|
271
|
+
"path",
|
|
272
|
+
"width",
|
|
273
|
+
"height",
|
|
274
|
+
"bytes"
|
|
275
|
+
]
|
|
264
276
|
},
|
|
265
277
|
render: (_args, value) => {
|
|
266
278
|
const record = asRecord(value);
|
|
267
|
-
const shot = asRecord(record.attachment);
|
|
268
279
|
return [{
|
|
269
280
|
type: "text",
|
|
270
|
-
text: "Screenshot of container " + String(record.containerId) + "
|
|
271
|
-
}, {
|
|
272
|
-
type: "image",
|
|
273
|
-
attachment: record.attachment
|
|
281
|
+
text: "Screenshot of container " + String(record.containerId) + " (" + String(record.width) + "x" + String(record.height) + ", " + String(record.bytes) + " bytes) saved to " + String(record.path) + "."
|
|
274
282
|
}];
|
|
275
283
|
}
|
|
276
284
|
},
|
|
277
285
|
async execute(args) {
|
|
278
286
|
const containerId = containerIdOf(args);
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
});
|
|
283
|
-
const data = Buffer.from(base64Of(raw), "base64");
|
|
287
|
+
const result = await withSession(containerId, "debug_capture_screenshot", { fullPage: asRecord(args).fullPage === true });
|
|
288
|
+
const path = result.path;
|
|
289
|
+
if (typeof path !== "string" || path === "") throw new Error("screenshot result carried no file path");
|
|
284
290
|
return {
|
|
285
291
|
containerId,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
path,
|
|
293
|
+
width: result.width,
|
|
294
|
+
height: result.height,
|
|
295
|
+
bytes: result.bytes
|
|
290
296
|
};
|
|
291
297
|
}
|
|
292
298
|
});
|
|
@@ -327,12 +333,12 @@ function applyBoxTools(ctx) {
|
|
|
327
333
|
properties: {
|
|
328
334
|
containerId: { type: "string" },
|
|
329
335
|
name: { type: "string" },
|
|
330
|
-
matched:
|
|
336
|
+
matched: nullable,
|
|
331
337
|
landed: { type: "boolean" },
|
|
332
|
-
hitTag:
|
|
333
|
-
hitText:
|
|
334
|
-
occludedBy:
|
|
335
|
-
selector:
|
|
338
|
+
hitTag: nullable,
|
|
339
|
+
hitText: nullable,
|
|
340
|
+
occludedBy: nullable,
|
|
341
|
+
selector: nullable,
|
|
336
342
|
x: { type: "number" },
|
|
337
343
|
y: { type: "number" },
|
|
338
344
|
tag: { type: "string" },
|
|
@@ -406,12 +412,17 @@ function applyBoxTools(ctx) {
|
|
|
406
412
|
properties: {
|
|
407
413
|
containerId: { type: "string" },
|
|
408
414
|
x: { type: "number" },
|
|
409
|
-
y: { type: "number" }
|
|
415
|
+
y: { type: "number" },
|
|
416
|
+
landed: { type: "boolean" },
|
|
417
|
+
hitTag: nullable,
|
|
418
|
+
hitText: nullable,
|
|
419
|
+
occludedBy: nullable
|
|
410
420
|
},
|
|
411
421
|
required: [
|
|
412
422
|
"containerId",
|
|
413
423
|
"x",
|
|
414
|
-
"y"
|
|
424
|
+
"y",
|
|
425
|
+
"landed"
|
|
415
426
|
]
|
|
416
427
|
},
|
|
417
428
|
render: (_args, value) => renderClick(value, "at (" + String(asRecord(value).x) + ", " + String(asRecord(value).y) + ")")
|
|
@@ -533,7 +544,9 @@ function applyBoxTools(ctx) {
|
|
|
533
544
|
scrollHeight: { type: "number" },
|
|
534
545
|
viewportHeight: { type: "number" },
|
|
535
546
|
screensBelow: { type: "number" },
|
|
536
|
-
moved: { type: "boolean" }
|
|
547
|
+
moved: { type: "boolean" },
|
|
548
|
+
scroller: nullable,
|
|
549
|
+
scrollerIsDocument: { type: "boolean" }
|
|
537
550
|
},
|
|
538
551
|
required: [
|
|
539
552
|
"containerId",
|
|
@@ -587,7 +600,7 @@ function applyBoxTools(ctx) {
|
|
|
587
600
|
properties: {
|
|
588
601
|
containerId: { type: "string" },
|
|
589
602
|
inserted: { type: "number" },
|
|
590
|
-
focused:
|
|
603
|
+
focused: nullable,
|
|
591
604
|
landed: { type: "boolean" }
|
|
592
605
|
},
|
|
593
606
|
required: [
|
|
@@ -608,9 +621,97 @@ function applyBoxTools(ctx) {
|
|
|
608
621
|
const containerId = containerIdOf(args);
|
|
609
622
|
const record = asRecord(args);
|
|
610
623
|
if (typeof record.text !== "string" || record.text === "") throw new Error("text is required and must be non-empty");
|
|
624
|
+
const result = await withSession(containerId, "debug_type_text", { text: record.text });
|
|
611
625
|
return {
|
|
612
626
|
containerId,
|
|
613
|
-
|
|
627
|
+
inserted: result.inserted,
|
|
628
|
+
focused: result.focused,
|
|
629
|
+
landed: result.focused !== null
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
registerTool(ctx, {
|
|
634
|
+
name: "box_console",
|
|
635
|
+
description: "Read what the page has logged, and clear the buffer — a second call reports only what arrived since the first. Uncaught errors are included whether or not any code caught them, which is the point: a client plugin that fails to register leaves a page that looks fine and says nothing, and this is the only tool that sees it. Repeated identical lines are collapsed into one entry with a count. The levels default to error and warning because log-level traffic is usually dozens of lines per page load; pass levels to ask for the rest when the failures are not the problem.",
|
|
636
|
+
parameters: {
|
|
637
|
+
type: "object",
|
|
638
|
+
properties: {
|
|
639
|
+
containerId: {
|
|
640
|
+
type: "string",
|
|
641
|
+
description: "Container whose console to read."
|
|
642
|
+
},
|
|
643
|
+
levels: {
|
|
644
|
+
type: "array",
|
|
645
|
+
items: {
|
|
646
|
+
type: "string",
|
|
647
|
+
enum: [
|
|
648
|
+
"log",
|
|
649
|
+
"info",
|
|
650
|
+
"debug",
|
|
651
|
+
"warning",
|
|
652
|
+
"error",
|
|
653
|
+
"assert"
|
|
654
|
+
]
|
|
655
|
+
},
|
|
656
|
+
description: "Which severities to return. Defaults to error and warning."
|
|
657
|
+
},
|
|
658
|
+
limit: {
|
|
659
|
+
type: "number",
|
|
660
|
+
description: "Most recent entries to return. Defaults to 50, capped at 500."
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
required: ["containerId"]
|
|
664
|
+
},
|
|
665
|
+
timeoutMs: WARM_BUDGET_MS,
|
|
666
|
+
output: {
|
|
667
|
+
schema: {
|
|
668
|
+
type: "object",
|
|
669
|
+
additionalProperties: false,
|
|
670
|
+
properties: {
|
|
671
|
+
containerId: { type: "string" },
|
|
672
|
+
entries: { type: "array" },
|
|
673
|
+
total: { type: "number" },
|
|
674
|
+
truncated: { type: "boolean" },
|
|
675
|
+
raw: { type: "number" },
|
|
676
|
+
methods: { type: "array" }
|
|
677
|
+
},
|
|
678
|
+
required: [
|
|
679
|
+
"containerId",
|
|
680
|
+
"entries",
|
|
681
|
+
"total",
|
|
682
|
+
"truncated",
|
|
683
|
+
"raw",
|
|
684
|
+
"methods"
|
|
685
|
+
]
|
|
686
|
+
},
|
|
687
|
+
render: (_args, value) => {
|
|
688
|
+
const record = asRecord(value);
|
|
689
|
+
const entries = Array.isArray(record.entries) ? record.entries : [];
|
|
690
|
+
if (entries.length === 0) return [{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: "The page in container " + String(record.containerId) + " logged nothing at the levels asked for, and logged nothing since the last read." + nextHint("if the page was just loaded, try again after the failing step, or widen levels to log")
|
|
693
|
+
}];
|
|
694
|
+
const lines = entries.map((entry) => {
|
|
695
|
+
const item = asRecord(entry);
|
|
696
|
+
const where = item.source ? " (" + String(item.source) + ")" : "";
|
|
697
|
+
const times = Number(item.count ?? 1) > 1 ? " x" + String(item.count) : "";
|
|
698
|
+
return "[" + String(item.level) + "]" + where + " " + String(item.text) + times;
|
|
699
|
+
});
|
|
700
|
+
return [{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: "Console of container " + String(record.containerId) + " — " + String(record.total) + " distinct message(s), oldest first" + (record.truncated === true ? " (truncated to the most recent)" : "") + ". Reading this cleared the buffer.\n" + lines.join("\n")
|
|
703
|
+
}];
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
async execute(args) {
|
|
707
|
+
const containerId = containerIdOf(args);
|
|
708
|
+
const record = asRecord(args);
|
|
709
|
+
const request = {};
|
|
710
|
+
if (Array.isArray(record.levels) && record.levels.length > 0) request.levels = record.levels;
|
|
711
|
+
if (typeof record.limit === "number") request.limit = record.limit;
|
|
712
|
+
return {
|
|
713
|
+
containerId,
|
|
714
|
+
...await withSession(containerId, "debug_console", request)
|
|
614
715
|
};
|
|
615
716
|
}
|
|
616
717
|
});
|
|
@@ -681,8 +782,8 @@ function applyBoxTools(ctx) {
|
|
|
681
782
|
kind: { type: "string" },
|
|
682
783
|
path: { type: "string" },
|
|
683
784
|
configured: { type: "boolean" },
|
|
684
|
-
configuredPath:
|
|
685
|
-
problem:
|
|
785
|
+
configuredPath: nullable,
|
|
786
|
+
problem: nullable
|
|
686
787
|
},
|
|
687
788
|
required: ["available"]
|
|
688
789
|
},
|
|
@@ -739,7 +840,7 @@ function applyBoxTools(ctx) {
|
|
|
739
840
|
const containerId = containerIdOf(args);
|
|
740
841
|
return {
|
|
741
842
|
containerId,
|
|
742
|
-
|
|
843
|
+
closed: (await getRpc().call("debug_close", { id: containerId })).closed === true
|
|
743
844
|
};
|
|
744
845
|
}
|
|
745
846
|
});
|
|
@@ -1260,6 +1361,7 @@ function registerManageTools(ctx) {
|
|
|
1260
1361
|
};
|
|
1261
1362
|
const title = optional(record, "title");
|
|
1262
1363
|
if (title !== void 0) params.title = title;
|
|
1364
|
+
if (flag(record, "restart")) params.restart = true;
|
|
1263
1365
|
const added = await rpc.call("add_container_workspace", params);
|
|
1264
1366
|
return {
|
|
1265
1367
|
action,
|
|
@@ -1725,7 +1827,7 @@ function registerManageTools(ctx) {
|
|
|
1725
1827
|
"from-version",
|
|
1726
1828
|
"describe",
|
|
1727
1829
|
"url",
|
|
1728
|
-
"browse"
|
|
1830
|
+
"browse-paths"
|
|
1729
1831
|
]),
|
|
1730
1832
|
name: {
|
|
1731
1833
|
type: "string",
|
|
@@ -1741,7 +1843,7 @@ function registerManageTools(ctx) {
|
|
|
1741
1843
|
},
|
|
1742
1844
|
containerId: {
|
|
1743
1845
|
type: "string",
|
|
1744
|
-
description: "Which container, for describe, url and browse."
|
|
1846
|
+
description: "Which container, for describe, url and browse-paths."
|
|
1745
1847
|
},
|
|
1746
1848
|
profile: {
|
|
1747
1849
|
type: "string",
|
|
@@ -1815,6 +1917,7 @@ function registerManageTools(ctx) {
|
|
|
1815
1917
|
action,
|
|
1816
1918
|
result: asObject(await rpc.call("container_url", { id: needId() }))
|
|
1817
1919
|
};
|
|
1920
|
+
case "browse-paths":
|
|
1818
1921
|
case "browse": return {
|
|
1819
1922
|
action,
|
|
1820
1923
|
result: asObject(await rpc.call("browse_container_paths", { id: needId() }))
|
|
@@ -1824,7 +1927,7 @@ function registerManageTools(ctx) {
|
|
|
1824
1927
|
"from-version",
|
|
1825
1928
|
"describe",
|
|
1826
1929
|
"url",
|
|
1827
|
-
"browse"
|
|
1930
|
+
"browse-paths"
|
|
1828
1931
|
]);
|
|
1829
1932
|
}
|
|
1830
1933
|
}
|
|
@@ -2124,15 +2227,11 @@ function asArray(value) {
|
|
|
2124
2227
|
/** Cordis plugin name used by loader diagnostics and the patch overlay. */
|
|
2125
2228
|
const name = "dsh-box-sandbox";
|
|
2126
2229
|
/**
|
|
2127
|
-
* Services this plugin depends on.
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2230
|
+
* Services this plugin depends on. `tools` to register the box_* surface and
|
|
2231
|
+
* `systemPrompt` for the cross-tool workflow section; the client itself is
|
|
2232
|
+
* pure transport and needs nothing.
|
|
2130
2233
|
*/
|
|
2131
|
-
const inject = [
|
|
2132
|
-
"tools",
|
|
2133
|
-
"attachments",
|
|
2134
|
-
"systemPrompt"
|
|
2135
|
-
];
|
|
2234
|
+
const inject = ["tools", "systemPrompt"];
|
|
2136
2235
|
/**
|
|
2137
2236
|
* Effective configuration after defaults.
|
|
2138
2237
|
*
|
|
@@ -2195,7 +2294,7 @@ async function status(config) {
|
|
|
2195
2294
|
* anything a tool already reports belongs in that tool.
|
|
2196
2295
|
*/
|
|
2197
2296
|
const WORKFLOW_PROMPT_SECTION = "dsh-box-sandbox:page-workflow";
|
|
2198
|
-
const WORKFLOW_PROMPT_ORDER =
|
|
2297
|
+
const WORKFLOW_PROMPT_ORDER = 3350;
|
|
2199
2298
|
function pageWorkflowPrompt() {
|
|
2200
2299
|
return [
|
|
2201
2300
|
"Debugging a page with the box_* tools:",
|
|
@@ -2211,7 +2310,11 @@ function pageWorkflowPrompt() {
|
|
|
2211
2310
|
"Before typing, click the field. box_type_text reports the focused element and inserts nothing",
|
|
2212
2311
|
"when no editable field holds focus.",
|
|
2213
2312
|
"If a page tool cannot find a browser, box_set_browser reports which one is in use and pins a",
|
|
2214
|
-
"different one."
|
|
2313
|
+
"different one.",
|
|
2314
|
+
"When a page looks right but behaves wrongly — a client bundle that never registered, a control",
|
|
2315
|
+
"that does nothing — read box_console. A page that failed to load its own assets says so only",
|
|
2316
|
+
"there; the rest of the surface reports a page that is present, which is exactly what a broken",
|
|
2317
|
+
"one also is."
|
|
2215
2318
|
].join("\n");
|
|
2216
2319
|
}
|
|
2217
2320
|
/** Register the plugin against a freshly mounted Cordis context. */
|
package/dist/rpc.d.mts
CHANGED
|
@@ -115,6 +115,15 @@ declare class DshboxRpc {
|
|
|
115
115
|
* never retried -- they are answers, not outages.
|
|
116
116
|
*/
|
|
117
117
|
call<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
118
|
+
/**
|
|
119
|
+
* The message for "discovery.json names a daemon that is not there".
|
|
120
|
+
*
|
|
121
|
+
* Staleness has two causes needing different fixes: the file may be
|
|
122
|
+
* missing (the daemon was never started) or it may name a pid that has
|
|
123
|
+
* exited (it crashed, or the machine rebooted). Both arrive here as a
|
|
124
|
+
* refused connection, so the record is read again to tell them apart.
|
|
125
|
+
*/
|
|
126
|
+
private unreachable;
|
|
118
127
|
/** Liveness probe. Resolves to the daemon's own status frame. */
|
|
119
128
|
ping(): Promise<{
|
|
120
129
|
pid: number;
|
package/dist/rpc.mjs
CHANGED
|
@@ -148,17 +148,34 @@ var DshboxRpc = class {
|
|
|
148
148
|
* never retried -- they are answers, not outages.
|
|
149
149
|
*/
|
|
150
150
|
async call(method, params) {
|
|
151
|
+
let lastStale = null;
|
|
151
152
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
152
153
|
const record = this.discovery();
|
|
153
154
|
try {
|
|
154
155
|
return await this.send(record, method, params);
|
|
155
156
|
} catch (error) {
|
|
156
|
-
if (
|
|
157
|
-
|
|
157
|
+
if (!isStaleEndpoint(error)) throw error;
|
|
158
|
+
lastStale = error;
|
|
159
|
+
if (attempt === 0) continue;
|
|
160
|
+
throw this.unreachable(method, lastStale);
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
|
-
/* c8 ignore next
|
|
161
|
-
throw
|
|
163
|
+
/* c8 ignore next */
|
|
164
|
+
throw this.unreachable(method, lastStale);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The message for "discovery.json names a daemon that is not there".
|
|
168
|
+
*
|
|
169
|
+
* Staleness has two causes needing different fixes: the file may be
|
|
170
|
+
* missing (the daemon was never started) or it may name a pid that has
|
|
171
|
+
* exited (it crashed, or the machine rebooted). Both arrive here as a
|
|
172
|
+
* refused connection, so the record is read again to tell them apart.
|
|
173
|
+
*/
|
|
174
|
+
unreachable(method, cause) {
|
|
175
|
+
const path = discoveryPath(this.configDir);
|
|
176
|
+
const record = readDiscovery(this.configDir);
|
|
177
|
+
if (record === null) return new DshboxUnavailableError("dshbox is not running, so " + method + " has nothing to talk to. Start it from the desktop app, or run `dshbox` once, then retry. No discovery record at " + path + ".", { cause });
|
|
178
|
+
return new DshboxUnavailableError("dshbox is not running, so " + method + " has nothing to talk to. The discovery record at " + path + " is stale: it names pid " + record.pid + " on port " + record.port + ", and nothing is listening there. Start the desktop app (or run `dshbox`) to bring up a new daemon; the stale record is replaced on startup.", { cause });
|
|
162
179
|
}
|
|
163
180
|
/** Liveness probe. Resolves to the daemon's own status frame. */
|
|
164
181
|
ping() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexus-aethra/dsh-box-sandbox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Host-side bridge from a DSH session to the DSH Box daemon: typed box_* tools an agent can call without a shell.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
"box",
|
|
11
11
|
"tools"
|
|
12
12
|
],
|
|
13
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/Nexus-Aethra/DSHBox/tree/main/plugins/dsh-box-sandbox",
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/Nexus-Aethra/DSHBox/issues"
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/
|
|
19
|
+
"url": "git+https://github.com/Nexus-Aethra/DSHBox.git",
|
|
20
20
|
"directory": "plugins/dsh-box-sandbox"
|
|
21
21
|
},
|
|
22
22
|
"author": "Nexus Aethra",
|
|
@@ -46,6 +46,14 @@
|
|
|
46
46
|
"patch": "./cordis.patch.yml"
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"watch": "tsdown --watch",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"check:outputs": "node scripts/check-output-schemas.mjs",
|
|
54
|
+
"check:tools": "node scripts/check-tool-outputs.mjs",
|
|
55
|
+
"deploy": "node scripts/deploy.mjs"
|
|
56
|
+
},
|
|
49
57
|
"peerDependencies": {
|
|
50
58
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
51
59
|
},
|
|
@@ -53,10 +61,5 @@
|
|
|
53
61
|
"@types/node": "^22.10.0",
|
|
54
62
|
"tsdown": "^0.22.2",
|
|
55
63
|
"typescript": "^5.6.0"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"build": "tsdown",
|
|
59
|
-
"watch": "tsdown --watch",
|
|
60
|
-
"typecheck": "tsc --noEmit"
|
|
61
64
|
}
|
|
62
|
-
}
|
|
65
|
+
}
|