@nexus-aethra/dsh-box-sandbox 0.2.0 → 0.2.1-alpha.1
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/dist/index.mjs +287 -23
- package/package.json +1 -1
- package/skills/dsh-box-tools/SKILL.md +10 -5
package/dist/index.mjs
CHANGED
|
@@ -109,7 +109,19 @@ async function waitForTask(id, options = {}) {
|
|
|
109
109
|
* it is an agent that will not.
|
|
110
110
|
*/
|
|
111
111
|
async function runTask(method, params, options = {}) {
|
|
112
|
-
|
|
112
|
+
return followTask(await getRpc().call(method, params), options);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Follow an already-enqueued task record to its end, with runTask's rules.
|
|
116
|
+
*
|
|
117
|
+
* Exists for the methods that only sometimes enqueue: the daemon answers them
|
|
118
|
+
* in line when no task was needed and with a task record when one was, and the
|
|
119
|
+
* caller cannot know which until the reply is in hand. A record without an id
|
|
120
|
+
* is not a task and is handed back untouched — deciding that shape is the
|
|
121
|
+
* caller's, since only it knows what the synchronous answer looks like.
|
|
122
|
+
*/
|
|
123
|
+
async function followTask(enqueued, options = {}) {
|
|
124
|
+
const { finished, task } = await waitForTask(enqueued.id, options);
|
|
113
125
|
if (!finished) throw new Error(`the ${task.kind} task did not finish within the budget; it is still ${task.status} at ${task.progress}%. Watch it with box_task, or read ${task.logPath ?? "its log"}.`);
|
|
114
126
|
if (failed(task)) throw new Error(`the ${task.kind} task ended ${task.status}` + (task.error ? `: ${task.error}` : "") + (task.rollbackError ? ` (rollback also failed: ${task.rollbackError})` : "") + (task.logPath ? `. Full detail in ${task.logPath}` : "."));
|
|
115
127
|
return task;
|
|
@@ -264,21 +276,24 @@ function applyBoxTools(ctx) {
|
|
|
264
276
|
path: { type: "string" },
|
|
265
277
|
width: { type: "number" },
|
|
266
278
|
height: { type: "number" },
|
|
267
|
-
bytes: { type: "number" }
|
|
279
|
+
bytes: { type: "number" },
|
|
280
|
+
url: { type: "string" },
|
|
281
|
+
title: nullable
|
|
268
282
|
},
|
|
269
283
|
required: [
|
|
270
284
|
"containerId",
|
|
271
285
|
"path",
|
|
272
286
|
"width",
|
|
273
287
|
"height",
|
|
274
|
-
"bytes"
|
|
288
|
+
"bytes",
|
|
289
|
+
"url"
|
|
275
290
|
]
|
|
276
291
|
},
|
|
277
292
|
render: (_args, value) => {
|
|
278
293
|
const record = asRecord(value);
|
|
279
294
|
return [{
|
|
280
295
|
type: "text",
|
|
281
|
-
text: "Screenshot of container " + String(record.containerId) + " (" + String(record.width) + "x" + String(record.height) + ", " + String(record.bytes) + " bytes) saved to " + String(record.path) + "."
|
|
296
|
+
text: "Screenshot of container " + String(record.containerId) + " (" + String(record.width) + "x" + String(record.height) + ", " + String(record.bytes) + " bytes) saved to " + String(record.path) + ". Page: " + String(record.url) + (record.title ? " — " + String(record.title) : "") + "."
|
|
282
297
|
}];
|
|
283
298
|
}
|
|
284
299
|
},
|
|
@@ -292,7 +307,9 @@ function applyBoxTools(ctx) {
|
|
|
292
307
|
path,
|
|
293
308
|
width: result.width,
|
|
294
309
|
height: result.height,
|
|
295
|
-
bytes: result.bytes
|
|
310
|
+
bytes: result.bytes,
|
|
311
|
+
url: result.url,
|
|
312
|
+
title: result.title
|
|
296
313
|
};
|
|
297
314
|
}
|
|
298
315
|
});
|
|
@@ -440,6 +457,108 @@ function applyBoxTools(ctx) {
|
|
|
440
457
|
};
|
|
441
458
|
}
|
|
442
459
|
});
|
|
460
|
+
registerTool(ctx, {
|
|
461
|
+
name: "box_hover",
|
|
462
|
+
description: "Park the pointer on a control and report what the hover revealed. Use it when a control exists only while hovered — row actions in this UI are display:none until :hover, so box_page_text does not list them and box_click_element cannot find them: hover the row first, then list or click. Target it the same way as box_click_element (role and name, or x,y when there is no name). The pointer stays where this leaves it until the next move or click, and the reply names what ended up under it, so a hover that landed on something else is visible immediately.",
|
|
463
|
+
parameters: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
containerId: {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "Container whose page to hover."
|
|
469
|
+
},
|
|
470
|
+
name: {
|
|
471
|
+
type: "string",
|
|
472
|
+
description: "Accessible name of the control to hover, matched exactly, e.g. the row name box_page_text reported."
|
|
473
|
+
},
|
|
474
|
+
role: {
|
|
475
|
+
type: "string",
|
|
476
|
+
description: "Role to require alongside the name, e.g. row or button."
|
|
477
|
+
},
|
|
478
|
+
nth: {
|
|
479
|
+
type: "number",
|
|
480
|
+
description: "Zero-based index among controls sharing the name. Omit for the first."
|
|
481
|
+
},
|
|
482
|
+
within: {
|
|
483
|
+
type: "string",
|
|
484
|
+
description: "Container name to scope the search to, when the same name appears in more than one place."
|
|
485
|
+
},
|
|
486
|
+
x: {
|
|
487
|
+
type: "number",
|
|
488
|
+
description: "Horizontal viewport coordinate, from the left edge. Use with y instead of name."
|
|
489
|
+
},
|
|
490
|
+
y: {
|
|
491
|
+
type: "number",
|
|
492
|
+
description: "Vertical viewport coordinate, from the top edge. Use with x instead of name."
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
required: ["containerId"]
|
|
496
|
+
},
|
|
497
|
+
timeoutMs: WARM_BUDGET_MS,
|
|
498
|
+
output: {
|
|
499
|
+
schema: {
|
|
500
|
+
type: "object",
|
|
501
|
+
additionalProperties: false,
|
|
502
|
+
properties: {
|
|
503
|
+
containerId: { type: "string" },
|
|
504
|
+
url: { type: "string" },
|
|
505
|
+
x: { type: "number" },
|
|
506
|
+
y: { type: "number" },
|
|
507
|
+
landed: { type: "boolean" },
|
|
508
|
+
hitTag: nullable,
|
|
509
|
+
hitText: nullable,
|
|
510
|
+
matched: nullable,
|
|
511
|
+
occludedBy: nullable
|
|
512
|
+
},
|
|
513
|
+
required: [
|
|
514
|
+
"containerId",
|
|
515
|
+
"url",
|
|
516
|
+
"x",
|
|
517
|
+
"y",
|
|
518
|
+
"landed"
|
|
519
|
+
]
|
|
520
|
+
},
|
|
521
|
+
render: (_args, value) => {
|
|
522
|
+
const record = asRecord(value);
|
|
523
|
+
const at = "at (" + String(record.x) + ", " + String(record.y) + ")";
|
|
524
|
+
if (record.landed === false) {
|
|
525
|
+
const cover = record.occludedBy ? " it was covered by <" + String(record.occludedBy) + ">" + (record.hitText ? " " + JSON.stringify(record.hitText) : "") : " nothing is under that point";
|
|
526
|
+
return [{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: "The hover " + at + " in container " + String(record.containerId) + " did NOT land:" + cover + "." + nextHint("hover the row or control by name, then call box_page_text to list what appeared")
|
|
529
|
+
}];
|
|
530
|
+
}
|
|
531
|
+
const under = record.hitTag ? " over <" + String(record.hitTag) + ">" : "";
|
|
532
|
+
return [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: "Pointer parked " + at + " in container " + String(record.containerId) + under + "." + (record.matched ? " Target: " + JSON.stringify(record.matched) + "." : "") + nextHint("call box_page_text again to list what the hover revealed, or box_click_element to act on it")
|
|
535
|
+
}];
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
async execute(args) {
|
|
539
|
+
const containerId = containerIdOf(args);
|
|
540
|
+
const record = asRecord(args);
|
|
541
|
+
const hasName = typeof record.name === "string" && record.name.trim() !== "";
|
|
542
|
+
const hasPoint = typeof record.x === "number" && typeof record.y === "number";
|
|
543
|
+
if (!hasName && !hasPoint) throw new Error("give a name (with optional role, nth, within) or both x and y");
|
|
544
|
+
if (hasName) return {
|
|
545
|
+
containerId,
|
|
546
|
+
...await withSession(containerId, "debug_hover", {
|
|
547
|
+
name: record.name,
|
|
548
|
+
role: typeof record.role === "string" ? record.role : void 0,
|
|
549
|
+
nth: typeof record.nth === "number" ? record.nth : void 0,
|
|
550
|
+
within: typeof record.within === "string" ? record.within : void 0
|
|
551
|
+
})
|
|
552
|
+
};
|
|
553
|
+
return {
|
|
554
|
+
containerId,
|
|
555
|
+
...await withSession(containerId, "debug_hover", {
|
|
556
|
+
x: record.x,
|
|
557
|
+
y: record.y
|
|
558
|
+
})
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
});
|
|
443
562
|
registerTool(ctx, {
|
|
444
563
|
name: "box_page_text",
|
|
445
564
|
description: "CALL THIS FIRST when you need to know what is on a page. It renders the whole page as text: every control and region with its role, name, on-screen position, and whether it is currently visible, and whether it is disabled. It needs no selector, and it is the only listing: there is no second way to ask what is on a page. Then hand the role+name it returns straight back to box_click_element. Anything marked BELOW FOLD needs box_scroll before you can act on it.",
|
|
@@ -456,7 +575,7 @@ function applyBoxTools(ctx) {
|
|
|
456
575
|
},
|
|
457
576
|
within: {
|
|
458
577
|
type: "string",
|
|
459
|
-
description: "
|
|
578
|
+
description: "Name of a container to scope the listing to, such as a dialog. Use it when a name is ambiguous: a settings dialog and the chat screen behind it can both offer the same one, and acting on the wrong element still looks like it worked."
|
|
460
579
|
},
|
|
461
580
|
limit: {
|
|
462
581
|
type: "number",
|
|
@@ -475,6 +594,8 @@ function applyBoxTools(ctx) {
|
|
|
475
594
|
count: { type: "number" },
|
|
476
595
|
truncated: { type: "boolean" },
|
|
477
596
|
belowFold: { type: "number" },
|
|
597
|
+
url: { type: "string" },
|
|
598
|
+
title: nullable,
|
|
478
599
|
elements: {
|
|
479
600
|
type: "array",
|
|
480
601
|
items: { type: "object" }
|
|
@@ -485,6 +606,7 @@ function applyBoxTools(ctx) {
|
|
|
485
606
|
"count",
|
|
486
607
|
"truncated",
|
|
487
608
|
"belowFold",
|
|
609
|
+
"url",
|
|
488
610
|
"elements"
|
|
489
611
|
]
|
|
490
612
|
},
|
|
@@ -493,7 +615,7 @@ function applyBoxTools(ctx) {
|
|
|
493
615
|
const elements = Array.isArray(record.elements) ? record.elements : [];
|
|
494
616
|
const below = Number(record.belowFold ?? 0);
|
|
495
617
|
const lines = [];
|
|
496
|
-
lines.push("Page of container " + String(record.containerId) + ": " + String(record.count) + " entr" + (record.count === 1 ? "y" : "ies") + (record.truncated === true ? " (truncated at the limit)." : "."));
|
|
618
|
+
lines.push("Page of container " + String(record.containerId) + " at " + String(record.url) + (record.title ? " — " + String(record.title) : "") + ": " + String(record.count) + " entr" + (record.count === 1 ? "y" : "ies") + (record.truncated === true ? " (truncated at the limit)." : "."));
|
|
497
619
|
if (below > 0) lines.push(below + " entr" + (below === 1 ? "y is" : "ies are") + " below the fold; call box_scroll to reach them.");
|
|
498
620
|
lines.push(renderPageElements(elements));
|
|
499
621
|
lines.push(nextHint("act on an entry with box_click_element (role+name) or box_click_at (x,y)"));
|
|
@@ -761,6 +883,111 @@ function applyBoxTools(ctx) {
|
|
|
761
883
|
};
|
|
762
884
|
}
|
|
763
885
|
});
|
|
886
|
+
registerTool(ctx, {
|
|
887
|
+
name: "box_navigate",
|
|
888
|
+
description: "Point the debugged page at a URL, or bring it back to the container app. With no url it navigates to the container's own live address — this is the recovery after a container restart: the host binds a fresh port on every start, so a page opened before the restart can never answer again, and every page tool against it fails or hangs. The reply reports where the page actually ended up (the app may redirect), not where it was asked to go.",
|
|
889
|
+
parameters: {
|
|
890
|
+
type: "object",
|
|
891
|
+
properties: {
|
|
892
|
+
containerId: {
|
|
893
|
+
type: "string",
|
|
894
|
+
description: "Container whose page to move."
|
|
895
|
+
},
|
|
896
|
+
url: {
|
|
897
|
+
type: "string",
|
|
898
|
+
description: "Absolute URL to open. Omit to go back to the container app itself."
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
required: ["containerId"]
|
|
902
|
+
},
|
|
903
|
+
timeoutMs: WARM_BUDGET_MS,
|
|
904
|
+
output: {
|
|
905
|
+
schema: {
|
|
906
|
+
type: "object",
|
|
907
|
+
additionalProperties: false,
|
|
908
|
+
properties: {
|
|
909
|
+
containerId: { type: "string" },
|
|
910
|
+
url: { type: "string" },
|
|
911
|
+
title: nullable,
|
|
912
|
+
navigatedTo: { type: "string" }
|
|
913
|
+
},
|
|
914
|
+
required: [
|
|
915
|
+
"containerId",
|
|
916
|
+
"url",
|
|
917
|
+
"navigatedTo"
|
|
918
|
+
]
|
|
919
|
+
},
|
|
920
|
+
render: (_args, value) => {
|
|
921
|
+
const record = asRecord(value);
|
|
922
|
+
return [{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: "Page of container " + String(record.containerId) + " now at " + String(record.url) + (record.title ? " — " + String(record.title) : "") + " (asked for " + String(record.navigatedTo) + ")." + nextHint("call box_page_text to see what the page shows now")
|
|
925
|
+
}];
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
async execute(args) {
|
|
929
|
+
const containerId = containerIdOf(args);
|
|
930
|
+
const record = asRecord(args);
|
|
931
|
+
const result = await withSession(containerId, "debug_navigate", { url: typeof record.url === "string" && record.url.trim() !== "" ? record.url.trim() : void 0 });
|
|
932
|
+
return {
|
|
933
|
+
containerId,
|
|
934
|
+
url: result.url,
|
|
935
|
+
title: result.title,
|
|
936
|
+
navigatedTo: result.navigatedTo
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
registerTool(ctx, {
|
|
941
|
+
name: "box_evaluate",
|
|
942
|
+
description: "Run one JavaScript expression in the debugged page and return its value. Use it for facts the other tools do not answer for: an attribute, a computed style, whether a selector matches, a value React keeps out of the accessibility tree. Non-string values come back JSON-encoded, so objects and arrays stay readable. The expression runs inside the page with the page's own scope — it can do anything the page's own scripts could, and no more.",
|
|
943
|
+
parameters: {
|
|
944
|
+
type: "object",
|
|
945
|
+
properties: {
|
|
946
|
+
containerId: {
|
|
947
|
+
type: "string",
|
|
948
|
+
description: "Container whose page to evaluate in."
|
|
949
|
+
},
|
|
950
|
+
expression: {
|
|
951
|
+
type: "string",
|
|
952
|
+
description: "JavaScript to evaluate, e.g. document.title or Boolean(document.querySelector(\".row-actions\"))."
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
required: ["containerId", "expression"]
|
|
956
|
+
},
|
|
957
|
+
timeoutMs: WARM_BUDGET_MS,
|
|
958
|
+
output: {
|
|
959
|
+
schema: {
|
|
960
|
+
type: "object",
|
|
961
|
+
additionalProperties: false,
|
|
962
|
+
properties: {
|
|
963
|
+
containerId: { type: "string" },
|
|
964
|
+
url: { type: "string" },
|
|
965
|
+
result: { type: "string" }
|
|
966
|
+
},
|
|
967
|
+
required: [
|
|
968
|
+
"containerId",
|
|
969
|
+
"url",
|
|
970
|
+
"result"
|
|
971
|
+
]
|
|
972
|
+
},
|
|
973
|
+
render: (_args, value) => {
|
|
974
|
+
const record = asRecord(value);
|
|
975
|
+
return [{
|
|
976
|
+
type: "text",
|
|
977
|
+
text: "Evaluated in container " + String(record.containerId) + " at " + String(record.url) + ":\n" + String(record.result)
|
|
978
|
+
}];
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
async execute(args) {
|
|
982
|
+
const containerId = containerIdOf(args);
|
|
983
|
+
const record = asRecord(args);
|
|
984
|
+
if (typeof record.expression !== "string" || record.expression.trim() === "") throw new Error("expression is required and must be non-empty");
|
|
985
|
+
return {
|
|
986
|
+
containerId,
|
|
987
|
+
...await withSession(containerId, "debug_evaluate", { expression: record.expression })
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
});
|
|
764
991
|
registerTool(ctx, {
|
|
765
992
|
name: "box_set_browser",
|
|
766
993
|
description: "Choose which browser the other box_* tools drive, and report the one in use. Call it when a page tool fails to launch a browser, or when auto-detection picks a browser you would rather not use. Pass an absolute path to a Chrome or Edge executable to pin it; pass an empty string to go back to auto-detection. The path is validated before it is saved, so a typo is rejected here rather than surfacing later as a failed launch.",
|
|
@@ -987,14 +1214,29 @@ function registerManageTools(ctx) {
|
|
|
987
1214
|
plugins: {
|
|
988
1215
|
type: "array",
|
|
989
1216
|
items: { type: "object" }
|
|
1217
|
+
},
|
|
1218
|
+
self: {
|
|
1219
|
+
type: "object",
|
|
1220
|
+
additionalProperties: false,
|
|
1221
|
+
properties: {
|
|
1222
|
+
role: {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
enum: ["container", "external"]
|
|
1225
|
+
},
|
|
1226
|
+
containerId: { oneOf: [{ type: "string" }, { type: "null" }] }
|
|
1227
|
+
},
|
|
1228
|
+
required: ["role", "containerId"]
|
|
990
1229
|
}
|
|
991
1230
|
}, [
|
|
992
1231
|
"box",
|
|
993
1232
|
"containers",
|
|
994
1233
|
"templates",
|
|
995
|
-
"plugins"
|
|
1234
|
+
"plugins",
|
|
1235
|
+
"self"
|
|
996
1236
|
], (value) => {
|
|
997
1237
|
const lines = [];
|
|
1238
|
+
const self = value.self ?? {};
|
|
1239
|
+
lines.push(`Self: ${self.role === "container" ? `inside container ${String(self.containerId)} — lifecycle verbs on it are refused` : "external (not inside a Box container)"}.`);
|
|
998
1240
|
const box = value.box ?? {};
|
|
999
1241
|
const containers = value.containers ?? [];
|
|
1000
1242
|
const templates = value.templates ?? [];
|
|
@@ -1013,7 +1255,8 @@ function registerManageTools(ctx) {
|
|
|
1013
1255
|
box: {},
|
|
1014
1256
|
containers: [],
|
|
1015
1257
|
templates: [],
|
|
1016
|
-
plugins: []
|
|
1258
|
+
plugins: [],
|
|
1259
|
+
self: selfIdentity()
|
|
1017
1260
|
};
|
|
1018
1261
|
if (want.includes("box")) result.box = asObject(await rpc.call("get_info"));
|
|
1019
1262
|
if (want.includes("containers")) result.containers = asArray(await rpc.call("list_containers"));
|
|
@@ -1379,11 +1622,12 @@ function registerManageTools(ctx) {
|
|
|
1379
1622
|
if (title !== void 0) params.title = title;
|
|
1380
1623
|
if (flag(record, "restart")) params.restart = true;
|
|
1381
1624
|
const added = await rpc.call("add_container_workspace", params);
|
|
1625
|
+
if (added !== null && typeof added === "object" && typeof added.id === "string" && typeof added.status === "string") await followTask(added, { timeoutMs: 84e4 });
|
|
1382
1626
|
return {
|
|
1383
1627
|
action,
|
|
1384
1628
|
containerId,
|
|
1385
1629
|
workspaces: (await rpc.call("list_container_workspaces", { id: containerId })).workspaces ?? [],
|
|
1386
|
-
created: added
|
|
1630
|
+
created: added !== null && !("status" in added) && added.created === true
|
|
1387
1631
|
};
|
|
1388
1632
|
}
|
|
1389
1633
|
badAction("workspace", action, ["list", "add"]);
|
|
@@ -2194,28 +2438,48 @@ function asObject(value) {
|
|
|
2194
2438
|
return { value };
|
|
2195
2439
|
}
|
|
2196
2440
|
/**
|
|
2197
|
-
*
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2441
|
+
* The daemon stamps `DSHBOX_CONTAINER_ID` onto the host process it spawns, so
|
|
2442
|
+
* a mounted plugin can name its own container without guessing. The stamp is
|
|
2443
|
+
* the authority; the install-path parse below is the fallback for hosts
|
|
2444
|
+
* started by older daemons, before the stamp existed — there, nothing hands a
|
|
2445
|
+
* plugin its own container id, so it is read back out of where the plugin is
|
|
2446
|
+
* installed, which is always
|
|
2202
2447
|
* `<instance>/profile/profiles/<p>/node_modules/...`: the container id is a
|
|
2203
2448
|
* directory name on the way to it.
|
|
2204
2449
|
*
|
|
2205
|
-
*
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2209
|
-
*
|
|
2450
|
+
* The fallback matches the segment's prefix, not the id's full shape. An
|
|
2451
|
+
* earlier version matched a UUID 8-4-4-4-12 and silently never matched
|
|
2452
|
+
* anything: ids are `container-<unix timestamp>...`, and a guard that quietly
|
|
2453
|
+
* does not fire is worse than no guard, because the code reads as though the
|
|
2454
|
+
* case is handled.
|
|
2210
2455
|
*/
|
|
2211
|
-
function
|
|
2456
|
+
function selfIdentity() {
|
|
2457
|
+
const stamped = process.env.DSHBOX_CONTAINER_ID?.trim();
|
|
2458
|
+
if (stamped) return {
|
|
2459
|
+
role: "container",
|
|
2460
|
+
containerId: stamped
|
|
2461
|
+
};
|
|
2212
2462
|
let here;
|
|
2213
2463
|
try {
|
|
2214
2464
|
here = dirname(fileURLToPath(import.meta.url));
|
|
2215
2465
|
} catch {
|
|
2216
|
-
return
|
|
2466
|
+
return {
|
|
2467
|
+
role: "external",
|
|
2468
|
+
containerId: null
|
|
2469
|
+
};
|
|
2217
2470
|
}
|
|
2218
|
-
|
|
2471
|
+
const own = here.split(/[\\/]/).find((part) => /^container-\d/.test(part));
|
|
2472
|
+
return own ? {
|
|
2473
|
+
role: "container",
|
|
2474
|
+
containerId: own
|
|
2475
|
+
} : {
|
|
2476
|
+
role: "external",
|
|
2477
|
+
containerId: null
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
/** True when this plugin is running inside the container it was asked about. */
|
|
2481
|
+
function runsInside(containerId) {
|
|
2482
|
+
return selfIdentity().containerId === containerId;
|
|
2219
2483
|
}
|
|
2220
2484
|
/** Read an optional finite number, treating blank and NaN as absent. */
|
|
2221
2485
|
function number(args, field) {
|
package/package.json
CHANGED
|
@@ -16,6 +16,11 @@ result, not a summary.
|
|
|
16
16
|
- **`box_overview` before anything else.** It is the only listing tool: containers,
|
|
17
17
|
templates, and the plugins the box knows. Acting on an id you guessed is the
|
|
18
18
|
failure this tool removes.
|
|
19
|
+
- **Check `self` in the same `box_overview` answer.** It says where you sit:
|
|
20
|
+
`role: container` with the container id you are running in, or `external` when
|
|
21
|
+
you watch the box from outside (a desktop or web DSH). A `container` agent
|
|
22
|
+
must not stop, restart, rebuild or remove its own container — `box_lifecycle`
|
|
23
|
+
refuses, because the call could never report back.
|
|
19
24
|
- **Anything that changes state through a task needs `box_task`.** Call it with
|
|
20
25
|
`action: 'status'` and the task id the action returned, or `action: 'list'` to
|
|
21
26
|
find what is already running. A task that has not finished has not happened.
|
|
@@ -27,7 +32,7 @@ result, not a summary.
|
|
|
27
32
|
|
|
28
33
|
| Tool | What it does |
|
|
29
34
|
|---|---|
|
|
30
|
-
| `box_overview` | Containers, templates, plugins. Start here. |
|
|
35
|
+
| `box_overview` | Containers, templates, plugins, and where you sit (`self`). Start here. |
|
|
31
36
|
| `box_lifecycle` | start / stop / stop-now / restart / rebuild / remove a container |
|
|
32
37
|
| `box_create` | from-template, from-version, describe, url, browse-paths |
|
|
33
38
|
| `box_resources` | list, read, write, extract, inject, rm, rm-view, view-on, view-off, prune, types, views |
|
|
@@ -35,7 +40,7 @@ result, not a summary.
|
|
|
35
40
|
| `box_templates` | list, show, pull, import, export, remove, prune, catalog |
|
|
36
41
|
| `box_settings` | toolchains, installed, catalog, refresh-catalog, data, references, template-info, template-list, save-mirrors, set-runtime-directory, uninstall-dsh |
|
|
37
42
|
| `box_task` | list, status, cancel, delete |
|
|
38
|
-
| `box_workspace` | list, add |
|
|
43
|
+
| `box_workspace` | list, add (restart: true stops the container, adds, starts it again) |
|
|
39
44
|
| `box_build` | build a template from a boxfile; apply a resource document |
|
|
40
45
|
|
|
41
46
|
## Rules that are easy to get wrong
|
|
@@ -60,9 +65,9 @@ result, not a summary.
|
|
|
60
65
|
## Debugging a page (separate tool family)
|
|
61
66
|
|
|
62
67
|
The same plugin also registers browser tools against a headless Chromium session:
|
|
63
|
-
`box_page_text`, `box_click_element`, `box_click_at`, `box_type_text`, `box_press_key`,
|
|
64
|
-
`box_scroll`, `box_screenshot`, `box_console`, `box_set_viewport`,
|
|
65
|
-
`box_close`.
|
|
68
|
+
`box_page_text`, `box_click_element`, `box_click_at`, `box_hover`, `box_type_text`, `box_press_key`,
|
|
69
|
+
`box_scroll`, `box_navigate`, `box_evaluate`, `box_screenshot`, `box_console`, `box_set_viewport`,
|
|
70
|
+
`box_set_browser`, `box_close`.
|
|
66
71
|
|
|
67
72
|
When using them, always start with the observation tool and act only on what it
|
|
68
73
|
returned. The tool descriptions carry the full workflow; follow them.
|