@adhdev/daemon-core 0.9.82-rc.552 → 0.9.82-rc.554
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/cli-adapter-types.d.ts +29 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +80 -1
- package/dist/cli-adapters/provider-cli-shared.d.ts +55 -0
- package/dist/cli-adapters/terminal-screen.d.ts +5 -0
- package/dist/commands/stream-commands.d.ts +31 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +789 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +785 -12
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-refine-gates.d.ts +86 -0
- package/dist/providers/cli-provider-instance.d.ts +97 -0
- package/dist/providers/provider-instance.d.ts +9 -0
- package/dist/providers/spec/adapter.d.ts +7 -0
- package/dist/providers/spec/cli-adapter.d.ts +64 -0
- package/dist/providers/spec/fsm-driver.d.ts +11 -0
- package/dist/repo-mesh-types.d.ts +23 -0
- package/package.json +3 -3
- package/src/cli-adapter-types.ts +29 -0
- package/src/cli-adapters/provider-cli-adapter.ts +135 -0
- package/src/cli-adapters/provider-cli-shared.ts +172 -0
- package/src/cli-adapters/terminal-screen.ts +5 -0
- package/src/commands/handler.ts +4 -0
- package/src/commands/router-refine.ts +40 -1
- package/src/commands/router.ts +15 -0
- package/src/commands/stream-commands.ts +125 -0
- package/src/index.ts +1 -0
- package/src/mesh/coordinator-prompt.ts +2 -0
- package/src/mesh/mesh-events-utils.ts +15 -0
- package/src/mesh/mesh-ledger.ts +6 -0
- package/src/mesh/mesh-refine-gates.ts +256 -0
- package/src/providers/cli-provider-instance.ts +277 -6
- package/src/providers/provider-instance-manager.ts +11 -0
- package/src/providers/provider-instance.ts +10 -0
- package/src/providers/spec/adapter.ts +7 -0
- package/src/providers/spec/cli-adapter.ts +122 -0
- package/src/providers/spec/fsm-driver.ts +5 -0
- package/src/repo-mesh-types.ts +35 -0
|
@@ -175,6 +175,13 @@ export class TerminalAdapter {
|
|
|
175
175
|
return { row: pos.row, col: pos.col };
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/** Current terminal geometry (columns × rows). Tracked here rather than
|
|
179
|
+
* read off the screen buffer so a resize is reflected immediately, before
|
|
180
|
+
* the next repaint. Consumed by the mesh_read_terminal viewport read. */
|
|
181
|
+
getScreenSize(): { cols: number; rows: number } {
|
|
182
|
+
return { cols: this.cols, rows: this.rows };
|
|
183
|
+
}
|
|
184
|
+
|
|
178
185
|
send_keys(text: string): void {
|
|
179
186
|
this.recordEvent('input', capPreview(escapeControl(text)), text.length);
|
|
180
187
|
this.pty?.write(text);
|
|
@@ -23,10 +23,17 @@ import { lastContiguousNumberedBlock } from './evaluator.js';
|
|
|
23
23
|
import { executeNativeHistory } from './native-history-executor.js';
|
|
24
24
|
import { detectBackgroundTaskActive } from './background-task-detector.js';
|
|
25
25
|
import * as fs from 'node:fs';
|
|
26
|
+
import { createHash } from 'node:crypto';
|
|
26
27
|
import type { NativeHistoryConfig, Control, ControlAction } from './types.js';
|
|
27
28
|
import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
|
|
28
29
|
import type { ChatMessage } from '../../types.js';
|
|
29
30
|
import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
31
|
+
import {
|
|
32
|
+
encodeMeshSendKeys,
|
|
33
|
+
truncateToByteTailByLine,
|
|
34
|
+
type MeshSendKeyItem,
|
|
35
|
+
type MeshSendKeyName,
|
|
36
|
+
} from '../../cli-adapters/provider-cli-shared.js';
|
|
30
37
|
import { LOG } from '../../logging/logger.js';
|
|
31
38
|
import {
|
|
32
39
|
buildClaudeInteractiveTuiAnswerSteps,
|
|
@@ -277,6 +284,121 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
277
284
|
return this.spawned && !this.exited;
|
|
278
285
|
}
|
|
279
286
|
|
|
287
|
+
// Process liveness for the MESH-STALL-WATCH watchdog (checkMeshWorkerStall).
|
|
288
|
+
// The spec path drives the child through the transport/driver rather than a
|
|
289
|
+
// directly-held ptyProcess handle, so liveness is tracked by the spawned/exited
|
|
290
|
+
// lifecycle flags — the same pair isReady() uses. A spawned, not-yet-exited
|
|
291
|
+
// session is alive. ProviderCliAdapter exposes the equivalent via `ptyProcess !== null`.
|
|
292
|
+
isAlive(): boolean {
|
|
293
|
+
return this.spawned && !this.exited;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// MESH-READ-TERMINAL / MESH-SEND-KEYS byte caps — same envelope as
|
|
297
|
+
// ProviderCliAdapter (32KiB default view, 64KiB absolute hard cap). Bytes,
|
|
298
|
+
// not chars: a multi-byte-glyph screen can exceed an MCP payload cap while
|
|
299
|
+
// the char count still looks safe.
|
|
300
|
+
private static readonly TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES = 32 * 1024;
|
|
301
|
+
private static readonly TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES = 64 * 1024;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* MESH-READ-TERMINAL (feature 2: RAW terminal read). Least-privilege read
|
|
305
|
+
* of the CURRENT rendered viewport for mesh_read_terminal on the spec path
|
|
306
|
+
* (claude-cli / antigravity / codex-cli — the native-source providers that
|
|
307
|
+
* route through SpecCliAdapter). Mirrors ProviderCliAdapter.getTerminalScreenSnapshot:
|
|
308
|
+
* - returns ONLY the driver's current viewport snapshot, the cursor
|
|
309
|
+
* position and the terminal geometry — NO scrollback, NO parser/FSM
|
|
310
|
+
* state, NO debug buffers;
|
|
311
|
+
* - the payload is byte-bounded (UTF-8) with bottom-tail preservation so a
|
|
312
|
+
* screen of multi-byte glyphs can never exceed the MCP payload cap;
|
|
313
|
+
* - `hash` is over the FULL untruncated viewport so a caller can detect a
|
|
314
|
+
* screen change across polls even when the returned text was truncated.
|
|
315
|
+
*
|
|
316
|
+
* SECURITY: the raw viewport can carry tokens / command args / env / user
|
|
317
|
+
* data. Callers MUST gate this on mesh ownership and MUST NOT log the text.
|
|
318
|
+
*/
|
|
319
|
+
getTerminalScreenSnapshot(maxBytes = SpecCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES): {
|
|
320
|
+
text: string;
|
|
321
|
+
cursor: { col: number; row: number };
|
|
322
|
+
cols: number;
|
|
323
|
+
rows: number;
|
|
324
|
+
truncated: boolean;
|
|
325
|
+
originalBytes: number;
|
|
326
|
+
returnedBytes: number;
|
|
327
|
+
hash: string;
|
|
328
|
+
} {
|
|
329
|
+
const cap = Math.min(
|
|
330
|
+
SpecCliAdapter.TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES,
|
|
331
|
+
Math.max(1024, Math.floor(maxBytes) || SpecCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES),
|
|
332
|
+
);
|
|
333
|
+
let rawViewport = '';
|
|
334
|
+
try { rawViewport = this.driver.snapshot() || ''; } catch { rawViewport = ''; }
|
|
335
|
+
let cursor = { row: 0, col: 0 };
|
|
336
|
+
try { cursor = this.driver.getCursorPosition(); } catch { /* keep 0,0 */ }
|
|
337
|
+
// getScreenSize is optional on ISpecDriver; a test double may omit it.
|
|
338
|
+
let size = { cols: 0, rows: 0 };
|
|
339
|
+
try { size = this.driver.getScreenSize?.() ?? size; } catch { /* keep 0,0 */ }
|
|
340
|
+
const truncation = truncateToByteTailByLine(rawViewport, cap);
|
|
341
|
+
const hash = createHash('sha256').update(rawViewport, 'utf8').digest('hex').slice(0, 16);
|
|
342
|
+
return {
|
|
343
|
+
text: truncation.text,
|
|
344
|
+
cursor: { col: cursor.col, row: cursor.row },
|
|
345
|
+
cols: size.cols,
|
|
346
|
+
rows: size.rows,
|
|
347
|
+
truncated: truncation.truncated,
|
|
348
|
+
originalBytes: truncation.originalBytes,
|
|
349
|
+
returnedBytes: truncation.returnedBytes,
|
|
350
|
+
hash,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key
|
|
356
|
+
* sequence into the spec-driven PTY for mesh_send_keys. Mirrors
|
|
357
|
+
* ProviderCliAdapter.injectKeys' modal fail-closed guard, then writes the
|
|
358
|
+
* whole encoded sequence in ONE pty_write dispatch (text+ENTER is a single
|
|
359
|
+
* contiguous string, so a submit key can never be separated from the text
|
|
360
|
+
* it submits).
|
|
361
|
+
*
|
|
362
|
+
* The spec path drives the child through the FsmDriver, not a directly-held
|
|
363
|
+
* ptyProcess — there is no adapter-level echo-gate/submit-retry FIFO to race
|
|
364
|
+
* against here (the driver serializes its own writes), so the only guard is
|
|
365
|
+
* the modal fail-closed: a NON-destructive injection into an actionable
|
|
366
|
+
* approval modal is refused (use mesh_approve) unless explicitly overridden.
|
|
367
|
+
* A destructive ESC/CTRL_C dismisses rather than confirms, so it is allowed
|
|
368
|
+
* past this gate (the tool layer owns the destructive double-gate + audit).
|
|
369
|
+
* This method NEVER logs the literal text — only key enums / byte length.
|
|
370
|
+
*/
|
|
371
|
+
async injectKeys(
|
|
372
|
+
items: MeshSendKeyItem[],
|
|
373
|
+
opts: { allowModalOverride?: boolean } = {},
|
|
374
|
+
): Promise<
|
|
375
|
+
| { ok: true; keys: MeshSendKeyName[]; hasDestructive: boolean; submits: boolean; bytes: number }
|
|
376
|
+
| { ok: false; refused: 'submit_race' | 'actionable_modal'; keys: MeshSendKeyName[]; hasDestructive: boolean }
|
|
377
|
+
> {
|
|
378
|
+
if (!this.spawned || this.exited) throw new Error(`${this.cliName} is not running`);
|
|
379
|
+
const encoded = encodeMeshSendKeys(items);
|
|
380
|
+
|
|
381
|
+
// Modal fail-closed — a NON-destructive injection while parked on an
|
|
382
|
+
// actionable approval modal is refused so a modal choice can't be
|
|
383
|
+
// confirmed via send_keys and bypass the approval policy.
|
|
384
|
+
const modalActive = this.latestState?.status === 'approval';
|
|
385
|
+
if (modalActive && !encoded.hasDestructive && !opts.allowModalOverride) {
|
|
386
|
+
LOG.warn('SpecAdapter', `[${this.cliType}] send_keys refused (actionable_modal): keys=${encoded.keys.join(',')} — use mesh_approve`);
|
|
387
|
+
return { ok: false, refused: 'actionable_modal', keys: encoded.keys, hasDestructive: encoded.hasDestructive };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Atomic write: the full encoded sequence goes out in ONE pty_write.
|
|
391
|
+
this.driver.dispatch({ kind: 'pty_write', data: encoded.sequence });
|
|
392
|
+
LOG.info('SpecAdapter', `[${this.cliType}] send_keys injected keys=${encoded.keys.join(',') || '(text-only)'} bytes=${Buffer.byteLength(encoded.sequence, 'utf8')} destructive=${encoded.hasDestructive}`);
|
|
393
|
+
return {
|
|
394
|
+
ok: true,
|
|
395
|
+
keys: encoded.keys,
|
|
396
|
+
hasDestructive: encoded.hasDestructive,
|
|
397
|
+
submits: encoded.submits,
|
|
398
|
+
bytes: Buffer.byteLength(encoded.sequence, 'utf8'),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
280
402
|
setOnStatusChange(cb: () => void): void {
|
|
281
403
|
this.statusCallback = cb;
|
|
282
404
|
}
|
|
@@ -122,6 +122,10 @@ export interface ISpecDriver {
|
|
|
122
122
|
snapshot(): string;
|
|
123
123
|
getCursorPosition(): { row: number; col: number };
|
|
124
124
|
getScreen(): string;
|
|
125
|
+
/** Current terminal geometry (columns × rows). Optional so a non-Fsm
|
|
126
|
+
* ISpecDriver implementation (test doubles) need not provide it; the
|
|
127
|
+
* mesh_read_terminal path falls back to a 0×0 geometry when absent. */
|
|
128
|
+
getScreenSize?(): { cols: number; rows: number };
|
|
125
129
|
getSpecPath(): string;
|
|
126
130
|
shutdown(): void;
|
|
127
131
|
getStateHistory(): ReadonlyArray<DriverHistoryEntry>;
|
|
@@ -443,6 +447,7 @@ export class FsmDriver implements ISpecDriver {
|
|
|
443
447
|
snapshot(): string { return this.adapter.snapshot(); }
|
|
444
448
|
getCursorPosition(): { row: number; col: number } { return this.adapter.getCursorPosition(); }
|
|
445
449
|
getScreen(): string { return this.adapter.snapshot(); }
|
|
450
|
+
getScreenSize(): { cols: number; rows: number } { return this.adapter.getScreenSize(); }
|
|
446
451
|
|
|
447
452
|
/** Scrollback-inclusive screen as line array — used only for modal/button
|
|
448
453
|
* content extraction so a tall prompt's off-screen anchors stay matchable.
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -350,6 +350,17 @@ export interface RepoMeshPolicy {
|
|
|
350
350
|
* A node policy may override this per-node (RepoMeshNodePolicy.delegatedWorkerAutoApprove).
|
|
351
351
|
*/
|
|
352
352
|
delegatedWorkerAutoApprove?: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* MESH-SEND-KEYS (feature 3): opt-in to allow the coordinator to inject
|
|
355
|
+
* DESTRUCTIVE keys (CTRL_C / ESC) into a worker PTY via mesh_send_keys. These
|
|
356
|
+
* can kill or derail the worker process, and delegatedWorkerAutoApprove is a
|
|
357
|
+
* TOOL-CONSENT policy, not a PTY-input authorization — so a destructive key
|
|
358
|
+
* injection additionally requires this explicit mesh-owner opt-in AND a
|
|
359
|
+
* per-call confirm_destructive=true. Defaults to false (destructive keys
|
|
360
|
+
* refused). Non-destructive keys (text/ENTER/arrows/TAB/BACKSPACE) are
|
|
361
|
+
* unaffected. A node policy may override per-node.
|
|
362
|
+
*/
|
|
363
|
+
allowSendKeysDestructive?: boolean;
|
|
353
364
|
/**
|
|
354
365
|
* What to do with delegated session-host records for a node when it is removed.
|
|
355
366
|
* Defaults to 'preserve' so completed work can be reviewed later and live
|
|
@@ -473,6 +484,11 @@ export interface RepoMeshNodePolicy {
|
|
|
473
484
|
* precedence over the mesh-level policy for worker sessions launched onto this node.
|
|
474
485
|
*/
|
|
475
486
|
delegatedWorkerAutoApprove?: boolean;
|
|
487
|
+
/**
|
|
488
|
+
* MESH-SEND-KEYS (feature 3): per-node override for
|
|
489
|
+
* RepoMeshPolicy.allowSendKeysDestructive.
|
|
490
|
+
*/
|
|
491
|
+
allowSendKeysDestructive?: boolean;
|
|
476
492
|
/**
|
|
477
493
|
* Optional associated/external repos that must be checked alongside this node.
|
|
478
494
|
* These are explicit policy/config entries only; Repo Mesh does not auto-discover
|
|
@@ -734,6 +750,25 @@ export function resolveDelegatedWorkerAutoApprove(
|
|
|
734
750
|
return true;
|
|
735
751
|
}
|
|
736
752
|
|
|
753
|
+
/**
|
|
754
|
+
* MESH-SEND-KEYS (feature 3): resolve whether DESTRUCTIVE key injection
|
|
755
|
+
* (CTRL_C/ESC via mesh_send_keys) is permitted for a node. Node policy overrides
|
|
756
|
+
* mesh policy; DEFAULTS TO FALSE (fail-closed) — a destructive key still requires
|
|
757
|
+
* a per-call confirm_destructive=true on top of this opt-in.
|
|
758
|
+
*/
|
|
759
|
+
export function resolveAllowSendKeysDestructive(
|
|
760
|
+
meshPolicy?: Pick<RepoMeshPolicy, 'allowSendKeysDestructive'> | null,
|
|
761
|
+
nodePolicy?: Pick<RepoMeshNodePolicy, 'allowSendKeysDestructive'> | null,
|
|
762
|
+
): boolean {
|
|
763
|
+
if (typeof nodePolicy?.allowSendKeysDestructive === 'boolean') {
|
|
764
|
+
return nodePolicy.allowSendKeysDestructive;
|
|
765
|
+
}
|
|
766
|
+
if (typeof meshPolicy?.allowSendKeysDestructive === 'boolean') {
|
|
767
|
+
return meshPolicy.allowSendKeysDestructive;
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
|
|
737
772
|
/**
|
|
738
773
|
* Resolve the enforced per-(node, provider) maxParallel cap from a node's resolved
|
|
739
774
|
* capability slots, or undefined when no matching slot declares a finite cap. Used
|