@adhdev/daemon-core 1.0.18-rc.19 → 1.0.18-rc.2
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-adapters/cli-state-engine.d.ts +0 -77
- package/dist/cli-adapters/provider-cli-shared.d.ts +0 -33
- package/dist/cli-adapters/pty-transport.d.ts +1 -2
- package/dist/commands/cli-manager.d.ts +0 -9
- package/dist/commands/upgrade-helper.d.ts +0 -1
- package/dist/commands/windows-atomic-upgrade.d.ts +1 -17
- package/dist/config/mesh-json-config.d.ts +0 -62
- package/dist/index.d.ts +2 -4
- package/dist/index.js +4143 -6578
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4138 -6570
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +0 -76
- package/dist/mesh/mesh-active-work.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -28
- package/dist/mesh/mesh-node-identity.d.ts +0 -23
- package/dist/mesh/mesh-refine-gates.d.ts +0 -89
- package/dist/mesh/mesh-remote-event-pull.d.ts +0 -3
- package/dist/mesh/mesh-runtime-store.d.ts +0 -11
- package/dist/mesh/mesh-work-queue.d.ts +1 -24
- package/dist/providers/chat-message-normalization.d.ts +0 -22
- package/dist/providers/cli-provider-instance-types.d.ts +0 -4
- package/dist/providers/cli-provider-instance.d.ts +0 -78
- package/dist/providers/contracts.d.ts +0 -17
- package/dist/providers/provider-schema.d.ts +0 -1
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +0 -14
- package/dist/providers/types/interactive-prompt.d.ts +0 -18
- package/dist/repo-mesh-types.d.ts +6 -38
- package/dist/shared-types.d.ts +2 -4
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +0 -10
- package/src/cli-adapters/cli-state-engine.ts +3 -220
- package/src/cli-adapters/provider-cli-adapter.ts +11 -41
- package/src/cli-adapters/provider-cli-shared.ts +0 -41
- package/src/cli-adapters/pty-transport.d.ts +1 -2
- package/src/cli-adapters/pty-transport.ts +1 -1
- package/src/cli-adapters/session-host-transport.ts +3 -8
- package/src/commands/cli-manager.ts +8 -72
- package/src/commands/high-family/mesh-coordinator-launch.ts +2 -17
- package/src/commands/high-family/mesh-events.ts +2 -13
- package/src/commands/med-family/mesh-crud.ts +0 -155
- package/src/commands/med-family/mesh-queue.ts +1 -74
- package/src/commands/router-refine.ts +4 -71
- package/src/commands/router.ts +0 -8
- package/src/commands/upgrade-helper.ts +82 -106
- package/src/commands/windows-atomic-upgrade.ts +35 -281
- package/src/config/mesh-json-config.ts +0 -111
- package/src/index.ts +1 -21
- package/src/mesh/coordinator-prompt.ts +11 -258
- package/src/mesh/mesh-active-work.ts +1 -11
- package/src/mesh/mesh-completion-synthesis.ts +1 -12
- package/src/mesh/mesh-event-classify.ts +0 -19
- package/src/mesh/mesh-event-forwarding.ts +6 -64
- package/src/mesh/mesh-events-utils.ts +0 -42
- package/src/mesh/mesh-ledger.ts +0 -77
- package/src/mesh/mesh-node-identity.ts +0 -49
- package/src/mesh/mesh-queue-assignment.ts +11 -210
- package/src/mesh/mesh-reconcile-loop.ts +3 -306
- package/src/mesh/mesh-refine-gates.ts +0 -300
- package/src/mesh/mesh-remote-event-pull.ts +47 -68
- package/src/mesh/mesh-runtime-store.ts +0 -21
- package/src/mesh/mesh-work-queue.ts +2 -85
- package/src/providers/chat-message-normalization.ts +0 -53
- package/src/providers/cli-provider-instance-types.ts +0 -53
- package/src/providers/cli-provider-instance.ts +15 -497
- package/src/providers/contracts.ts +1 -25
- package/src/providers/provider-schema.ts +0 -83
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +0 -23
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +0 -20
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +0 -44
- package/src/providers/types/interactive-prompt.ts +0 -77
- package/src/repo-mesh-types.ts +12 -112
- package/src/session-host/managed-host.ts +0 -34
- package/src/shared-types.ts +1 -9
- package/src/status/reporter.ts +0 -1
- package/src/status/snapshot.ts +0 -2
- package/dist/commands/process-lifecycle.d.ts +0 -43
- package/dist/mesh/mesh-disk-retention.d.ts +0 -105
- package/dist/providers/auto-approve-modes.d.ts +0 -14
- package/src/commands/process-lifecycle.ts +0 -248
- package/src/mesh/mesh-disk-retention.ts +0 -370
- package/src/providers/auto-approve-modes.ts +0 -97
|
@@ -14,8 +14,6 @@ import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
|
|
|
14
14
|
import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
|
|
15
15
|
import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
|
|
16
16
|
import type { MagiKindPanelMap, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
|
|
17
|
-
import type { ProviderModule } from './providers/contracts.js';
|
|
18
|
-
import type { RepoMeshDeclarativeConfig } from './config/mesh-json-config.js';
|
|
19
17
|
export interface RepoMesh {
|
|
20
18
|
id: string;
|
|
21
19
|
name: string;
|
|
@@ -257,8 +255,6 @@ export interface RepoMeshPolicy {
|
|
|
257
255
|
* A node policy may override this per-node (RepoMeshNodePolicy.delegatedWorkerAutoApprove).
|
|
258
256
|
*/
|
|
259
257
|
delegatedWorkerAutoApprove?: boolean;
|
|
260
|
-
/** Explicit opt-in required before delegated workers may use a dangerous provider mode. */
|
|
261
|
-
delegatedWorkerDangerousModeAllow?: boolean;
|
|
262
258
|
/**
|
|
263
259
|
* MESH-SEND-KEYS (feature 3): opt-in to allow the coordinator to inject
|
|
264
260
|
* DESTRUCTIVE keys (CTRL_C / ESC) into a worker PTY via mesh_send_keys. These
|
|
@@ -410,8 +406,6 @@ export interface RepoMeshNodePolicy {
|
|
|
410
406
|
* precedence over the mesh-level policy for worker sessions launched onto this node.
|
|
411
407
|
*/
|
|
412
408
|
delegatedWorkerAutoApprove?: boolean;
|
|
413
|
-
/** Per-node override for dangerous delegated worker mode authorization. */
|
|
414
|
-
delegatedWorkerDangerousModeAllow?: boolean;
|
|
415
409
|
/**
|
|
416
410
|
* MESH-SEND-KEYS (feature 3): per-node override for
|
|
417
411
|
* RepoMeshPolicy.allowSendKeysDestructive.
|
|
@@ -530,39 +524,13 @@ export declare function normalizeAutoFastForwardPolicy(value: unknown): NonNulla
|
|
|
530
524
|
*/
|
|
531
525
|
export declare function mergeAndNormalizePolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy;
|
|
532
526
|
/**
|
|
533
|
-
* Resolve delegated worker
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
* invariant:
|
|
539
|
-
*
|
|
540
|
-
* ① ENABLE gate (machine-local policy only): node boolean > mesh boolean.
|
|
541
|
-
* `enabled=false` returns `false` IMMEDIATELY, BEFORE any mode selection.
|
|
542
|
-
* The repo `mesh.json` providerDefaults has ZERO influence here — a
|
|
543
|
-
* node/mesh opt-out is never overridden by a repo-declared requested mode.
|
|
544
|
-
*
|
|
545
|
-
* ② MODE selection (only when enabled === true):
|
|
546
|
-
* task override [FUTURE — param reserved below, not yet wired] >
|
|
547
|
-
* repo mesh.json providerDefaults.autoApproveModes[providerType] >
|
|
548
|
-
* provider spec autoApproveModes.default.
|
|
549
|
-
* A repo-requested mode ID is adopted ONLY when it exists in the provider's
|
|
550
|
-
* own `autoApproveModes.modes`; an unknown/stale/typo'd ID is IGNORED and we
|
|
551
|
-
* fall back to the provider default (fail-closed: never coerce into a
|
|
552
|
-
* dangerous mode via a bad ID).
|
|
553
|
-
*
|
|
554
|
-
* ③ DANGEROUS gate: whichever mode stage ② picked, if it is dangerous and the
|
|
555
|
-
* machine-local delegatedWorkerDangerousModeAllow is not set, downgrade to a
|
|
556
|
-
* non-dangerous PTY-parse mode (or `false` if none exists).
|
|
527
|
+
* Resolve whether a delegated worker session launched onto `nodePolicy` (within a mesh
|
|
528
|
+
* governed by `meshPolicy`) should auto-approve. Precedence: node override → mesh policy
|
|
529
|
+
* → default true. The result is stamped into the worker launch settings envelope as
|
|
530
|
+
* `autoApprove`; it wins over the global per-provider-type autoApprove config because the
|
|
531
|
+
* launch path merges the envelope as a settingsOverride on top of the provider defaults.
|
|
557
532
|
*/
|
|
558
|
-
export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove'
|
|
559
|
-
export declare function resolveDelegatedWorkerDangerousModeAllow(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerDangerousModeAllow'> | null): boolean;
|
|
560
|
-
/** Shape a boolean-or-mode resolution for the settings precedence contract. */
|
|
561
|
-
export declare function delegatedWorkerAutoApproveSettings(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, provider?: Pick<ProviderModule, 'autoApproveModes'> | null, repoConfig?: RepoMeshDeclarativeConfig | null, providerType?: string | null): {
|
|
562
|
-
autoApprove: boolean | undefined;
|
|
563
|
-
autoApproveMode: string | undefined;
|
|
564
|
-
delegatedWorkerDangerousModeAllow: boolean;
|
|
565
|
-
};
|
|
533
|
+
export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove'> | null): boolean;
|
|
566
534
|
/**
|
|
567
535
|
* MESH-SEND-KEYS (feature 3): resolve whether DESTRUCTIVE key injection
|
|
568
536
|
* (CTRL_C/ESC via mesh_send_keys) is permitted for a node. Node policy overrides
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { ProviderState, ProviderStatus, ActiveChatData, IdeProviderState, C
|
|
|
13
13
|
export type { ProviderErrorReason } from './providers/provider-instance.js';
|
|
14
14
|
import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _ProviderErrorReason } from './providers/provider-instance.js';
|
|
15
15
|
import type { WorkspaceEntry } from './config/workspaces.js';
|
|
16
|
-
import type {
|
|
16
|
+
import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
|
|
17
17
|
import type { GitCompactSummary, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams } from './git/git-types.js';
|
|
18
18
|
import type { InteractivePrompt } from './providers/types/interactive-prompt.js';
|
|
19
19
|
export type { GitCommandName, GitCompactSummary, GitDiffSummary, GitFailureReason, GitFileChange, GitFileChangeStatus, GitRepoIdentity, GitRepoStatus, GitSnapshot, GitSnapshotCompareSummary, GitSnapshotReason, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams, } from './git/git-types.js';
|
|
@@ -428,8 +428,6 @@ export interface AvailableProviderInfo {
|
|
|
428
428
|
lastVerification?: MachineProviderCheckResult;
|
|
429
429
|
/** Provider-declared Repo Mesh coordinator/MCP behavior. */
|
|
430
430
|
meshCoordinator?: ProviderMeshCoordinatorConfig;
|
|
431
|
-
/** Provider-declared auto-approve choices shown by session launch UIs. */
|
|
432
|
-
autoApproveModes?: AutoApproveModesConfig;
|
|
433
431
|
/** BRAIN-ROUTING: suggested model values for the new-session model dropdown. */
|
|
434
432
|
modelOptions?: string[];
|
|
435
433
|
/** BRAIN-ROUTING: reasoning-effort values for the new-session thinking dropdown. */
|
|
@@ -626,7 +624,7 @@ export interface DashboardBootstrapDaemonEntry extends Partial<CloudDaemonSummar
|
|
|
626
624
|
p2p?: StatusReportPayload['p2p'];
|
|
627
625
|
timestamp?: number;
|
|
628
626
|
}
|
|
629
|
-
export type DaemonStatusEventName = 'agent:generating_started' | 'agent:waiting_approval' | 'agent:
|
|
627
|
+
export type DaemonStatusEventName = 'agent:generating_started' | 'agent:waiting_approval' | 'agent:generating_completed' | 'agent:stopped' | 'monitor:no_progress' | 'monitor:long_generating';
|
|
630
628
|
/** Minimal daemon-originated event payload relayed through the server. */
|
|
631
629
|
export interface DaemonStatusEventPayload {
|
|
632
630
|
event: DaemonStatusEventName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "1.0.18-rc.
|
|
3
|
+
"version": "1.0.18-rc.2",
|
|
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": "1.0.18-rc.
|
|
51
|
-
"@adhdev/session-host-core": "1.0.18-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "1.0.18-rc.2",
|
|
51
|
+
"@adhdev/session-host-core": "1.0.18-rc.2",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -39,7 +39,6 @@ import type { IdeProviderInstance } from '../providers/ide-provider-instance.js'
|
|
|
39
39
|
import { createDefaultGitCommandServices } from '../git/git-commands.js';
|
|
40
40
|
import { setupMeshEventForwarding } from '../mesh/mesh-events.js';
|
|
41
41
|
import { setupMeshReconcileLoop } from '../mesh/mesh-reconcile-loop.js';
|
|
42
|
-
import { MeshRuntimeStore } from '../mesh/mesh-runtime-store.js';
|
|
43
42
|
import { loadMeshCoordinatorRegistry } from '../mesh/coordinator-registry.js';
|
|
44
43
|
import { applyProcessHardening } from './process-hardening.js';
|
|
45
44
|
import { installProviderProcessShim } from '../providers/sdk/v1/sandbox/require-whitelist.js';
|
|
@@ -504,13 +503,4 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
|
|
|
504
503
|
try { m.disconnect(); } catch { /* noop */ }
|
|
505
504
|
}
|
|
506
505
|
cdpManagers.clear();
|
|
507
|
-
|
|
508
|
-
// 7. VACUUM the mesh runtime DB. Retention prunes rows with DELETE (frees pages
|
|
509
|
-
// inside the file but never shrinks it); the mesh-runtime.db grew to hundreds of
|
|
510
|
-
// MB (mission 86def38d disk-accumulation bootstrap failure) because it was never
|
|
511
|
-
// compacted. Do this LAST, after all writers (reconcile loop, CLIs, instances)
|
|
512
|
-
// are stopped, so nothing contends for the exclusive VACUUM lock. Best-effort —
|
|
513
|
-
// vacuum() swallows its own errors; the try/catch guards the getInstance() throw
|
|
514
|
-
// when the store never opened (degraded JSONL-only mode).
|
|
515
|
-
try { MeshRuntimeStore.getInstance().vacuum(); } catch { /* store unavailable — nothing to vacuum */ }
|
|
516
506
|
}
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
buildCliScreenSnapshot,
|
|
17
17
|
compactPromptText,
|
|
18
|
-
isPurePtyTranscriptProvider,
|
|
19
18
|
normalizePromptText,
|
|
20
19
|
promptLikelyVisible,
|
|
21
20
|
type CliChatMessage,
|
|
@@ -87,18 +86,6 @@ interface IdleFinishCandidate {
|
|
|
87
86
|
assistantLength: number;
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
/**
|
|
91
|
-
* Minimal shape computeApprovalContentSignature / isStaleResolvedApproval
|
|
92
|
-
* actually need — deliberately narrower than the full CliBufferSnapshot so
|
|
93
|
-
* callers outside the settled-eval loop (e.g. the adapter's startup-gate
|
|
94
|
-
* modal parse in getStatus/getDebugState) can supply just these two fields
|
|
95
|
-
* without having to fabricate an entire snapshot.
|
|
96
|
-
*/
|
|
97
|
-
interface ApprovalSignatureSnapshot {
|
|
98
|
-
screenText?: string;
|
|
99
|
-
accumulatedBuffer?: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
89
|
interface SettledEvalContext {
|
|
103
90
|
now: number;
|
|
104
91
|
modal: { message: string; buttons: string[] } | null;
|
|
@@ -168,22 +155,6 @@ export class CliStateEngine {
|
|
|
168
155
|
// ── Approval ─────────────────────────────────────
|
|
169
156
|
lastApprovalResolvedAt = 0;
|
|
170
157
|
lastResolvedModalMessage = '';
|
|
171
|
-
/**
|
|
172
|
-
* Normalized approval-context signature captured at resolve time (see
|
|
173
|
-
* `computeApprovalContentSignature`) — the screen text with blank-line
|
|
174
|
-
* padding and any manifest-declared chrome (transcriptPty.chromePatterns,
|
|
175
|
-
* spinner.patterns) stripped out. Used by applyWaitingApproval's
|
|
176
|
-
* isStaleResolvedRepaint check to tell "the same already-answered modal,
|
|
177
|
-
* re-parsed from a screen that has not meaningfully changed" apart from
|
|
178
|
-
* "a genuinely new approval" — content-based, not time-based, because
|
|
179
|
-
* ordinary TUI chrome (status bar, context meter, blank-line repaint)
|
|
180
|
-
* keeps producing fresh PTY bytes on an otherwise-unchanged screen and
|
|
181
|
-
* defeats any output-timestamp discriminator within a few hundred ms.
|
|
182
|
-
* Cleared whenever a turn starts or the session tears down (see
|
|
183
|
-
* onTurnStarted / resetActiveTurnState / onPtyExit) so a next-turn
|
|
184
|
-
* approval is never compared against stale prior-turn state.
|
|
185
|
-
*/
|
|
186
|
-
lastApprovalResolvedContentSignature = '';
|
|
187
158
|
/**
|
|
188
159
|
* Monotonic counter bumped every time the FSM *enters* waiting_approval
|
|
189
160
|
* with a freshly captured modal (see `applyWaitingApproval`). It is the
|
|
@@ -333,61 +304,12 @@ export class CliStateEngine {
|
|
|
333
304
|
// anchor its window on dispatch time rather than completion time.
|
|
334
305
|
this.currentTurnStartedAt = Date.now();
|
|
335
306
|
this.responseEpoch += 1;
|
|
336
|
-
// A new turn's own prompt echo is real new content, so the stale-repaint
|
|
337
|
-
// guard's signature would naturally drift anyway — but clear the
|
|
338
|
-
// resolve-time bookkeeping explicitly here too, so a next-turn approval
|
|
339
|
-
// that happens to share message text with a previous turn's already-
|
|
340
|
-
// resolved one is never compared against stale prior-turn state.
|
|
341
|
-
this.clearApprovalResolutionMemory();
|
|
342
|
-
// (fix: kimi K3 send→idle-looking→generating lag / missed-generating on
|
|
343
|
-
// fast tool turns) For transcriptAuthority:'provider' providers (kimi,
|
|
344
|
-
// and other native-transcript sources), the PTY-scanned parser always
|
|
345
|
-
// returns messages:[] — the provider owns the transcript, not the PTY —
|
|
346
|
-
// so evaluateSettled's shouldHoldGenerating fast-path exception
|
|
347
|
-
// (hasFinalCurrentTurnAssistant) can never release early and
|
|
348
|
-
// recent_activity_hold ends up carrying the ENTIRE "is this turn still
|
|
349
|
-
// generating" signal for these providers regardless of what the spinner
|
|
350
|
-
// script does. That fallback only fires once a settle tick actually
|
|
351
|
-
// runs, which needs PTY output to schedule — for a model that "thinks"
|
|
352
|
-
// silently for many seconds before its first repaint (observed 12-20s
|
|
353
|
-
// for Kimi K3's default "high" effort), the dashboard looks idle for
|
|
354
|
-
// that whole window even though the turn was already accepted. Worse,
|
|
355
|
-
// a turn that fully completes (tool calls + reply) within a single
|
|
356
|
-
// settle debounce window can go straight idle→idle with no visible
|
|
357
|
-
// generating state at all (observed live with a yolo tool-use turn).
|
|
358
|
-
// onTurnStarted is the authoritative "a turn was just submitted" signal
|
|
359
|
-
// — promote to generating immediately instead of waiting on PTY-driven
|
|
360
|
-
// detection. This does not weaken completion detection: applyIdle /
|
|
361
|
-
// finishResponse (authoritative settled evidence) still own the actual
|
|
362
|
-
// idle transition, unchanged. Scoped to transcriptAuthority:'provider'
|
|
363
|
-
// only, so PTY-authoritative providers (whose spinner/settled parsing
|
|
364
|
-
// already drives generating promptly) are unaffected.
|
|
365
|
-
//
|
|
366
|
-
// (fix: kimi pure-PTY completion-emit) The pure-PTY full-buffer class
|
|
367
|
-
// (kimi and kin — see isPurePtyTranscriptProvider) is NOT
|
|
368
|
-
// transcriptAuthority:'provider', so without this it stays idle when a
|
|
369
|
-
// prompt is submitted from idle: the FSM never crosses generating→idle,
|
|
370
|
-
// detectStatusTransition's generating|waiting_approval→idle arm never
|
|
371
|
-
// runs, and agent:generating_completed is never emitted — the mesh
|
|
372
|
-
// coordinator leaves the task 'assigned' and the status-agnostic stall
|
|
373
|
-
// watchdog then false-fires task_stalled on the finished-but-idle
|
|
374
|
-
// session. Promoting this class to generating on turn-start makes the
|
|
375
|
-
// existing PTY-parsed final-assistant idle gate produce a real
|
|
376
|
-
// generating→idle completion edge. Same idle safety as above: applyIdle
|
|
377
|
-
// / finishResponse still own the actual idle transition.
|
|
378
|
-
const promoteOnTurnStart = this.provider.transcriptAuthority === 'provider'
|
|
379
|
-
|| isPurePtyTranscriptProvider(this.provider);
|
|
380
|
-
if (promoteOnTurnStart && this.currentStatus !== 'waiting_approval') {
|
|
381
|
-
this.setStatus('generating', 'turn_started');
|
|
382
|
-
this.callbacks.onStatusChange();
|
|
383
|
-
}
|
|
384
307
|
}
|
|
385
308
|
|
|
386
309
|
/** Called when PTY exits */
|
|
387
310
|
onPtyExit(): void {
|
|
388
311
|
this.clearAllTimers();
|
|
389
312
|
this.setStatus('stopped', 'pty_exit');
|
|
390
|
-
this.clearApprovalResolutionMemory();
|
|
391
313
|
}
|
|
392
314
|
|
|
393
315
|
/** Called when adapter starts up successfully */
|
|
@@ -460,11 +382,6 @@ export class CliStateEngine {
|
|
|
460
382
|
this.activeModal = null;
|
|
461
383
|
this.lastApprovalResolvedAt = Date.now();
|
|
462
384
|
this.lastResolvedModalMessage = currentModalMessage;
|
|
463
|
-
// Snapshot the chrome-stripped screen at the moment of resolve — the
|
|
464
|
-
// baseline applyWaitingApproval's isStaleResolvedRepaint check compares
|
|
465
|
-
// against to tell a stale re-parse of THIS modal apart from a
|
|
466
|
-
// genuinely new one, regardless of how much wall-clock time passes.
|
|
467
|
-
this.lastApprovalResolvedContentSignature = this.computeApprovalContentSignature(snap);
|
|
468
385
|
this.lastResolvedEntrySeq = this.approvalEntrySeq;
|
|
469
386
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
470
387
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
@@ -582,7 +499,6 @@ export class CliStateEngine {
|
|
|
582
499
|
this.pendingScriptStatusSince = 0;
|
|
583
500
|
this.approvalResumeDeferSince = 0;
|
|
584
501
|
this.approvalResumeDeferEpoch = -1;
|
|
585
|
-
this.clearApprovalResolutionMemory();
|
|
586
502
|
}
|
|
587
503
|
|
|
588
504
|
clearIdleFinishCandidate(reason: string): void {
|
|
@@ -763,9 +679,7 @@ export class CliStateEngine {
|
|
|
763
679
|
if (!status) return;
|
|
764
680
|
|
|
765
681
|
const prevStatus = this.currentStatus;
|
|
766
|
-
const ctx: SettledEvalContext = {
|
|
767
|
-
now, modal, status, parsedMessages, lastParsedAssistant, parsedStatus: parsedStatus || null, prevStatus,
|
|
768
|
-
};
|
|
682
|
+
const ctx: SettledEvalContext = { now, modal, status, parsedMessages, lastParsedAssistant, parsedStatus: parsedStatus || null, prevStatus };
|
|
769
683
|
|
|
770
684
|
if (!this.applyPendingScriptStatusDebounce(ctx)) return;
|
|
771
685
|
|
|
@@ -829,7 +743,7 @@ export class CliStateEngine {
|
|
|
829
743
|
this.applyError(ctx, session);
|
|
830
744
|
return;
|
|
831
745
|
}
|
|
832
|
-
if (status === 'waiting_approval') { this.applyWaitingApproval(ctx
|
|
746
|
+
if (status === 'waiting_approval') { this.applyWaitingApproval(ctx); return; }
|
|
833
747
|
if (status === 'generating') { this.applyGenerating(ctx); return; }
|
|
834
748
|
if (status === 'idle') { this.applyIdle(ctx, snap, now); }
|
|
835
749
|
}
|
|
@@ -887,7 +801,7 @@ export class CliStateEngine {
|
|
|
887
801
|
this.callbacks.onStatusChange();
|
|
888
802
|
}
|
|
889
803
|
|
|
890
|
-
private applyWaitingApproval(ctx: SettledEvalContext
|
|
804
|
+
private applyWaitingApproval(ctx: SettledEvalContext): void {
|
|
891
805
|
const { modal } = ctx;
|
|
892
806
|
this.clearIdleFinishCandidate('waiting_approval');
|
|
893
807
|
const inCooldown = this.lastApprovalResolvedAt
|
|
@@ -978,51 +892,6 @@ export class CliStateEngine {
|
|
|
978
892
|
}
|
|
979
893
|
return;
|
|
980
894
|
}
|
|
981
|
-
// (fix: kimi stale-approval re-latch, observed live on kimi-code
|
|
982
|
-
// v0.28.1/K3) A freshly-*parsed* modal is not proof the CLI is
|
|
983
|
-
// presenting it right now — parseApproval scans an accumulated
|
|
984
|
-
// raw-output window (recentOutputBuffer / window-around-question
|
|
985
|
-
// scope), which can still contain the text of an approval that was
|
|
986
|
-
// ALREADY resolved a moment ago and re-surface it on the very next
|
|
987
|
-
// settle pass, even though resolveModal() already wrote the key and
|
|
988
|
-
// cleared activeModal. Reproduced live: after approving a tool call,
|
|
989
|
-
// the FSM re-latched `waiting_approval` on the identical
|
|
990
|
-
// already-answered question with NO further genuinely new content
|
|
991
|
-
// ever arriving afterward — the dashboard stayed wedged on "waiting
|
|
992
|
-
// for approval" until the 5-minute maxResponse watchdog forced a
|
|
993
|
-
// recheck.
|
|
994
|
-
//
|
|
995
|
-
// An output-TIMESTAMP discriminator ("has any PTY byte arrived since
|
|
996
|
-
// the resolve") was tried first and found insufficient: a live
|
|
997
|
-
// standalone repro showed kimi's own idle-screen chrome (status bar,
|
|
998
|
-
// context meter, blank-line repaint) advances lastNonEmptyOutputAt
|
|
999
|
-
// within ~300ms of the resolve even though NOTHING approval-relevant
|
|
1000
|
-
// changed, so the guard stopped protecting almost immediately instead
|
|
1001
|
-
// of for as long as the staleness actually persisted (observed 30s+).
|
|
1002
|
-
//
|
|
1003
|
-
// Reject a recapture only when ALL of: (a) this is the first capture
|
|
1004
|
-
// since the last resolve (`!this.activeModal` — a genuinely repeated
|
|
1005
|
-
// approval re-enters this branch too, since resolveModal always nulls
|
|
1006
|
-
// activeModal), (b) the message text matches the one we just
|
|
1007
|
-
// resolved, AND (c) the chrome-stripped approval-context signature
|
|
1008
|
-
// (computeApprovalContentSignature — screen text with blank-line
|
|
1009
|
-
// padding and manifest-declared chrome removed) is UNCHANGED from
|
|
1010
|
-
// the signature captured at resolve time. (c) is the load-bearing,
|
|
1011
|
-
// content-based condition, deliberately not time-bounded: it keeps
|
|
1012
|
-
// rejecting for as long as nothing approval-relevant changes,
|
|
1013
|
-
// regardless of how many chrome-only repaints occur or how much
|
|
1014
|
-
// wall-clock time passes, and it stops rejecting the instant real
|
|
1015
|
-
// new content (tool output, a fresh conversational turn) appears.
|
|
1016
|
-
// This also keeps consecutive-approvals-with-identical-text (e.g.
|
|
1017
|
-
// two back-to-back "Allow Bash command?" prompts) working correctly:
|
|
1018
|
-
// a real follow-up approval necessarily means the CLI produced real
|
|
1019
|
-
// new output first (running the previous tool, then asking again),
|
|
1020
|
-
// which changes the signature regardless of shared message text.
|
|
1021
|
-
const isStaleResolvedRepaint = !this.activeModal && this.isStaleResolvedApproval(modal, snap);
|
|
1022
|
-
if (isStaleResolvedRepaint) {
|
|
1023
|
-
LOG.debug('CLI', `[${this.provider.type}] ignoring stale re-parsed approval matching the just-resolved modal (approval-context signature unchanged)`);
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
895
|
this.modalLostAt = 0;
|
|
1027
896
|
this.isWaitingForResponse = true;
|
|
1028
897
|
this.setStatus('waiting_approval', 'script_detect');
|
|
@@ -1387,92 +1256,6 @@ export class CliStateEngine {
|
|
|
1387
1256
|
|
|
1388
1257
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
1389
1258
|
|
|
1390
|
-
/**
|
|
1391
|
-
* Derive a stable approval-context signature from the current screen,
|
|
1392
|
-
* with blank-line padding and any manifest-declared chrome stripped out.
|
|
1393
|
-
*
|
|
1394
|
-
* Generic by design — no kimi- or provider-specific hardcoding: it reads
|
|
1395
|
-
* whatever `tui.transcriptPty.chromePatterns` and `tui.spinner.patterns`
|
|
1396
|
-
* the ACTIVE provider's own manifest already declares (present on any
|
|
1397
|
-
* declarative-TUI provider; simply absent/empty for scripted providers,
|
|
1398
|
-
* in which case this degrades to blank-line stripping only — never worse
|
|
1399
|
-
* than comparing the raw screen). Those pattern lists exist precisely to
|
|
1400
|
-
* name "known volatile repaint noise" (status bar, context meter, spinner
|
|
1401
|
-
* ticks, banners) — reusing them here means the SAME declared knowledge
|
|
1402
|
-
* that governs transcript-chrome stripping also governs staleness
|
|
1403
|
-
* detection, instead of re-encoding provider knowledge into daemon-core.
|
|
1404
|
-
*
|
|
1405
|
-
* Deliberately NOT a hash of the whole screen/buffer: an ordinary TUI
|
|
1406
|
-
* repaints its footer/status/context-meter chrome continuously even while
|
|
1407
|
-
* genuinely idle, so a raw whole-screen or whole-buffer fingerprint (or a
|
|
1408
|
-
* mere "did any bytes arrive" timestamp) changes on every repaint tick
|
|
1409
|
-
* regardless of whether anything approval-relevant actually happened.
|
|
1410
|
-
* Stripping the declared chrome first yields a signature that only
|
|
1411
|
-
* changes when the surrounding conversation/tool-output content itself
|
|
1412
|
-
* changes — exactly the discriminator applyWaitingApproval's
|
|
1413
|
-
* isStaleResolvedRepaint check needs.
|
|
1414
|
-
*/
|
|
1415
|
-
private computeApprovalContentSignature(snap: ApprovalSignatureSnapshot): string {
|
|
1416
|
-
const screenText = snap.screenText || snap.accumulatedBuffer || '';
|
|
1417
|
-
if (!screenText) return '';
|
|
1418
|
-
const tui = (this.provider as { tui?: Record<string, any> }).tui;
|
|
1419
|
-
const patternSpecs: Array<{ regex?: unknown; flags?: unknown }> = [
|
|
1420
|
-
...(Array.isArray(tui?.transcriptPty?.chromePatterns) ? tui!.transcriptPty.chromePatterns : []),
|
|
1421
|
-
...(Array.isArray(tui?.spinner?.patterns) ? tui!.spinner.patterns : []),
|
|
1422
|
-
];
|
|
1423
|
-
const chromeRegexes: RegExp[] = [];
|
|
1424
|
-
for (const spec of patternSpecs) {
|
|
1425
|
-
if (spec && typeof spec.regex === 'string') {
|
|
1426
|
-
try {
|
|
1427
|
-
chromeRegexes.push(new RegExp(spec.regex, typeof spec.flags === 'string' ? spec.flags : ''));
|
|
1428
|
-
} catch {
|
|
1429
|
-
// Ignore an unparseable manifest regex — signature just skips that filter.
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
const kept: string[] = [];
|
|
1434
|
-
for (const rawLine of screenText.split('\n')) {
|
|
1435
|
-
const line = rawLine.trim();
|
|
1436
|
-
if (!line) continue; // strip blank-line repaint padding
|
|
1437
|
-
if (chromeRegexes.some((re) => re.test(line))) continue; // strip declared chrome
|
|
1438
|
-
kept.push(line);
|
|
1439
|
-
}
|
|
1440
|
-
return kept.join('\n');
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
* True when `modal` is a stale re-parse of an already-resolved approval:
|
|
1445
|
-
* same message text, and the chrome-stripped approval-context signature
|
|
1446
|
-
* of `snap` is unchanged from the signature captured at resolve time.
|
|
1447
|
-
*
|
|
1448
|
-
* Public and reused verbatim by BOTH the settled-eval capture path
|
|
1449
|
-
* (applyWaitingApproval, below) and any OUTSIDE re-parse the adapter
|
|
1450
|
-
* performs independently of the settle loop — e.g. provider-cli-adapter's
|
|
1451
|
-
* getStatus()/getDebugState() startup-gate modal detection, which reads
|
|
1452
|
-
* `recentOutputBuffer` directly while `startupParseGate` is open and can
|
|
1453
|
-
* re-surface the same already-resolved modal before the gate closes.
|
|
1454
|
-
* Centralizing the discriminator here means there is exactly ONE
|
|
1455
|
-
* definition of "stale" for the whole session — no divergent duplicate
|
|
1456
|
-
* heuristic re-implemented per call site.
|
|
1457
|
-
*/
|
|
1458
|
-
isStaleResolvedApproval(modal: { message: string; buttons: string[] } | null, snap: ApprovalSignatureSnapshot): boolean {
|
|
1459
|
-
if (!modal) return false;
|
|
1460
|
-
const normalizedMessage = typeof modal.message === 'string' ? modal.message.trim() : '';
|
|
1461
|
-
if (!normalizedMessage) return false;
|
|
1462
|
-
return this.lastApprovalResolvedAt > 0
|
|
1463
|
-
&& normalizedMessage === this.lastResolvedModalMessage
|
|
1464
|
-
&& this.computeApprovalContentSignature(snap) === this.lastApprovalResolvedContentSignature;
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
/** Clear all resolve-time approval bookkeeping (message, timestamp, content
|
|
1468
|
-
* signature) — called at turn/session boundaries so a next-turn or
|
|
1469
|
-
* next-session approval is never compared against stale prior state. */
|
|
1470
|
-
private clearApprovalResolutionMemory(): void {
|
|
1471
|
-
this.lastApprovalResolvedAt = 0;
|
|
1472
|
-
this.lastResolvedModalMessage = '';
|
|
1473
|
-
this.lastApprovalResolvedContentSignature = '';
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
1259
|
/**
|
|
1477
1260
|
* Schedule one more settled evaluation while pinned to `waiting_approval`
|
|
1478
1261
|
* with no actionable modal. The settled FSM normally only re-runs on new
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
compactPromptText,
|
|
40
40
|
estimatePromptDisplayLines,
|
|
41
41
|
extractPromptRetrySnippet,
|
|
42
|
-
isPurePtyTranscriptProvider,
|
|
43
42
|
listCliScriptNames,
|
|
44
43
|
normalizePromptText,
|
|
45
44
|
normalizeScreenSnapshot,
|
|
@@ -457,7 +456,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
457
456
|
* provider-owned) so no other provider's turn-scoped parse changes.
|
|
458
457
|
*/
|
|
459
458
|
private parsesFullPtyTranscriptFromBuffer(): boolean {
|
|
460
|
-
|
|
459
|
+
if (this.providerOwnsTranscript()) return false;
|
|
460
|
+
// nativeHistory is a top-level provider field not surfaced on
|
|
461
|
+
// CliProviderModule; read it via the same structural cast used for `tui`.
|
|
462
|
+
if ((this.provider as { nativeHistory?: unknown }).nativeHistory) return false;
|
|
463
|
+
const transcriptPty = (this.provider.tui as { transcriptPty?: { scope?: unknown } } | undefined)?.transcriptPty;
|
|
464
|
+
return transcriptPty?.scope === 'buffer';
|
|
461
465
|
}
|
|
462
466
|
|
|
463
467
|
/**
|
|
@@ -703,10 +707,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
703
707
|
}
|
|
704
708
|
});
|
|
705
709
|
|
|
706
|
-
this.ptyProcess.onExit(({ exitCode
|
|
707
|
-
|
|
708
|
-
// otherwise unreported) is logged as "unknown", never as exit 0.
|
|
709
|
-
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode === null || exitCode === undefined ? 'unknown' : exitCode}${signal ? ` (signal ${signal})` : ''}`);
|
|
710
|
+
this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
711
|
+
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
|
|
710
712
|
this.flushPendingOutputParse();
|
|
711
713
|
this.ptyProcess = null;
|
|
712
714
|
this.engine.onPtyExit();
|
|
@@ -1070,19 +1072,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1070
1072
|
|
|
1071
1073
|
getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
|
|
1072
1074
|
const allowParse = options.allowParse !== false;
|
|
1073
|
-
|
|
1074
|
-
// (fix: kimi startup-gate stale-approval bypass) startupParseGate can
|
|
1075
|
-
// still be open by the time a real approval is requested AND resolved
|
|
1076
|
-
// — this ad-hoc parse of recentOutputBuffer runs independently of the
|
|
1077
|
-
// settled-eval loop and previously had no staleness protection at all,
|
|
1078
|
-
// so it kept re-surfacing the identical already-resolved modal for as
|
|
1079
|
-
// long as its text remained anywhere in the rolling buffer, bypassing
|
|
1080
|
-
// engine.activeModal (which resolveModal() had already correctly
|
|
1081
|
-
// cleared). Reuse the SAME discriminator the settle loop uses instead
|
|
1082
|
-
// of inventing a second one here.
|
|
1083
|
-
if (startupModal && this.engine.isStaleResolvedApproval(startupModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
|
|
1084
|
-
startupModal = null;
|
|
1085
|
-
}
|
|
1075
|
+
const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
1086
1076
|
const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
|
|
1087
1077
|
? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
|
|
1088
1078
|
: null;
|
|
@@ -1189,20 +1179,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1189
1179
|
// and let a later evaluate find the complete modal.
|
|
1190
1180
|
const buttonsOk = liveModal && Array.isArray(liveModal.buttons)
|
|
1191
1181
|
&& liveModal.buttons.some((b: any) => typeof b === 'string' && b.trim());
|
|
1192
|
-
|
|
1193
|
-
// NOT gated by startupParseGate — it runs on every getStatus()
|
|
1194
|
-
// call (including the periodic background status heartbeat)
|
|
1195
|
-
// for as long as isWaitingForResponse stays true, which for a
|
|
1196
|
-
// provider that keeps "thinking" after the approved tool call
|
|
1197
|
-
// can be the whole rest of the turn. Its own unguarded re-parse
|
|
1198
|
-
// of recentOutputBuffer/terminalScreen previously kept
|
|
1199
|
-
// re-writing engine.activeModal directly — bypassing setStatus
|
|
1200
|
-
// (so rawStatus never even flipped) and bypassing
|
|
1201
|
-
// applyWaitingApproval's staleness guard entirely, silently
|
|
1202
|
-
// re-corrupting the engine's own state moments after
|
|
1203
|
-
// resolveModal() had correctly cleared it. Reuse the same
|
|
1204
|
-
// engine-owned discriminator here too.
|
|
1205
|
-
if (liveModal && buttonsOk && !this.engine.isStaleResolvedApproval(liveModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
|
|
1182
|
+
if (liveModal && buttonsOk) {
|
|
1206
1183
|
effectiveModal = liveModal;
|
|
1207
1184
|
if (!this.engine.activeModal) this.engine.activeModal = liveModal;
|
|
1208
1185
|
}
|
|
@@ -2497,14 +2474,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2497
2474
|
|
|
2498
2475
|
getDebugState(): Record<string, any> {
|
|
2499
2476
|
const screenText = sanitizeTerminalText(this.terminalScreen.getText());
|
|
2500
|
-
|
|
2501
|
-
// (fix: kimi startup-gate stale-approval bypass) See the matching
|
|
2502
|
-
// comment in getStatus() — this ad-hoc startup-gate parse bypassed
|
|
2503
|
-
// engine.activeModal's staleness protection entirely. Reuse the same
|
|
2504
|
-
// engine-owned discriminator rather than duplicating it here.
|
|
2505
|
-
if (startupModal && this.engine.isStaleResolvedApproval(startupModal, { screenText: this.terminalScreen.getText(), accumulatedBuffer: this.accumulatedBuffer })) {
|
|
2506
|
-
startupModal = null;
|
|
2507
|
-
}
|
|
2477
|
+
const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
2508
2478
|
const startupDetectedStatus = this.startupParseGate && !startupModal
|
|
2509
2479
|
? this.runDetectStatus(this.recentOutputBuffer || screenText)
|
|
2510
2480
|
: null;
|
|
@@ -254,47 +254,6 @@ export interface CliProviderModule {
|
|
|
254
254
|
_versionWarning?: string | null;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
/**
|
|
258
|
-
* PURE-PTY TRANSCRIPT CLASS predicate (kimi and kin).
|
|
259
|
-
*
|
|
260
|
-
* A provider is "pure-PTY full-buffer" when it reconstructs its ENTIRE transcript
|
|
261
|
-
* from the rendered PTY buffer on every read and has NO alternate transcript
|
|
262
|
-
* source:
|
|
263
|
-
* - transcriptAuthority !== 'provider' (the daemon PTY parser owns the transcript,
|
|
264
|
-
* not a provider-side canonical source)
|
|
265
|
-
* - NO nativeHistory (no on-disk / native-source history to fall back to)
|
|
266
|
-
* - tui.transcriptPty.scope === 'buffer' (the parser walks the full rendered buffer)
|
|
267
|
-
*
|
|
268
|
-
* This class is invisible to two provider-authority-keyed code paths that other
|
|
269
|
-
* providers rely on for mesh completion semantics:
|
|
270
|
-
* 1. CliStateEngine.onTurnStarted only promotes to 'generating' for
|
|
271
|
-
* transcriptAuthority==='provider' providers — so a pure-PTY session that
|
|
272
|
-
* submits a prompt while already idle collapses idle→idle, the
|
|
273
|
-
* generating→idle edge never occurs, and agent:generating_completed is never
|
|
274
|
-
* emitted (the coordinator ledger leaves the task 'assigned' forever).
|
|
275
|
-
* 2. checkMeshWorkerStall's native-transcript completion reconcile is gated on
|
|
276
|
-
* the native-source shape, so a finished pure-PTY worker's static idle is
|
|
277
|
-
* misread as monitor:no_progress (a false task_stalled).
|
|
278
|
-
*
|
|
279
|
-
* The runtime capability, NOT any single spec field value, is authoritative: a
|
|
280
|
-
* given kimi manifest checkout may declare nativeHistory/transcriptAuthority, but
|
|
281
|
-
* the live-loaded pure-PTY session has none. Callers that only hold a
|
|
282
|
-
* CliProviderModule (the engine) share this exact predicate with the adapter
|
|
283
|
-
* (parsesFullPtyTranscriptFromBuffer) so the two never drift.
|
|
284
|
-
*/
|
|
285
|
-
export function isPurePtyTranscriptProvider(provider: {
|
|
286
|
-
transcriptAuthority?: 'provider' | 'daemon';
|
|
287
|
-
nativeHistory?: unknown;
|
|
288
|
-
tui?: Record<string, unknown>;
|
|
289
|
-
}): boolean {
|
|
290
|
-
if (provider.transcriptAuthority === 'provider') return false;
|
|
291
|
-
// nativeHistory is a top-level provider field not surfaced on
|
|
292
|
-
// CliProviderModule; read it via the same structural shape the adapter uses.
|
|
293
|
-
if (provider.nativeHistory) return false;
|
|
294
|
-
const transcriptPty = (provider.tui as { transcriptPty?: { scope?: unknown } } | undefined)?.transcriptPty;
|
|
295
|
-
return transcriptPty?.scope === 'buffer';
|
|
296
|
-
}
|
|
297
|
-
|
|
298
257
|
function stripAnsi(str: string): string {
|
|
299
258
|
// eslint-disable-next-line no-control-regex
|
|
300
259
|
return str
|
|
@@ -37,8 +37,7 @@ export interface PtyRuntimeTransport {
|
|
|
37
37
|
getMetadata?(): PtyRuntimeMetadata | null;
|
|
38
38
|
onData(callback: (data: string) => void): void;
|
|
39
39
|
onExit(callback: (info: {
|
|
40
|
-
exitCode: number
|
|
41
|
-
signal?: number | null;
|
|
40
|
+
exitCode: number;
|
|
42
41
|
}) => void): void;
|
|
43
42
|
}
|
|
44
43
|
export interface PtyTransportFactory {
|
|
@@ -62,7 +62,7 @@ export interface PtyRuntimeTransport {
|
|
|
62
62
|
updateMeta?(meta: Record<string, unknown>, replace?: boolean): void;
|
|
63
63
|
getMetadata?(): PtyRuntimeMetadata | null;
|
|
64
64
|
onData(callback: (data: string) => void): void;
|
|
65
|
-
onExit(callback: (info: { exitCode: number
|
|
65
|
+
onExit(callback: (info: { exitCode: number }) => void): void;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface PtyTransportFactory {
|
|
@@ -42,7 +42,7 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
42
42
|
|
|
43
43
|
private readonly client: SessionHostClient;
|
|
44
44
|
private readonly dataCallbacks = new Set<(data: string) => void>();
|
|
45
|
-
private readonly exitCallbacks = new Set<(info: { exitCode: number
|
|
45
|
+
private readonly exitCallbacks = new Set<(info: { exitCode: number }) => void>();
|
|
46
46
|
private readonly pendingOutput: string[] = [];
|
|
47
47
|
private operationChain = Promise.resolve();
|
|
48
48
|
private unsubscribe: (() => void) | null = null;
|
|
@@ -79,7 +79,7 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
onExit(callback: (info: { exitCode: number
|
|
82
|
+
onExit(callback: (info: { exitCode: number }) => void): void {
|
|
83
83
|
this.exitCallbacks.add(callback);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -361,13 +361,8 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
363
|
if (event.type === 'session_exit') {
|
|
364
|
-
// Preserve the nullable/unknown exitCode and signal exactly as the
|
|
365
|
-
// session host reported them — never collapse null to 0, which would
|
|
366
|
-
// make a signal-terminated process indistinguishable from a clean exit.
|
|
367
|
-
const exitCode = typeof event.exitCode === 'number' ? event.exitCode : null;
|
|
368
|
-
const signal = typeof event.signal === 'number' ? event.signal : null;
|
|
369
364
|
for (const callback of this.exitCallbacks) {
|
|
370
|
-
callback({ exitCode
|
|
365
|
+
callback({ exitCode: event.exitCode ?? 0 });
|
|
371
366
|
}
|
|
372
367
|
void this.closeClient(false);
|
|
373
368
|
}
|