@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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { EventEmitter } from 'events';
|
|
16
16
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
17
17
|
import { type MeshLedgerOriginatingCoordinatorV2 } from './contracts.js';
|
|
18
|
-
export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis';
|
|
18
|
+
export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis' | 'key_injection';
|
|
19
19
|
export interface MeshLedgerEntry {
|
|
20
20
|
id: string;
|
|
21
21
|
meshId: string;
|
|
@@ -327,6 +327,92 @@ export declare function resolveRefineryAutoPublishSubmoduleMainCommits(mesh: any
|
|
|
327
327
|
source?: string;
|
|
328
328
|
};
|
|
329
329
|
export declare function runMeshRefinePatchEquivalenceGate(repoRoot: string, baseHead: string, branchHead: string): Promise<MeshRefinePatchEquivalenceSummary>;
|
|
330
|
+
/**
|
|
331
|
+
* Machine-readable sub-classification of a `patch_equivalence_failed` (and the
|
|
332
|
+
* related submodule-gitlink preflight blocks). The opaque top-level
|
|
333
|
+
* `patch_equivalence_failed` code is preserved for backward compatibility; this
|
|
334
|
+
* detailed reason is added ALONGSIDE it so coordinators no longer have to guess
|
|
335
|
+
* WHY the preflight blocked (the 2026-07-17 hidden-spinner convergence incident:
|
|
336
|
+
* the real cause was a diverged base + an unreachable submodule gitlink artifact,
|
|
337
|
+
* not a real patch conflict, but Refinery only returned the opaque code and the
|
|
338
|
+
* coordinator mis-attributed it to a stale daemon version).
|
|
339
|
+
*/
|
|
340
|
+
export type MeshRefinePatchEquivalenceDetailedReasonCode =
|
|
341
|
+
/** Worktree base diverged from target base (HEAD is not a descendant of origin/main). */
|
|
342
|
+
'base_divergence'
|
|
343
|
+
/** Submodule gitlink commit is not reachable from the submodule's remote main branch (publish needed). */
|
|
344
|
+
| 'submodule_unreachable'
|
|
345
|
+
/** Genuine non-equivalent content: expected tree vs actual merge diff differ. */
|
|
346
|
+
| 'actual_patch_diff'
|
|
347
|
+
/** Submodule gitlink trivial fast-forward mis-judged as non-equivalent (HEAD descends origin/main, patch-id equal, blocked only by the gitlink). */
|
|
348
|
+
| 'trivial_ff_misjudgment'
|
|
349
|
+
/** Already identical to origin/main (ahead 0 / behind 0, no diff) — should be treated as success/no-op. */
|
|
350
|
+
| 'already_converged'
|
|
351
|
+
/** Fallback when the classifier itself could not run (git error); keep the opaque code, note the reason. */
|
|
352
|
+
| 'unclassified';
|
|
353
|
+
export type MeshRefinePatchEquivalenceFailureClassification = {
|
|
354
|
+
detailedReason: MeshRefinePatchEquivalenceDetailedReasonCode;
|
|
355
|
+
/** Human-readable one-line description of the sub-cause. */
|
|
356
|
+
detailedReasonDescription: string;
|
|
357
|
+
/** Suggested next action for the coordinator/owner (free-form, actionable). */
|
|
358
|
+
recommendedAction: string;
|
|
359
|
+
/** Structured supporting evidence: SHAs, ahead/behind, submodule reachability, patch-id comparison, diff stat. */
|
|
360
|
+
evidence: {
|
|
361
|
+
baseHead?: string;
|
|
362
|
+
branchHead?: string;
|
|
363
|
+
mergeBase?: string;
|
|
364
|
+
/** How many commits base (origin/main) is ahead of the branch's merge-base (branch is behind). */
|
|
365
|
+
behind?: number;
|
|
366
|
+
/** How many commits the branch is ahead of the merge-base. */
|
|
367
|
+
ahead?: number;
|
|
368
|
+
/** True when HEAD is NOT a descendant of the target base (diverged). */
|
|
369
|
+
baseDiverged?: boolean;
|
|
370
|
+
expectedPatchId?: string;
|
|
371
|
+
actualPatchId?: string;
|
|
372
|
+
patchIdEqual?: boolean;
|
|
373
|
+
/** Compact one-line diff stat summary of the residual/actual merge diff (best-effort). */
|
|
374
|
+
diffStat?: string;
|
|
375
|
+
/** Per-submodule gitlink reachability against submodule origin/main (best-effort). */
|
|
376
|
+
submoduleGitlinks?: Array<{
|
|
377
|
+
path: string;
|
|
378
|
+
baseCommit?: string;
|
|
379
|
+
branchCommit?: string;
|
|
380
|
+
/** True when branchCommit descends baseCommit (a strict fast-forward advance). */
|
|
381
|
+
fastForward?: boolean;
|
|
382
|
+
/** True when branchCommit is reachable from the submodule's local origin/main. */
|
|
383
|
+
reachableFromOriginMain?: boolean;
|
|
384
|
+
}>;
|
|
385
|
+
/** Effective auto-publish-submodule-main-commits policy value at classification time. */
|
|
386
|
+
autoPublishSubmoduleMainCommits?: boolean;
|
|
387
|
+
/** Set when the classifier itself errored (detailedReason === 'unclassified'). */
|
|
388
|
+
classifierError?: string;
|
|
389
|
+
};
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* Classify WHY a patch-equivalence preflight blocked, turning the opaque
|
|
393
|
+
* `patch_equivalence_failed` code into a machine-readable {@link
|
|
394
|
+
* MeshRefinePatchEquivalenceDetailedReasonCode} plus a recommended action and
|
|
395
|
+
* structured evidence. Read-only: runs only `git` inspection commands (rev-list,
|
|
396
|
+
* merge-base, diff --stat, submodule reachability probes) against the already-set
|
|
397
|
+
* worktree — it never mutates the repo.
|
|
398
|
+
*
|
|
399
|
+
* Priority of classification (first match wins):
|
|
400
|
+
* 1. already_converged — ahead 0 & behind 0 & no residual diff
|
|
401
|
+
* 2. submodule_unreachable — a changed gitlink commit is not reachable from the
|
|
402
|
+
* submodule's origin/main (publish needed)
|
|
403
|
+
* 3. trivial_ff_misjudgment — HEAD descends origin/main AND (excl. gitlinks) the
|
|
404
|
+
* patch-ids match — blocked only by a ff gitlink
|
|
405
|
+
* 4. base_divergence — HEAD is not a descendant of the target base
|
|
406
|
+
* 5. actual_patch_diff — genuine content divergence (the residual case)
|
|
407
|
+
*
|
|
408
|
+
* `targetBaseRef` is the ref the branch is meant to land on (e.g. 'origin/main'
|
|
409
|
+
* or the pinned baseHead SHA). `autoPublishSubmoduleMainCommits` is threaded in so
|
|
410
|
+
* the submodule_unreachable recommendation can name the current policy value.
|
|
411
|
+
*/
|
|
412
|
+
export declare function classifyPatchEquivalenceFailure(repoRoot: string, baseHead: string, branchHead: string, summary: MeshRefinePatchEquivalenceSummary, options?: {
|
|
413
|
+
targetBaseRef?: string;
|
|
414
|
+
autoPublishSubmoduleMainCommits?: boolean;
|
|
415
|
+
}): Promise<MeshRefinePatchEquivalenceFailureClassification>;
|
|
330
416
|
export type MeshWorktreePatchContainmentSummary = {
|
|
331
417
|
/** True only when merging worktreeHead into ref introduces no new patch. */
|
|
332
418
|
contained: boolean;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { type ProviderModule, type InputEnvelope } from './contracts.js';
|
|
8
8
|
import type { ProviderInstance, ProviderState, InstanceContext, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
9
9
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
|
+
import type { MeshSendKeyItem, MeshSendKeyName } from '../cli-adapters/provider-cli-shared.js';
|
|
10
11
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
11
12
|
import type { ChatMessage } from '../types.js';
|
|
12
13
|
export { buildCliStructuredInputPrompt } from './cli-provider-input-prompt.js';
|
|
@@ -169,12 +170,15 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
169
170
|
* with no approval never carries recency and is never held (no regression).
|
|
170
171
|
*/
|
|
171
172
|
private static readonly APPROVAL_RESUME_GRACE_MS;
|
|
173
|
+
private static readonly MESH_WORKER_STALL_THRESHOLD_MS;
|
|
172
174
|
private adapter;
|
|
173
175
|
private context;
|
|
174
176
|
private events;
|
|
175
177
|
private lastStatus;
|
|
176
178
|
private agentReadyEmitted;
|
|
177
179
|
private generatingStartedAt;
|
|
180
|
+
private meshStallAnchorAt;
|
|
181
|
+
private meshStallEmittedForAnchor;
|
|
178
182
|
private busyEpoch;
|
|
179
183
|
private fastCollapseSynthesizedTaskId;
|
|
180
184
|
private startupGraceCollapseAt;
|
|
@@ -419,6 +423,21 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
419
423
|
* the dashboard tail-repair cache — a display value, not a completion decision.
|
|
420
424
|
*/
|
|
421
425
|
private lastVisibleAssistantSummary;
|
|
426
|
+
/**
|
|
427
|
+
* NOTIF Defect-B: the final assistant summary this instance ALREADY parsed and
|
|
428
|
+
* cached for the current turn (lastCompletionSummary), if any. The evidence
|
|
429
|
+
* probe (completionFinalAssistantEvidence) is a POINT-SAMPLE: on a native-source
|
|
430
|
+
* provider (antigravity) the parsed screen and the native transcript can both
|
|
431
|
+
* momentarily yield no in-turn final assistant at the exact instant the
|
|
432
|
+
* completion gate fires — source='unavailable', missingEvidence=true — even
|
|
433
|
+
* though a prior poll already read the real answer off native-history and cached
|
|
434
|
+
* it here (the same value mesh_read_chat.summary shows). Consulting the cache at
|
|
435
|
+
* emit time lets that already-secured summary count as evidence, so the completion
|
|
436
|
+
* notification carries the answer instead of completion_diagnostic=missing_final_assistant
|
|
437
|
+
* with an empty summary. Returns '' when the cache is empty or was reset by the
|
|
438
|
+
* next turn (see lastCompletionSummary = null on onTurnStarted).
|
|
439
|
+
*/
|
|
440
|
+
private cachedCompletionSummaryContent;
|
|
422
441
|
private completionFinalAssistantEvidence;
|
|
423
442
|
private completionFinalSummary;
|
|
424
443
|
private buildCompletedFinalizationDiagnostic;
|
|
@@ -429,6 +448,84 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
429
448
|
private approvalResolutionFinalizationBlock;
|
|
430
449
|
private scheduleCompletedDebounceFlush;
|
|
431
450
|
private isMeshWorkerSession;
|
|
451
|
+
/**
|
|
452
|
+
* MESH-READ-TERMINAL (feature 2: RAW terminal read). Public read of the
|
|
453
|
+
* CURRENT rendered PTY viewport for the mesh_read_terminal tool, delegating to
|
|
454
|
+
* the adapter's narrow getTerminalScreenSnapshot() (viewport + cursor + size
|
|
455
|
+
* only; no debug buffers / parser state / history; byte-bounded, bottom-tail
|
|
456
|
+
* preserved).
|
|
457
|
+
*
|
|
458
|
+
* Gated on isMeshWorkerSession(): this raw viewport can expose tokens /
|
|
459
|
+
* command args / env / user data, so only a coordinator-spawned worker session
|
|
460
|
+
* is readable. The MCP layer ALSO cross-checks mesh/session/node ownership
|
|
461
|
+
* (isMeshOwnedDelegateSession) — isMeshWorkerSession alone is a broad
|
|
462
|
+
* "delegated" gate, so the two together block cross-mesh access. Returns null
|
|
463
|
+
* for a non-mesh session so the daemon command surfaces a clean refusal.
|
|
464
|
+
*/
|
|
465
|
+
getTerminalScreenSnapshot(maxBytes?: number): {
|
|
466
|
+
text: string;
|
|
467
|
+
cursor: {
|
|
468
|
+
col: number;
|
|
469
|
+
row: number;
|
|
470
|
+
};
|
|
471
|
+
cols: number;
|
|
472
|
+
rows: number;
|
|
473
|
+
truncated: boolean;
|
|
474
|
+
originalBytes: number;
|
|
475
|
+
returnedBytes: number;
|
|
476
|
+
hash: string;
|
|
477
|
+
} | null;
|
|
478
|
+
/**
|
|
479
|
+
* MESH-SEND-KEYS (feature 3: key injection). Public entry for the
|
|
480
|
+
* mesh_send_keys tool, delegating to the adapter's injectKeys() (structured
|
|
481
|
+
* key encoding + atomic write + submit-race recheck + modal fail-closed).
|
|
482
|
+
*
|
|
483
|
+
* Gated on isMeshWorkerSession(): PTY input into a worker is a
|
|
484
|
+
* coordinator-only capability. The MCP layer ALSO cross-checks mesh/session/
|
|
485
|
+
* node ownership (isMeshOwnedDelegateSession) and owns the destructive-key
|
|
486
|
+
* double gate (confirm_destructive + policy) and the audit ledger. Returns a
|
|
487
|
+
* refusal object for a non-mesh session so the daemon command surfaces a clean
|
|
488
|
+
* error (never silently writes to a non-worker PTY).
|
|
489
|
+
*/
|
|
490
|
+
injectKeys(items: MeshSendKeyItem[], opts?: {
|
|
491
|
+
allowModalOverride?: boolean;
|
|
492
|
+
}): Promise<{
|
|
493
|
+
ok: true;
|
|
494
|
+
keys: MeshSendKeyName[];
|
|
495
|
+
hasDestructive: boolean;
|
|
496
|
+
submits: boolean;
|
|
497
|
+
bytes: number;
|
|
498
|
+
} | {
|
|
499
|
+
ok: false;
|
|
500
|
+
refused: 'submit_race' | 'actionable_modal' | 'not_mesh_worker' | 'unsupported';
|
|
501
|
+
keys: MeshSendKeyName[];
|
|
502
|
+
hasDestructive: boolean;
|
|
503
|
+
}>;
|
|
504
|
+
/**
|
|
505
|
+
* MESH-STALL-WATCH (feature 1: STALL detection). Status-agnostic stall
|
|
506
|
+
* watchdog for coordinator-spawned mesh worker sessions. Driven by the
|
|
507
|
+
* ProviderInstanceManager's existing 5s onTick loop (NO new timer) — see
|
|
508
|
+
* ProviderInstanceManager.startTicking. Reuses the adapter's raw-PTY-output
|
|
509
|
+
* clock (lastOutputAt, bumped on every output chunk) as the sole signal: if a
|
|
510
|
+
* live worker's screen has been byte-for-byte unchanged for
|
|
511
|
+
* MESH_WORKER_STALL_THRESHOLD_MS (180s), fire ONE informational
|
|
512
|
+
* monitor:no_progress event down the existing task_stalled ledger +
|
|
513
|
+
* pendingCoordinatorEvent path.
|
|
514
|
+
*
|
|
515
|
+
* Deliberately status-agnostic: it does NOT read getStatus()'s reported status
|
|
516
|
+
* (which would couple it to the generating-only StatusMonitor and the
|
|
517
|
+
* idle-timeout FSM). A normally-idle worker CAN trip this after 3 quiet
|
|
518
|
+
* minutes; that is accepted and surfaced as an informational stall (NOT a
|
|
519
|
+
* failure/auto-restart) so the coordinator judges. getStatus/getState are NOT
|
|
520
|
+
* called here, so status heartbeats never move the stall anchor.
|
|
521
|
+
*
|
|
522
|
+
* Anchoring: the episode arms against the current lastOutputAt; a worker that
|
|
523
|
+
* has emitted nothing yet (lastOutputAt === 0) anchors on this.startedAt (spawn
|
|
524
|
+
* time) so a silent spawn is still caught. Any new output re-arms the anchor
|
|
525
|
+
* and clears the emitted flag, so one continuous stall emits at most once and a
|
|
526
|
+
* later stall re-arms cleanly.
|
|
527
|
+
*/
|
|
528
|
+
checkMeshWorkerStall(now?: number): void;
|
|
432
529
|
/**
|
|
433
530
|
* AUTOAPPROVE-FLAP-RECUR (Fix A+B): how long a busy blip / modal scroll-out may
|
|
434
531
|
* persist before the in-progress settle gate is torn down. For a delegated
|
|
@@ -164,6 +164,15 @@ export interface ProviderInstance {
|
|
|
164
164
|
init(context: InstanceContext): Promise<void>;
|
|
165
165
|
/** Tick — periodic status refresh (IDE: readChat, Extension: stream collection) */
|
|
166
166
|
onTick(): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* MESH-STALL-WATCH (feature 1: STALL detection). Status-agnostic stall
|
|
169
|
+
* watchdog for coordinator-spawned mesh worker sessions, invoked from the
|
|
170
|
+
* ProviderInstanceManager's existing tick loop (no separate timer). Fires ONE
|
|
171
|
+
* informational monitor:no_progress event when a live worker's raw PTY output
|
|
172
|
+
* has been unchanged past the stall threshold. Optional — only CLI instances
|
|
173
|
+
* (which own a PTY / lastOutputAt clock) implement it; a no-op elsewhere.
|
|
174
|
+
*/
|
|
175
|
+
checkMeshWorkerStall?(now?: number): void;
|
|
167
176
|
/** Return current status */
|
|
168
177
|
getState(): ProviderState;
|
|
169
178
|
/**
|
|
@@ -79,6 +79,13 @@ export declare class TerminalAdapter {
|
|
|
79
79
|
row: number;
|
|
80
80
|
col: number;
|
|
81
81
|
};
|
|
82
|
+
/** Current terminal geometry (columns × rows). Tracked here rather than
|
|
83
|
+
* read off the screen buffer so a resize is reflected immediately, before
|
|
84
|
+
* the next repaint. Consumed by the mesh_read_terminal viewport read. */
|
|
85
|
+
getScreenSize(): {
|
|
86
|
+
cols: number;
|
|
87
|
+
rows: number;
|
|
88
|
+
};
|
|
82
89
|
send_keys(text: string): void;
|
|
83
90
|
/** Forward runtime metadata (meshNodeId, workspaceLabel, lifecycle, …) to
|
|
84
91
|
* the underlying transport so it reaches the session registry. The spec
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
|
|
2
2
|
import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
3
|
+
import { type MeshSendKeyItem, type MeshSendKeyName } from '../../cli-adapters/provider-cli-shared.js';
|
|
3
4
|
import { type InteractivePromptResponse } from '../types/interactive-prompt.js';
|
|
4
5
|
export declare class SpecCliAdapter implements CliAdapter {
|
|
5
6
|
readonly cliType: string;
|
|
@@ -74,6 +75,69 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
74
75
|
cancel(): void;
|
|
75
76
|
isProcessing(): boolean;
|
|
76
77
|
isReady(): boolean;
|
|
78
|
+
isAlive(): boolean;
|
|
79
|
+
private static readonly TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES;
|
|
80
|
+
private static readonly TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES;
|
|
81
|
+
/**
|
|
82
|
+
* MESH-READ-TERMINAL (feature 2: RAW terminal read). Least-privilege read
|
|
83
|
+
* of the CURRENT rendered viewport for mesh_read_terminal on the spec path
|
|
84
|
+
* (claude-cli / antigravity / codex-cli — the native-source providers that
|
|
85
|
+
* route through SpecCliAdapter). Mirrors ProviderCliAdapter.getTerminalScreenSnapshot:
|
|
86
|
+
* - returns ONLY the driver's current viewport snapshot, the cursor
|
|
87
|
+
* position and the terminal geometry — NO scrollback, NO parser/FSM
|
|
88
|
+
* state, NO debug buffers;
|
|
89
|
+
* - the payload is byte-bounded (UTF-8) with bottom-tail preservation so a
|
|
90
|
+
* screen of multi-byte glyphs can never exceed the MCP payload cap;
|
|
91
|
+
* - `hash` is over the FULL untruncated viewport so a caller can detect a
|
|
92
|
+
* screen change across polls even when the returned text was truncated.
|
|
93
|
+
*
|
|
94
|
+
* SECURITY: the raw viewport can carry tokens / command args / env / user
|
|
95
|
+
* data. Callers MUST gate this on mesh ownership and MUST NOT log the text.
|
|
96
|
+
*/
|
|
97
|
+
getTerminalScreenSnapshot(maxBytes?: number): {
|
|
98
|
+
text: string;
|
|
99
|
+
cursor: {
|
|
100
|
+
col: number;
|
|
101
|
+
row: number;
|
|
102
|
+
};
|
|
103
|
+
cols: number;
|
|
104
|
+
rows: number;
|
|
105
|
+
truncated: boolean;
|
|
106
|
+
originalBytes: number;
|
|
107
|
+
returnedBytes: number;
|
|
108
|
+
hash: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key
|
|
112
|
+
* sequence into the spec-driven PTY for mesh_send_keys. Mirrors
|
|
113
|
+
* ProviderCliAdapter.injectKeys' modal fail-closed guard, then writes the
|
|
114
|
+
* whole encoded sequence in ONE pty_write dispatch (text+ENTER is a single
|
|
115
|
+
* contiguous string, so a submit key can never be separated from the text
|
|
116
|
+
* it submits).
|
|
117
|
+
*
|
|
118
|
+
* The spec path drives the child through the FsmDriver, not a directly-held
|
|
119
|
+
* ptyProcess — there is no adapter-level echo-gate/submit-retry FIFO to race
|
|
120
|
+
* against here (the driver serializes its own writes), so the only guard is
|
|
121
|
+
* the modal fail-closed: a NON-destructive injection into an actionable
|
|
122
|
+
* approval modal is refused (use mesh_approve) unless explicitly overridden.
|
|
123
|
+
* A destructive ESC/CTRL_C dismisses rather than confirms, so it is allowed
|
|
124
|
+
* past this gate (the tool layer owns the destructive double-gate + audit).
|
|
125
|
+
* This method NEVER logs the literal text — only key enums / byte length.
|
|
126
|
+
*/
|
|
127
|
+
injectKeys(items: MeshSendKeyItem[], opts?: {
|
|
128
|
+
allowModalOverride?: boolean;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
ok: true;
|
|
131
|
+
keys: MeshSendKeyName[];
|
|
132
|
+
hasDestructive: boolean;
|
|
133
|
+
submits: boolean;
|
|
134
|
+
bytes: number;
|
|
135
|
+
} | {
|
|
136
|
+
ok: false;
|
|
137
|
+
refused: 'submit_race' | 'actionable_modal';
|
|
138
|
+
keys: MeshSendKeyName[];
|
|
139
|
+
hasDestructive: boolean;
|
|
140
|
+
}>;
|
|
77
141
|
setOnStatusChange(cb: () => void): void;
|
|
78
142
|
setOnPtyData(cb: (data: string) => void): void;
|
|
79
143
|
writeRaw(data: string): void;
|
|
@@ -126,6 +126,13 @@ export interface ISpecDriver {
|
|
|
126
126
|
col: number;
|
|
127
127
|
};
|
|
128
128
|
getScreen(): string;
|
|
129
|
+
/** Current terminal geometry (columns × rows). Optional so a non-Fsm
|
|
130
|
+
* ISpecDriver implementation (test doubles) need not provide it; the
|
|
131
|
+
* mesh_read_terminal path falls back to a 0×0 geometry when absent. */
|
|
132
|
+
getScreenSize?(): {
|
|
133
|
+
cols: number;
|
|
134
|
+
rows: number;
|
|
135
|
+
};
|
|
129
136
|
getSpecPath(): string;
|
|
130
137
|
shutdown(): void;
|
|
131
138
|
getStateHistory(): ReadonlyArray<DriverHistoryEntry>;
|
|
@@ -251,6 +258,10 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
251
258
|
col: number;
|
|
252
259
|
};
|
|
253
260
|
getScreen(): string;
|
|
261
|
+
getScreenSize(): {
|
|
262
|
+
cols: number;
|
|
263
|
+
rows: number;
|
|
264
|
+
};
|
|
254
265
|
/** Scrollback-inclusive screen as line array — used only for modal/button
|
|
255
266
|
* content extraction so a tall prompt's off-screen anchors stay matchable.
|
|
256
267
|
* Falls back to the viewport snapshot if scrollback read is unavailable. */
|
|
@@ -255,6 +255,17 @@ export interface RepoMeshPolicy {
|
|
|
255
255
|
* A node policy may override this per-node (RepoMeshNodePolicy.delegatedWorkerAutoApprove).
|
|
256
256
|
*/
|
|
257
257
|
delegatedWorkerAutoApprove?: boolean;
|
|
258
|
+
/**
|
|
259
|
+
* MESH-SEND-KEYS (feature 3): opt-in to allow the coordinator to inject
|
|
260
|
+
* DESTRUCTIVE keys (CTRL_C / ESC) into a worker PTY via mesh_send_keys. These
|
|
261
|
+
* can kill or derail the worker process, and delegatedWorkerAutoApprove is a
|
|
262
|
+
* TOOL-CONSENT policy, not a PTY-input authorization — so a destructive key
|
|
263
|
+
* injection additionally requires this explicit mesh-owner opt-in AND a
|
|
264
|
+
* per-call confirm_destructive=true. Defaults to false (destructive keys
|
|
265
|
+
* refused). Non-destructive keys (text/ENTER/arrows/TAB/BACKSPACE) are
|
|
266
|
+
* unaffected. A node policy may override per-node.
|
|
267
|
+
*/
|
|
268
|
+
allowSendKeysDestructive?: boolean;
|
|
258
269
|
/**
|
|
259
270
|
* What to do with delegated session-host records for a node when it is removed.
|
|
260
271
|
* Defaults to 'preserve' so completed work can be reviewed later and live
|
|
@@ -374,6 +385,11 @@ export interface RepoMeshNodePolicy {
|
|
|
374
385
|
* precedence over the mesh-level policy for worker sessions launched onto this node.
|
|
375
386
|
*/
|
|
376
387
|
delegatedWorkerAutoApprove?: boolean;
|
|
388
|
+
/**
|
|
389
|
+
* MESH-SEND-KEYS (feature 3): per-node override for
|
|
390
|
+
* RepoMeshPolicy.allowSendKeysDestructive.
|
|
391
|
+
*/
|
|
392
|
+
allowSendKeysDestructive?: boolean;
|
|
377
393
|
/**
|
|
378
394
|
* Optional associated/external repos that must be checked alongside this node.
|
|
379
395
|
* These are explicit policy/config entries only; Repo Mesh does not auto-discover
|
|
@@ -479,6 +495,13 @@ export declare function mergeAndNormalizePolicy(base: RepoMeshPolicy | undefined
|
|
|
479
495
|
* launch path merges the envelope as a settingsOverride on top of the provider defaults.
|
|
480
496
|
*/
|
|
481
497
|
export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove'> | null): boolean;
|
|
498
|
+
/**
|
|
499
|
+
* MESH-SEND-KEYS (feature 3): resolve whether DESTRUCTIVE key injection
|
|
500
|
+
* (CTRL_C/ESC via mesh_send_keys) is permitted for a node. Node policy overrides
|
|
501
|
+
* mesh policy; DEFAULTS TO FALSE (fail-closed) — a destructive key still requires
|
|
502
|
+
* a per-call confirm_destructive=true on top of this opt-in.
|
|
503
|
+
*/
|
|
504
|
+
export declare function resolveAllowSendKeysDestructive(meshPolicy?: Pick<RepoMeshPolicy, 'allowSendKeysDestructive'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'allowSendKeysDestructive'> | null): boolean;
|
|
482
505
|
/**
|
|
483
506
|
* Resolve the enforced per-(node, provider) maxParallel cap from a node's resolved
|
|
484
507
|
* capability slots, or undefined when no matching slot declares a finite cap. Used
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.554",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.554",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.554",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
package/src/cli-adapter-types.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ChatMessage } from './types.js';
|
|
8
8
|
import type { InteractivePrompt, InteractivePromptResponse } from './providers/types/interactive-prompt.js';
|
|
9
|
+
import type { MeshSendKeyItem, MeshSendKeyName } from './cli-adapters/provider-cli-shared.js';
|
|
9
10
|
|
|
10
11
|
export interface CliAdapterStatus {
|
|
11
12
|
status?: string;
|
|
@@ -118,6 +119,34 @@ export interface CliAdapter {
|
|
|
118
119
|
cancel(): void;
|
|
119
120
|
isProcessing(): boolean;
|
|
120
121
|
isReady(): boolean;
|
|
122
|
+
// Liveness of the underlying process/PTY. Optional because not every adapter
|
|
123
|
+
// implementation exposes it (the spec-driven path historically did not); the
|
|
124
|
+
// MESH-STALL-WATCH watchdog must call it defensively (typeof guard) so a
|
|
125
|
+
// missing implementation never throws in the 5s tick.
|
|
126
|
+
isAlive?(): boolean;
|
|
127
|
+
// MESH-READ-TERMINAL (feature 2) / MESH-SEND-KEYS (feature 3). Optional
|
|
128
|
+
// because not every adapter implements the raw-terminal read / structured
|
|
129
|
+
// key-injection surface; callers (cli-provider-instance) MUST typeof-guard
|
|
130
|
+
// and return a clean unsupported result rather than throwing when absent.
|
|
131
|
+
// Both ProviderCliAdapter (PTY path) and SpecCliAdapter (native-source spec
|
|
132
|
+
// path — claude-cli / antigravity / codex-cli) implement them.
|
|
133
|
+
getTerminalScreenSnapshot?(maxBytes?: number): {
|
|
134
|
+
text: string;
|
|
135
|
+
cursor: { col: number; row: number };
|
|
136
|
+
cols: number;
|
|
137
|
+
rows: number;
|
|
138
|
+
truncated: boolean;
|
|
139
|
+
originalBytes: number;
|
|
140
|
+
returnedBytes: number;
|
|
141
|
+
hash: string;
|
|
142
|
+
};
|
|
143
|
+
injectKeys?(
|
|
144
|
+
items: MeshSendKeyItem[],
|
|
145
|
+
opts?: { allowModalOverride?: boolean },
|
|
146
|
+
): Promise<
|
|
147
|
+
| { ok: true; keys: MeshSendKeyName[]; hasDestructive: boolean; submits: boolean; bytes: number }
|
|
148
|
+
| { ok: false; refused: 'submit_race' | 'actionable_modal'; keys: MeshSendKeyName[]; hasDestructive: boolean }
|
|
149
|
+
>;
|
|
121
150
|
setOnStatusChange(callback: () => void): void;
|
|
122
151
|
updateRuntimeSettings?(settings: Record<string, unknown>): void;
|
|
123
152
|
setCliScripts?(scripts: Record<string, unknown>): void;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import * as os from 'os';
|
|
18
|
+
import { createHash } from 'crypto';
|
|
18
19
|
import type { CliAdapter, CliLaunchInfo } from '../cli-adapter-types.js';
|
|
19
20
|
import type { InteractivePromptResponse } from '../providers/types/interactive-prompt.js';
|
|
20
21
|
import { LOG } from '../logging/logger.js';
|
|
@@ -43,7 +44,11 @@ import {
|
|
|
43
44
|
normalizeScreenSnapshot,
|
|
44
45
|
promptLikelyVisible,
|
|
45
46
|
sanitizeTerminalText,
|
|
47
|
+
truncateToByteTailByLine,
|
|
48
|
+
encodeMeshSendKeys,
|
|
46
49
|
TerminalTranscriptAccumulator,
|
|
50
|
+
type MeshSendKeyItem,
|
|
51
|
+
type MeshSendKeyName,
|
|
47
52
|
type CliChatMessage,
|
|
48
53
|
type CliProviderModule,
|
|
49
54
|
type CliScriptInput,
|
|
@@ -308,6 +313,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
308
313
|
result: any;
|
|
309
314
|
} | null = null;
|
|
310
315
|
private static readonly SCREEN_SNAPSHOT_MIN_INTERVAL_MS = 250;
|
|
316
|
+
// MESH-READ-TERMINAL (feature 2): byte caps for getTerminalScreenSnapshot.
|
|
317
|
+
// Byte, not char — a multi-byte-glyph screen can exceed an MCP payload cap
|
|
318
|
+
// while the char count still looks safe. 32KiB default, 64KiB absolute hard cap.
|
|
319
|
+
private static readonly TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES = 32 * 1024;
|
|
320
|
+
private static readonly TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES = 64 * 1024;
|
|
311
321
|
// (FALSEIDLE Path-C) Consecutive gate-eligible getStatus polls a mesh/autonomous
|
|
312
322
|
// session must show before the poll-static-idle confirm fires. 2 = one extra
|
|
313
323
|
// status tick of hysteresis: enough to reject a single momentary-silence
|
|
@@ -2297,6 +2307,131 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2297
2307
|
};
|
|
2298
2308
|
}
|
|
2299
2309
|
isAlive(): boolean { return this.ptyProcess !== null; }
|
|
2310
|
+
|
|
2311
|
+
/**
|
|
2312
|
+
* MESH-READ-TERMINAL (feature 2: RAW terminal read). Narrow, least-privilege
|
|
2313
|
+
* read of the CURRENT rendered viewport for mesh_read_terminal. Deliberately
|
|
2314
|
+
* NARROWER than getSnapshot()/getDebugSnapshot():
|
|
2315
|
+
* - It returns ONLY the terminal's current rendered viewport (what a human
|
|
2316
|
+
* would see on screen right now), the cursor position and the viewport
|
|
2317
|
+
* size. NO debug buffers, NO parser/FSM state, NO scrollback/history.
|
|
2318
|
+
* - It does NOT call getParseScreenText() (which may graft an older snapshot
|
|
2319
|
+
* onto the current frame for parse accuracy) — the caller asked for the
|
|
2320
|
+
* live viewport, not a parse-optimized composite.
|
|
2321
|
+
* - The payload is bounded in BYTES (UTF-8) with bottom-tail preservation so
|
|
2322
|
+
* a screen of multi-byte glyphs can never exceed the MCP payload cap. See
|
|
2323
|
+
* truncateToByteTailByLine.
|
|
2324
|
+
*
|
|
2325
|
+
* SECURITY NOTE: the raw viewport can contain tokens / command args / env
|
|
2326
|
+
* values / user data. Callers MUST gate this on mesh ownership and MUST NOT
|
|
2327
|
+
* log the returned text. Opt-in redaction is intentionally out of scope for
|
|
2328
|
+
* this feature and left as a future enhancement.
|
|
2329
|
+
*
|
|
2330
|
+
* `maxBytes` is clamped to [1KiB, ABSOLUTE_MAX] (default 32KiB, hard cap 64KiB).
|
|
2331
|
+
*/
|
|
2332
|
+
getTerminalScreenSnapshot(maxBytes = ProviderCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES): {
|
|
2333
|
+
text: string;
|
|
2334
|
+
cursor: { col: number; row: number };
|
|
2335
|
+
cols: number;
|
|
2336
|
+
rows: number;
|
|
2337
|
+
truncated: boolean;
|
|
2338
|
+
originalBytes: number;
|
|
2339
|
+
returnedBytes: number;
|
|
2340
|
+
hash: string;
|
|
2341
|
+
} {
|
|
2342
|
+
const cap = Math.min(
|
|
2343
|
+
ProviderCliAdapter.TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES,
|
|
2344
|
+
Math.max(1024, Math.floor(maxBytes) || ProviderCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES),
|
|
2345
|
+
);
|
|
2346
|
+
// Current rendered viewport only — no scrollback, no parse composite.
|
|
2347
|
+
const rawViewport = this.terminalScreen.getText() || '';
|
|
2348
|
+
const size = this.terminalScreen.getSize();
|
|
2349
|
+
const cursor = this.terminalScreen.getCursorPosition();
|
|
2350
|
+
const truncation = truncateToByteTailByLine(rawViewport, cap);
|
|
2351
|
+
const hash = createHash('sha256').update(rawViewport, 'utf8').digest('hex').slice(0, 16);
|
|
2352
|
+
return {
|
|
2353
|
+
text: truncation.text,
|
|
2354
|
+
cursor,
|
|
2355
|
+
cols: size.cols,
|
|
2356
|
+
rows: size.rows,
|
|
2357
|
+
truncated: truncation.truncated,
|
|
2358
|
+
originalBytes: truncation.originalBytes,
|
|
2359
|
+
returnedBytes: truncation.returnedBytes,
|
|
2360
|
+
hash,
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
/**
|
|
2365
|
+
* MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key sequence
|
|
2366
|
+
* into the PTY for the mesh_send_keys tool. Reuses the same serialized write
|
|
2367
|
+
* path as sends (writeToPty via writeRaw semantics) so the injection FIFO-
|
|
2368
|
+
* orders behind any in-flight write.
|
|
2369
|
+
*
|
|
2370
|
+
* Two guards run INSIDE this method, immediately before the write, so they see
|
|
2371
|
+
* a consistent snapshot of the adapter's submit/echo/queue state (no async gap
|
|
2372
|
+
* between check and write — the encode is synchronous and the write is chained
|
|
2373
|
+
* atomically after it):
|
|
2374
|
+
*
|
|
2375
|
+
* 1. submit-race recheck (echo-gate): even though writeToPty is FIFO, an
|
|
2376
|
+
* already-SCHEDULED echo-gated Enter (engine.submitPendingUntil in the
|
|
2377
|
+
* future), an armed stuck-submit retry (submitRetryTimer), or an in-flight
|
|
2378
|
+
* pending-outbound flush can submit at a DIFFERENT tick than our write —
|
|
2379
|
+
* so an injected literal could be submitted by a pending Enter, or our
|
|
2380
|
+
* ENTER could submit a half-typed pending body. When any of those is live
|
|
2381
|
+
* we REFUSE the injection rather than interleave.
|
|
2382
|
+
*
|
|
2383
|
+
* 2. modal fail-closed: if the session is parked on an actionable approval
|
|
2384
|
+
* modal, refuse a NON-destructive injection (ENTER/text/arrows) and direct
|
|
2385
|
+
* the caller to mesh_approve — so a modal choice can't be confirmed via
|
|
2386
|
+
* send_keys to bypass the approval policy. (A destructive ESC/CTRL_C, which
|
|
2387
|
+
* dismisses rather than confirms, is allowed to proceed past this gate; it
|
|
2388
|
+
* is separately gated by confirm_destructive + policy at the tool layer.)
|
|
2389
|
+
*
|
|
2390
|
+
* The caller (tool layer) owns the destructive-key double gate and the audit
|
|
2391
|
+
* ledger. This method NEVER logs the literal text (only key enums / byte len).
|
|
2392
|
+
*/
|
|
2393
|
+
async injectKeys(
|
|
2394
|
+
items: MeshSendKeyItem[],
|
|
2395
|
+
opts: { allowModalOverride?: boolean } = {},
|
|
2396
|
+
): Promise<
|
|
2397
|
+
| { ok: true; keys: MeshSendKeyName[]; hasDestructive: boolean; submits: boolean; bytes: number }
|
|
2398
|
+
| { ok: false; refused: 'submit_race' | 'actionable_modal'; keys: MeshSendKeyName[]; hasDestructive: boolean }
|
|
2399
|
+
> {
|
|
2400
|
+
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
2401
|
+
const encoded = encodeMeshSendKeys(items);
|
|
2402
|
+
|
|
2403
|
+
// (1) submit-race recheck — atomic w.r.t. the write below (no await between).
|
|
2404
|
+
const now = Date.now();
|
|
2405
|
+
const submitPending = this.engine.submitPendingUntil > now;
|
|
2406
|
+
const submitRetryArmed = this.submitRetryTimer !== null;
|
|
2407
|
+
const outboundBusy = this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length > 0;
|
|
2408
|
+
if (submitPending || submitRetryArmed || outboundBusy) {
|
|
2409
|
+
LOG.warn('CLI', `[${this.cliType}] send_keys refused (submit_race): submitPending=${submitPending} submitRetry=${submitRetryArmed} outboundBusy=${outboundBusy} keys=${encoded.keys.join(',')}`);
|
|
2410
|
+
return { ok: false, refused: 'submit_race', keys: encoded.keys, hasDestructive: encoded.hasDestructive };
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// (2) modal fail-closed — a NON-destructive injection into an actionable
|
|
2414
|
+
// modal is refused unless explicitly overridden.
|
|
2415
|
+
const modalActive = this.engine.hasActionableApproval();
|
|
2416
|
+
if (modalActive && !encoded.hasDestructive && !opts.allowModalOverride) {
|
|
2417
|
+
LOG.warn('CLI', `[${this.cliType}] send_keys refused (actionable_modal): keys=${encoded.keys.join(',')} — use mesh_approve`);
|
|
2418
|
+
return { ok: false, refused: 'actionable_modal', keys: encoded.keys, hasDestructive: encoded.hasDestructive };
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// Atomic write: the full encoded sequence goes out in ONE writeToPty (which
|
|
2422
|
+
// chains onto the write FIFO). text+ENTER is already one contiguous string,
|
|
2423
|
+
// so the submit key can never be separated from the text it submits.
|
|
2424
|
+
await this.writeToPty(encoded.sequence);
|
|
2425
|
+
LOG.info('CLI', `[${this.cliType}] send_keys injected keys=${encoded.keys.join(',') || '(text-only)'} bytes=${Buffer.byteLength(encoded.sequence, 'utf8')} destructive=${encoded.hasDestructive}`);
|
|
2426
|
+
return {
|
|
2427
|
+
ok: true,
|
|
2428
|
+
keys: encoded.keys,
|
|
2429
|
+
hasDestructive: encoded.hasDestructive,
|
|
2430
|
+
submits: encoded.submits,
|
|
2431
|
+
bytes: Buffer.byteLength(encoded.sequence, 'utf8'),
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2300
2435
|
flushOutboundQueue(): void { this.schedulePendingOutboundFlush(); }
|
|
2301
2436
|
|
|
2302
2437
|
async writeRaw(data: string | Buffer): Promise<void> {
|