@adhdev/daemon-standalone 0.9.82-rc.551 → 0.9.82-rc.553

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/public/index.html CHANGED
@@ -7,9 +7,9 @@
7
7
  <meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
8
8
  <link rel="icon" href="/otter-logo.png" />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-DeQBOiPc.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-Y0Gx4EGC.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-MlvUnxKA.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-D_scUWf6.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-C8iJ0twv.css">
13
13
  </head>
14
14
  <body>
15
15
  <!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
@@ -382,6 +382,8 @@ var CANONICAL_MESH_TOOL_NAMES = [
382
382
  "mesh_send_task",
383
383
  "mesh_read_chat",
384
384
  "mesh_read_debug",
385
+ "mesh_read_terminal",
386
+ "mesh_send_keys",
385
387
  "mesh_launch_session",
386
388
  "mesh_git_status",
387
389
  "mesh_read_node_logs",
@@ -899,6 +901,44 @@ var MESH_READ_DEBUG_TOOL = {
899
901
  required: ["node_id", "session_id"]
900
902
  }
901
903
  };
904
+ var MESH_READ_TERMINAL_TOOL = {
905
+ name: "mesh_read_terminal",
906
+ description: "Read the CURRENT raw terminal screen (the rendered PTY viewport \u2014 what a human would see on screen right now) of a delegated agent session on a mesh node. This is the LIVE screen, not the parsed chat transcript: use it to see exactly what the worker is showing \u2014 a prompt it is parked on, a modal, a spinner, or unparsed output that mesh_read_chat does not surface. For the conversation transcript use mesh_read_chat instead. The reply is byte-bounded (default 32KiB, max 64KiB; the BOTTOM of the screen \u2014 prompt/modal/recent output \u2014 is kept when truncated) and returns truncated/original_bytes/returned_bytes plus the cursor position and viewport size. Scoped to coordinator-spawned mesh worker sessions only. NOTE: the raw screen can contain tokens / command args / env values, so treat the returned text as sensitive.",
907
+ inputSchema: {
908
+ type: "object",
909
+ properties: {
910
+ node_id: { type: "string", description: "Target node ID." },
911
+ session_id: { type: "string", description: "Agent session ID whose live terminal viewport to read." },
912
+ max_bytes: { type: "number", description: "Optional UTF-8 byte cap for the returned screen text (default 32768, clamped to [1024, 65536]). When the screen exceeds it, the bottom (most recent) lines are kept." }
913
+ },
914
+ required: ["node_id", "session_id"]
915
+ }
916
+ };
917
+ var MESH_SEND_KEYS_TOOL = {
918
+ name: "mesh_send_keys",
919
+ description: `Inject a STRUCTURED key sequence into a delegated worker session's live PTY (keystrokes a human would type). Use for interactions mesh_send_task cannot express: dismiss/answer a non-approval prompt, navigate a picker (arrows/TAB), submit an already-typed line (ENTER), correct input (BACKSPACE), or interrupt a runaway command (CTRL_C). For sending a task/message, use mesh_send_task; for an APPROVAL modal, use mesh_approve (send_keys is refused on an actionable approval modal by design). Each sequence item is either {"text":"literal"} or {"key":NAME} where NAME \u2208 ENTER|ESC|CTRL_C|UP|DOWN|LEFT|RIGHT|TAB|BACKSPACE. text+ENTER is submitted atomically. DESTRUCTIVE keys (CTRL_C, ESC) can kill/derail the worker and require BOTH confirm_destructive=true AND mesh policy allowSendKeysDestructive \u2014 otherwise refused. The injection is refused if the session has a pending submit/echo race, or (for non-destructive keys) an actionable approval modal. Scoped to coordinator-spawned mesh worker sessions. Each injection is audited (key enums + result; the literal text body is NOT recorded).`,
920
+ inputSchema: {
921
+ type: "object",
922
+ properties: {
923
+ node_id: { type: "string", description: "Target node ID." },
924
+ session_id: { type: "string", description: "Agent session ID whose PTY to inject into." },
925
+ sequence: {
926
+ type: "array",
927
+ description: 'Ordered key sequence. Each item is {"text":"literal UTF-8"} OR {"key":"ENTER|ESC|CTRL_C|UP|DOWN|LEFT|RIGHT|TAB|BACKSPACE"}. Max 64 items, 4096 total text bytes.',
928
+ items: {
929
+ type: "object",
930
+ properties: {
931
+ text: { type: "string", description: "Literal UTF-8 text to type." },
932
+ key: { type: "string", enum: ["ENTER", "ESC", "CTRL_C", "UP", "DOWN", "LEFT", "RIGHT", "TAB", "BACKSPACE"], description: "Named key." }
933
+ }
934
+ }
935
+ },
936
+ confirm_destructive: { type: "boolean", description: "Required true when the sequence contains a destructive key (CTRL_C/ESC). Also requires mesh policy allowSendKeysDestructive." },
937
+ allow_modal_override: { type: "boolean", description: "Override the actionable-approval-modal fail-closed refusal for NON-destructive keys. Use only when you deliberately need to inject into a modal-parked session that is NOT an approval you should route through mesh_approve." }
938
+ },
939
+ required: ["node_id", "session_id", "sequence"]
940
+ }
941
+ };
902
942
  var MESH_LAUNCH_SESSION_TOOL = {
903
943
  name: "mesh_launch_session",
904
944
  description: "Launch a new agent session on a mesh node. Returns the session ID for subsequent send_task/read_chat calls. If the user names a provider, preserve it exactly: Hermes = hermes-cli, Claude Code/Claude = claude-cli, Codex = codex-cli, Gemini = gemini-cli. If type is omitted, resolve strictly from the node policy providerPriority and provider detection; fail closed when no configured provider is usable. Do not default to claude-cli.",
@@ -1443,6 +1483,8 @@ var ALL_MESH_TOOLS = [
1443
1483
  MESH_SEND_TASK_TOOL,
1444
1484
  MESH_READ_CHAT_TOOL,
1445
1485
  MESH_READ_DEBUG_TOOL,
1486
+ MESH_READ_TERMINAL_TOOL,
1487
+ MESH_SEND_KEYS_TOOL,
1446
1488
  MESH_LAUNCH_SESSION_TOOL,
1447
1489
  MESH_GIT_STATUS_TOOL,
1448
1490
  MESH_READ_NODE_LOGS_TOOL,
@@ -6735,6 +6777,98 @@ async function meshReadDebug(ctx, args) {
6735
6777
  const payload = unwrapCommandPayload(result);
6736
6778
  return JSON.stringify(payload, null, 2);
6737
6779
  }
6780
+ async function meshReadTerminal(ctx, args) {
6781
+ const node = await findNodeWithRefresh(ctx, args.node_id);
6782
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
6783
+ const record = liveSessions.find((s) => readSessionRecordId(s) === args.session_id);
6784
+ if (record && !isMeshOwnedDelegateSession(record, ctx.mesh.id, args.node_id)) {
6785
+ return JSON.stringify({
6786
+ success: false,
6787
+ error: "session is not a mesh-owned delegate of this mesh/node \u2014 mesh_read_terminal is scoped to sessions this coordinator spawned",
6788
+ nodeId: args.node_id,
6789
+ sessionId: args.session_id
6790
+ }, null, 2);
6791
+ }
6792
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
6793
+ const result = await commandForNode(ctx, node, "read_terminal", {
6794
+ sessionId: args.session_id,
6795
+ targetSessionId: args.session_id,
6796
+ workspace: node.workspace,
6797
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
6798
+ ...typeof args.max_bytes === "number" && Number.isFinite(args.max_bytes) ? { maxBytes: args.max_bytes } : {}
6799
+ });
6800
+ const payload = unwrapCommandPayload(result);
6801
+ return JSON.stringify(payload, null, 2);
6802
+ }
6803
+ var MESH_SEND_KEYS_DESTRUCTIVE = /* @__PURE__ */ new Set(["CTRL_C", "ESC"]);
6804
+ async function meshSendKeys(ctx, args) {
6805
+ const node = await findNodeWithRefresh(ctx, args.node_id);
6806
+ const items = Array.isArray(args.sequence) ? args.sequence : [];
6807
+ if (items.length === 0) {
6808
+ return JSON.stringify({ success: false, error: "sequence (non-empty array of {text}|{key}) required" }, null, 2);
6809
+ }
6810
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
6811
+ const record = liveSessions.find((s) => readSessionRecordId(s) === args.session_id);
6812
+ if (record && !isMeshOwnedDelegateSession(record, ctx.mesh.id, args.node_id)) {
6813
+ return JSON.stringify({
6814
+ success: false,
6815
+ error: "session is not a mesh-owned delegate of this mesh/node \u2014 mesh_send_keys is scoped to sessions this coordinator spawned",
6816
+ nodeId: args.node_id,
6817
+ sessionId: args.session_id
6818
+ }, null, 2);
6819
+ }
6820
+ const requestedKeys = items.map((it) => it && typeof it.key === "string" ? it.key : "").filter(Boolean);
6821
+ const hasDestructive = requestedKeys.some((k) => MESH_SEND_KEYS_DESTRUCTIVE.has(k));
6822
+ const auditKeys = requestedKeys.slice(0, 64);
6823
+ const recordAudit = (result2, extra = {}) => {
6824
+ try {
6825
+ (0, import_daemon_core4.appendLedgerEntry)(ctx.mesh.id, {
6826
+ kind: "key_injection",
6827
+ nodeId: args.node_id,
6828
+ sessionId: args.session_id,
6829
+ payload: {
6830
+ keys: auditKeys,
6831
+ // key ENUMS only — never the literal text body
6832
+ hasDestructive,
6833
+ confirmDestructive: args.confirm_destructive === true,
6834
+ result: result2,
6835
+ ...extra
6836
+ }
6837
+ });
6838
+ } catch {
6839
+ }
6840
+ };
6841
+ if (hasDestructive) {
6842
+ const policyAllows = (0, import_daemon_core4.resolveAllowSendKeysDestructive)(ctx.mesh.policy, node.policy);
6843
+ if (args.confirm_destructive !== true || !policyAllows) {
6844
+ recordAudit("refused", { refused: "destructive_gate", policyAllows });
6845
+ return JSON.stringify({
6846
+ success: false,
6847
+ error: "destructive key (CTRL_C/ESC) requires BOTH confirm_destructive=true AND mesh policy allowSendKeysDestructive=true",
6848
+ refused: "destructive_gate",
6849
+ confirmDestructive: args.confirm_destructive === true,
6850
+ policyAllowsDestructive: policyAllows
6851
+ }, null, 2);
6852
+ }
6853
+ }
6854
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
6855
+ const result = await commandForNode(ctx, node, "send_keys", {
6856
+ sessionId: args.session_id,
6857
+ targetSessionId: args.session_id,
6858
+ workspace: node.workspace,
6859
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
6860
+ sequence: items,
6861
+ confirm_destructive: args.confirm_destructive === true,
6862
+ allow_modal_override: args.allow_modal_override === true
6863
+ });
6864
+ const payload = unwrapCommandPayload(result);
6865
+ if (payload?.success === true) {
6866
+ recordAudit("injected", { submits: payload.submits === true });
6867
+ } else {
6868
+ recordAudit("refused", { refused: readString(payload?.refused) || "error" });
6869
+ }
6870
+ return JSON.stringify(payload, null, 2);
6871
+ }
6738
6872
  async function meshLaunchSession(ctx, args) {
6739
6873
  const node = await findNodeWithRefresh(ctx, args.node_id);
6740
6874
  const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
@@ -8485,6 +8619,12 @@ async function startMcpServer(opts) {
8485
8619
  case "mesh_read_debug":
8486
8620
  text = await meshReadDebug(meshCtx, a);
8487
8621
  break;
8622
+ case "mesh_read_terminal":
8623
+ text = await meshReadTerminal(meshCtx, a);
8624
+ break;
8625
+ case "mesh_send_keys":
8626
+ text = await meshSendKeys(meshCtx, a);
8627
+ break;
8488
8628
  case "mesh_launch_session":
8489
8629
  text = await meshLaunchSession(meshCtx, a);
8490
8630
  break;