@adhdev/daemon-core 0.9.82-rc.160 → 0.9.82-rc.162
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 +14 -1
- package/dist/commands/mesh-coordinator.d.ts +72 -1
- package/dist/config/chat-history.d.ts +2 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4924 -1411
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4976 -1476
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +35 -1
- package/dist/providers/cli-provider-instance.d.ts +1 -1
- package/dist/providers/contracts.d.ts +48 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/claude-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/codex-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/dispatcher.d.ts +24 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +2 -0
- package/dist/providers/spec/adapter.d.ts +56 -0
- package/dist/providers/spec/cli-adapter.d.ts +76 -0
- package/dist/providers/spec/driver.d.ts +148 -0
- package/dist/providers/spec/evaluator.d.ts +47 -0
- package/dist/providers/spec/loader.d.ts +14 -0
- package/dist/providers/spec/native-history-executor.d.ts +39 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +507 -0
- package/dist/providers/spec/types.d.ts +211 -0
- package/dist/repo-mesh-types.d.ts +33 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/package.json +2 -1
- package/src/cli-adapter-types.ts +15 -1
- package/src/commands/chat-commands.ts +150 -12
- package/src/commands/cli-manager.ts +11 -0
- package/src/commands/mesh-coordinator.ts +235 -1
- package/src/commands/router.ts +238 -50
- package/src/config/chat-history.ts +11 -3
- package/src/config/mesh-config.ts +16 -1
- package/src/index.ts +19 -0
- package/src/mesh/coordinator-prompt.ts +164 -8
- package/src/mesh/coordinator-registry.ts +50 -4
- package/src/providers/cli-provider-instance.ts +8 -3
- package/src/providers/contracts.ts +53 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +2 -2
- package/src/providers/native-history/claude-cli-transcript.ts +1 -1
- package/src/providers/native-history/codex-cli-transcript.ts +1 -1
- package/src/providers/native-history/dispatcher.ts +227 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +7 -0
- package/src/providers/provider-loader.ts +126 -3
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +13 -0
- package/src/providers/spec/adapter.ts +168 -0
- package/src/providers/spec/cli-adapter.ts +318 -0
- package/src/providers/spec/driver.ts +498 -0
- package/src/providers/spec/evaluator.ts +268 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +612 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +507 -0
- package/src/providers/spec/schema.json +210 -0
- package/src/providers/spec/types.ts +230 -0
- package/src/repo-mesh-types.ts +33 -1
- package/src/sessions/registry.ts +3 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
export type Size = number | string;
|
|
2
|
+
export interface Section {
|
|
3
|
+
id: string;
|
|
4
|
+
from_top?: Size;
|
|
5
|
+
from_bottom?: Size;
|
|
6
|
+
until?: {
|
|
7
|
+
section: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface SectionRegex {
|
|
11
|
+
section?: string;
|
|
12
|
+
regex: string;
|
|
13
|
+
flags?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SectionPattern {
|
|
16
|
+
section?: string;
|
|
17
|
+
pattern: string;
|
|
18
|
+
flags?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ModalButtonsRule {
|
|
21
|
+
section?: string;
|
|
22
|
+
pattern: string;
|
|
23
|
+
flags?: string;
|
|
24
|
+
key_for_index: string;
|
|
25
|
+
min_count?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SpecState {
|
|
28
|
+
id: string;
|
|
29
|
+
label: string;
|
|
30
|
+
when: SectionRegex;
|
|
31
|
+
extract_title?: SectionRegex;
|
|
32
|
+
modal_buttons?: ModalButtonsRule;
|
|
33
|
+
}
|
|
34
|
+
export type ControlAction = {
|
|
35
|
+
type: 'send_keys';
|
|
36
|
+
keys: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'open_picker';
|
|
39
|
+
trigger_keys: string;
|
|
40
|
+
wait_for: SectionRegex;
|
|
41
|
+
extract_choices: SectionPattern;
|
|
42
|
+
submit_key: string;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'attach_image';
|
|
45
|
+
method: 'tempfile_then_keys';
|
|
46
|
+
keys_template: string;
|
|
47
|
+
};
|
|
48
|
+
export interface Control {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
visible_when_state?: string[];
|
|
52
|
+
action: ControlAction;
|
|
53
|
+
}
|
|
54
|
+
export interface NotificationRule {
|
|
55
|
+
id: string;
|
|
56
|
+
when_state: string;
|
|
57
|
+
title: string;
|
|
58
|
+
body?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface DelegateTrigger {
|
|
61
|
+
id: string;
|
|
62
|
+
when_state: string;
|
|
63
|
+
after_duration_ms?: number;
|
|
64
|
+
task_template: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Native history config — three modes, picked by which fields are present:
|
|
68
|
+
*
|
|
69
|
+
* 1. `reader`: built-in reader id (claude-cli / codex-cli / antigravity-cli / hermes-cli).
|
|
70
|
+
* Backwards-compatible path used by the four shipped providers.
|
|
71
|
+
*
|
|
72
|
+
* 2. `source`: declarative source descriptor. The daemon's spec native-history
|
|
73
|
+
* executor reads the on-disk store (jsonl file / sqlite db) directly using
|
|
74
|
+
* paths + jsonpath maps from this block. New providers should prefer this
|
|
75
|
+
* mode — no daemon changes required.
|
|
76
|
+
*
|
|
77
|
+
* 3. `override_path`: relative path to a provider-supplied reader module
|
|
78
|
+
* (e.g. "./native_history/index.js"). Escape hatch for formats too
|
|
79
|
+
* exotic for the declarative executor. The module must default-export
|
|
80
|
+
* a function with the signature
|
|
81
|
+
* (input: NativeHistoryInput) => NativeHistoryResult | null
|
|
82
|
+
* and runs under provider-script-root sandboxing (same gate as
|
|
83
|
+
* scripts.js).
|
|
84
|
+
*
|
|
85
|
+
* The three modes are mutually exclusive — set exactly one. Loader rejects
|
|
86
|
+
* specs that set zero or more than one.
|
|
87
|
+
*/
|
|
88
|
+
export interface NativeHistoryConfig {
|
|
89
|
+
reader?: 'claude-cli' | 'codex-cli' | 'antigravity-cli' | 'hermes-cli';
|
|
90
|
+
source?: NativeHistorySource;
|
|
91
|
+
override_path?: string;
|
|
92
|
+
}
|
|
93
|
+
export type NativeHistorySource = NativeHistoryJsonlSource | NativeHistorySqliteSource;
|
|
94
|
+
/**
|
|
95
|
+
* JSONL source — each line in the resolved file is one record.
|
|
96
|
+
*
|
|
97
|
+
* `path` accepts ~/, environment variables (${HOME}), and a small set of
|
|
98
|
+
* runtime variables:
|
|
99
|
+
* {cwd} — the session's working directory, raw
|
|
100
|
+
* {cwd_dashed} — cwd with `/` replaced by `-` (claude's per-project key)
|
|
101
|
+
* {session_id} — providerSessionId, when known
|
|
102
|
+
* {yyyy} {mm} {dd} — UTC date components
|
|
103
|
+
*
|
|
104
|
+
* When `path` resolves to a directory, the daemon picks the newest file
|
|
105
|
+
* matching `file_pattern` whose mtime is inside `recent_window_ms` (default
|
|
106
|
+
* 5min) — prevents the wrong session's transcript from leaking through.
|
|
107
|
+
*/
|
|
108
|
+
export interface NativeHistoryJsonlSource {
|
|
109
|
+
kind: 'jsonl';
|
|
110
|
+
path: string;
|
|
111
|
+
file_pattern?: string;
|
|
112
|
+
recent_window_ms?: number;
|
|
113
|
+
session_id_from?: 'filename_uuid' | 'first_record';
|
|
114
|
+
session_id_path?: string;
|
|
115
|
+
message_filter?: {
|
|
116
|
+
where: string;
|
|
117
|
+
};
|
|
118
|
+
message_map: NativeHistoryMessageMap;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* SQLite source — provider runs two queries on a read-only handle: one to
|
|
122
|
+
* pick the session row (`session_query` — first row's first column is the
|
|
123
|
+
* sessionId), then one to fetch messages (`message_query` — `?` is bound
|
|
124
|
+
* to the sessionId). Output rows are projected through `message_map`.
|
|
125
|
+
*/
|
|
126
|
+
export interface NativeHistorySqliteSource {
|
|
127
|
+
kind: 'sqlite';
|
|
128
|
+
path: string;
|
|
129
|
+
session_query: string;
|
|
130
|
+
message_query: string;
|
|
131
|
+
message_map: NativeHistoryMessageMap;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Maps a source record (jsonl line object / sqlite row) to a daemon
|
|
135
|
+
* native-history message. Values are jsonpath-lite strings: `$.foo.bar`
|
|
136
|
+
* or `$.items[0].text`. Literal strings (no leading `$`) pass through.
|
|
137
|
+
*
|
|
138
|
+
* `content_strip` lets a spec drop wrapper tags that the agent injects
|
|
139
|
+
* into its own transcript (claude's `<local-command-caveat>`, agy's
|
|
140
|
+
* `<USER_REQUEST>` / `<ADDITIONAL_METADATA>` / `<USER_SETTINGS_CHANGE>`).
|
|
141
|
+
* Each entry is a tag name; the executor removes `<tag>…</tag>` segments
|
|
142
|
+
* non-greedily before the value is handed to the dashboard. After all
|
|
143
|
+
* strips, leading/trailing whitespace is trimmed. If the result is empty
|
|
144
|
+
* the message is dropped.
|
|
145
|
+
*/
|
|
146
|
+
export interface NativeHistoryMessageMap {
|
|
147
|
+
role: string;
|
|
148
|
+
content: string;
|
|
149
|
+
/**
|
|
150
|
+
* Tags whose entire `<tag>…</tag>` segment is removed from content.
|
|
151
|
+
* Use for noise wrappers the agent ships alongside the real message
|
|
152
|
+
* (claude's `<local-command-caveat>`, agy's `<ADDITIONAL_METADATA>`).
|
|
153
|
+
*/
|
|
154
|
+
content_strip?: string[];
|
|
155
|
+
/**
|
|
156
|
+
* Tags whose open/close markers are removed but inner content is kept.
|
|
157
|
+
* Use when the *real* user/assistant message is wrapped in a tag
|
|
158
|
+
* the agent uses for routing (agy wraps user input in `<USER_REQUEST>`).
|
|
159
|
+
*/
|
|
160
|
+
content_unwrap?: string[];
|
|
161
|
+
timestamp_ms?: string;
|
|
162
|
+
kind?: string;
|
|
163
|
+
}
|
|
164
|
+
export interface CliSpec {
|
|
165
|
+
$schema: 'adhdev:cli/spec@1';
|
|
166
|
+
id: string;
|
|
167
|
+
name: string;
|
|
168
|
+
binary: string;
|
|
169
|
+
spawn_args?: string[];
|
|
170
|
+
env?: Record<string, string>;
|
|
171
|
+
/**
|
|
172
|
+
* Optional version constraint this spec is authored for, e.g. ">=2.1.0".
|
|
173
|
+
* Pure metadata used by tooling/UI — the actual version-to-spec match
|
|
174
|
+
* happens in provider-loader via the manifest's compatibility array.
|
|
175
|
+
* Spec authors include this so a misrouted spec is easy to spot.
|
|
176
|
+
*/
|
|
177
|
+
cli_version_range?: string;
|
|
178
|
+
send_message: {
|
|
179
|
+
submit_key: string;
|
|
180
|
+
delay_ms_before_submit?: number;
|
|
181
|
+
delay_ms_per_char?: number;
|
|
182
|
+
};
|
|
183
|
+
layout: {
|
|
184
|
+
sections: Section[];
|
|
185
|
+
};
|
|
186
|
+
states: SpecState[];
|
|
187
|
+
default_state: string;
|
|
188
|
+
control_bar?: Control[];
|
|
189
|
+
notifications?: NotificationRule[];
|
|
190
|
+
delegate?: DelegateTrigger[];
|
|
191
|
+
native_history?: NativeHistoryConfig;
|
|
192
|
+
/**
|
|
193
|
+
* Per-spec debounce knobs. Defaults are conservative (busy_hold_ms
|
|
194
|
+
* 6000) and live in SpecDriver. Spec authors override here when a
|
|
195
|
+
* particular CLI flickers slower/faster than the default.
|
|
196
|
+
*/
|
|
197
|
+
debounce?: {
|
|
198
|
+
/** Min time to stay in busy after the evaluator last reported it.
|
|
199
|
+
* Absorbs per-frame flicker in TUIs that stream output through
|
|
200
|
+
* the same region as the spinner. */
|
|
201
|
+
busy_hold_ms?: number;
|
|
202
|
+
/** Min time after start() before a send_message is allowed to
|
|
203
|
+
* reach the PTY. Banner paints + auth flows + skill listings
|
|
204
|
+
* can keep the agent unable to accept input for several seconds
|
|
205
|
+
* even though the idle regex matches a transient empty prompt.
|
|
206
|
+
* Send_messages issued before this window are queued and drained
|
|
207
|
+
* once the window passes and an idle state has actually been
|
|
208
|
+
* observed. */
|
|
209
|
+
startup_grace_ms?: number;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -202,7 +202,28 @@ export interface RepoMeshCoordinatorConfig {
|
|
|
202
202
|
providerType?: string;
|
|
203
203
|
/** Preferred node to run coordinator on (null = auto) */
|
|
204
204
|
preferredNodeId?: string;
|
|
205
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Full mesh-level override for the coordinator system prompt. When set,
|
|
207
|
+
* replaces the daemon's rendered default and any user-file override
|
|
208
|
+
* (~/.adhdev/coordinator-prompts/<cli>.md). The per-launch
|
|
209
|
+
* extraSystemPrompt still composes on top — it always lands last as
|
|
210
|
+
* Additional Context. Supports the same {{placeholders}} the daemon's
|
|
211
|
+
* default template uses ({{meshName}}, {{repo}}, {{nodes}}, …).
|
|
212
|
+
*/
|
|
213
|
+
systemPromptOverride?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Mesh-level append. Composes after whichever base prompt won
|
|
216
|
+
* (override → user-file override → daemon default). Use this when you
|
|
217
|
+
* want extra rules for THIS mesh but otherwise the standard prompt is
|
|
218
|
+
* fine. Stacks with the user-file append (`<cli>.append.md`) — both
|
|
219
|
+
* apply if both are set.
|
|
220
|
+
*/
|
|
221
|
+
systemPromptAppend?: string;
|
|
222
|
+
/**
|
|
223
|
+
* @deprecated Use systemPromptAppend. Kept as a fallback alias so
|
|
224
|
+
* existing meshes.json files keep working without a migration step;
|
|
225
|
+
* the daemon prefers systemPromptAppend when both are present.
|
|
226
|
+
*/
|
|
206
227
|
systemPromptSuffix?: string;
|
|
207
228
|
}
|
|
208
229
|
/**
|
|
@@ -234,6 +255,15 @@ export interface LocalMeshNodeEntry {
|
|
|
234
255
|
machineId?: string;
|
|
235
256
|
userOverrides: Partial<RepoMeshNodeCapabilities>;
|
|
236
257
|
policy: RepoMeshNodePolicy;
|
|
258
|
+
/**
|
|
259
|
+
* Per-node instruction surfaced in the coordinator prompt so the LLM
|
|
260
|
+
* knows what each node is for (e.g. "this is the staging mirror — run
|
|
261
|
+
* only smoke tests here", or "use opus on this node, sonnet elsewhere").
|
|
262
|
+
* Empty/missing: omitted silently from the rendered prompt, no rule
|
|
263
|
+
* line about it gets added. The coordinator forwards/honors it when
|
|
264
|
+
* delegating; we don't enforce it at the daemon level.
|
|
265
|
+
*/
|
|
266
|
+
systemPrompt?: string;
|
|
237
267
|
/** For single-machine mesh: same daemon, different worktree */
|
|
238
268
|
isLocalWorktree?: boolean;
|
|
239
269
|
/** Branch this worktree tracks (set when created via clone_mesh_node) */
|
|
@@ -305,6 +335,8 @@ export interface RepoMeshNodeStatus {
|
|
|
305
335
|
machineStatus?: string;
|
|
306
336
|
isLocalWorktree?: boolean;
|
|
307
337
|
worktreeBranch?: string;
|
|
338
|
+
/** Mirrored from LocalMeshNodeEntry.systemPrompt for coordinator-prompt rendering. */
|
|
339
|
+
systemPrompt?: string;
|
|
308
340
|
health: RepoMeshNodeHealth;
|
|
309
341
|
git?: GitRepoStatus;
|
|
310
342
|
/**
|
|
@@ -7,6 +7,9 @@ export interface SessionRuntimeTarget {
|
|
|
7
7
|
cdpManagerKey?: string;
|
|
8
8
|
adapterKey?: string;
|
|
9
9
|
instanceKey?: string;
|
|
10
|
+
/** Wall clock at register time. native-history readers use it as a
|
|
11
|
+
* cutoff so a fresh session can't show records from a prior one. */
|
|
12
|
+
spawnedAtMs?: number;
|
|
10
13
|
}
|
|
11
14
|
export declare class SessionRegistry {
|
|
12
15
|
private readonly bySessionId;
|
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.162",
|
|
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",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@adhdev/session-host-core": "*",
|
|
50
50
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
51
|
+
"@xterm/headless": "^6.0.0",
|
|
51
52
|
"@xterm/xterm": "^6.0.0",
|
|
52
53
|
"ajv": "^8.20.0",
|
|
53
54
|
"ajv-formats": "^3.0.1",
|
package/src/cli-adapter-types.ts
CHANGED
|
@@ -13,6 +13,9 @@ export interface CliAdapterStatus {
|
|
|
13
13
|
message: string;
|
|
14
14
|
buttons: string[];
|
|
15
15
|
} | null;
|
|
16
|
+
providerSessionId?: string;
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
errorReason?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export interface AcpAdapterHandle {
|
|
@@ -40,7 +43,7 @@ export interface CliAdapter {
|
|
|
40
43
|
spawn(): Promise<void>;
|
|
41
44
|
sendMessage(text: string, options?: { force?: boolean }): Promise<void>;
|
|
42
45
|
forceSendMessage?(text: string): Promise<void>;
|
|
43
|
-
getStatus(): CliAdapterStatus;
|
|
46
|
+
getStatus(options?: { allowParse?: boolean }): CliAdapterStatus;
|
|
44
47
|
getScriptParsedStatus?(): unknown;
|
|
45
48
|
getDebugSnapshot?(): unknown;
|
|
46
49
|
invokeScript?(scriptName: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
@@ -63,4 +66,15 @@ export interface CliAdapter {
|
|
|
63
66
|
setOnPtyData?(callback: (data: string) => void): void;
|
|
64
67
|
writeRaw?(data: string): void;
|
|
65
68
|
resize?(cols: number, rows: number): void;
|
|
69
|
+
// ── Runtime metadata used by CliProviderInstance for session tracking ──
|
|
70
|
+
getRuntimeMetadata?(): unknown;
|
|
71
|
+
updateRuntimeMeta?(meta: Record<string, unknown>): void;
|
|
72
|
+
refreshProviderDefinition?(provider: unknown): void;
|
|
73
|
+
// ── Optional auxiliary fields some daemon paths read off the status ──
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CliAdapterStatusOptional {
|
|
77
|
+
providerSessionId?: string;
|
|
78
|
+
errorMessage?: string;
|
|
79
|
+
errorReason?: string;
|
|
66
80
|
}
|
|
@@ -15,6 +15,7 @@ import { validateReadChatResultPayload } from '../providers/read-chat-contract.j
|
|
|
15
15
|
import { pickApprovalButton } from '../providers/approval-utils.js';
|
|
16
16
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
17
17
|
import { isNativeSourceCanonicalHistory, readChatHistory, readProviderChatHistory } from '../config/chat-history.js';
|
|
18
|
+
import { getCoordinatorForSession } from '../mesh/coordinator-registry.js';
|
|
18
19
|
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
19
20
|
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
20
21
|
import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
|
|
@@ -324,6 +325,82 @@ function shouldPreserveNativeIdentity(providerType: string, sessionId: string, m
|
|
|
324
325
|
return true;
|
|
325
326
|
}
|
|
326
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Drop the synthetic "user" message some CLIs surface in their native
|
|
330
|
+
* transcript when the daemon injects a coordinator system prompt
|
|
331
|
+
* (codex puts the AGENTS.md / developer_instructions block in as
|
|
332
|
+
* role=user; agy/claude/hermes have similar artifacts). The user can
|
|
333
|
+
* opt back into seeing it via the provider setting
|
|
334
|
+
* `showCoordinatorSystemPrompt`. Default is off — the prompt is still
|
|
335
|
+
* fully visible from the chat-header ⓘ "Session info" dialog.
|
|
336
|
+
*
|
|
337
|
+
* Matching rules:
|
|
338
|
+
* 1. Setting must be off (default).
|
|
339
|
+
* 2. There must be a registered coordinator entry for the session.
|
|
340
|
+
* 3. The candidate message is filtered when its role is user OR
|
|
341
|
+
* system and its content either contains the prompt body verbatim,
|
|
342
|
+
* OR contains the well-known coordinator marker
|
|
343
|
+
* `adhdev-mesh-coordinator-prompt`. The marker covers context-file
|
|
344
|
+
* cases (agy AGENTS.md / gemini GEMINI.md) where the CLI may wrap
|
|
345
|
+
* its own preamble around our block. Verbatim-content covers
|
|
346
|
+
* codex's developer_instructions echo.
|
|
347
|
+
*
|
|
348
|
+
* Returns the messages array unchanged when none of the rules match,
|
|
349
|
+
* so this is safe to apply unconditionally to every read_chat result.
|
|
350
|
+
*/
|
|
351
|
+
function maybeHideCoordinatorPromptMessage(
|
|
352
|
+
h: CommandHelpers,
|
|
353
|
+
providerType: string,
|
|
354
|
+
sessionId: string | undefined,
|
|
355
|
+
messages: ChatMessage[],
|
|
356
|
+
): ChatMessage[] {
|
|
357
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
358
|
+
if (!sessionId) return messages;
|
|
359
|
+
const loader = h.ctx?.providerLoader;
|
|
360
|
+
if (!loader) return messages;
|
|
361
|
+
let showSetting: unknown = undefined;
|
|
362
|
+
try {
|
|
363
|
+
showSetting = (loader as any).getSettingValue?.(providerType, 'showCoordinatorSystemPrompt');
|
|
364
|
+
} catch { /* unknown setting key for this provider — fall through */ }
|
|
365
|
+
if (showSetting === true) return messages;
|
|
366
|
+
const coord = getCoordinatorForSession(sessionId);
|
|
367
|
+
if (!coord) return messages;
|
|
368
|
+
const promptBody = typeof coord.systemPrompt === 'string' ? coord.systemPrompt : '';
|
|
369
|
+
const MARKER = 'adhdev-mesh-coordinator-prompt';
|
|
370
|
+
const filtered = messages.filter(m => {
|
|
371
|
+
const role = String((m as any)?.role || '').toLowerCase();
|
|
372
|
+
if (role !== 'user' && role !== 'system') return true;
|
|
373
|
+
const content = flattenContent((m as any)?.content);
|
|
374
|
+
if (!content) return true;
|
|
375
|
+
if (content.includes(MARKER)) return false;
|
|
376
|
+
if (promptBody && content.includes(promptBody.slice(0, Math.min(400, promptBody.length)))) return false;
|
|
377
|
+
return true;
|
|
378
|
+
});
|
|
379
|
+
if (filtered.length !== messages.length) {
|
|
380
|
+
LOG.debug('ChatFilter', `[${providerType}] hid ${messages.length - filtered.length} coordinator-prompt message(s) from ${sessionId}`);
|
|
381
|
+
}
|
|
382
|
+
return filtered;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Convenience wrapper used at every native-history call site: normalize +
|
|
387
|
+
* conditionally drop the coordinator system-prompt message. Avoids
|
|
388
|
+
* duplicating the filter at four read_chat code paths.
|
|
389
|
+
*/
|
|
390
|
+
function normalizeAndFilterNativeHistory(
|
|
391
|
+
h: CommandHelpers,
|
|
392
|
+
providerType: string,
|
|
393
|
+
args: any,
|
|
394
|
+
messages: ChatMessage[],
|
|
395
|
+
nativeSessionId?: string,
|
|
396
|
+
): ChatMessage[] {
|
|
397
|
+
const normalized = normalizeNativeHistoryMessages(providerType, messages, nativeSessionId);
|
|
398
|
+
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId
|
|
399
|
+
: typeof args?.sessionId === 'string' ? args.sessionId
|
|
400
|
+
: undefined;
|
|
401
|
+
return maybeHideCoordinatorPromptMessage(h, providerType, sessionId, normalized);
|
|
402
|
+
}
|
|
403
|
+
|
|
327
404
|
function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[], nativeSessionId?: string): ChatMessage[] {
|
|
328
405
|
let turnIndex = 0;
|
|
329
406
|
return normalizeChatMessages(messages).map((message, index) => {
|
|
@@ -1003,6 +1080,41 @@ function hasSafeNativeHistoryMapping(args: {
|
|
|
1003
1080
|
// other. historySessionId (the provider-native session key) is required to
|
|
1004
1081
|
// establish ownership. hasSafeNativeHistoryMapping() enforces the same
|
|
1005
1082
|
// invariant after the read; both guards must hold for native history to be used.
|
|
1083
|
+
/**
|
|
1084
|
+
* Pull the session's spawnedAtMs out of the registry. Native-history
|
|
1085
|
+
* file pickers use it as a "files older than this can't be from this
|
|
1086
|
+
* session" floor; without it a fresh dashboard view would inherit the
|
|
1087
|
+
* previous session's transcript whenever its file happened to be the
|
|
1088
|
+
* newest match. Returns undefined when the session isn't registered
|
|
1089
|
+
* (e.g. read_chat before the live session was wired up) — the executor
|
|
1090
|
+
* treats undefined as "no floor".
|
|
1091
|
+
*/
|
|
1092
|
+
function sessionStartedAtMsFromRegistry(h: CommandHelpers, targetSessionId: string | undefined): number | undefined {
|
|
1093
|
+
const sid = typeof targetSessionId === 'string' ? targetSessionId.trim() : '';
|
|
1094
|
+
if (!sid) return undefined;
|
|
1095
|
+
const target = h.ctx?.sessionRegistry?.get?.(sid);
|
|
1096
|
+
return typeof target?.spawnedAtMs === 'number' ? target.spawnedAtMs : undefined;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Pull the env vars the daemon set when it spawned this session's CLI.
|
|
1101
|
+
* Mesh coordinator points hermes at a per-coordinator HERMES_HOME so
|
|
1102
|
+
* the native-history reader needs that override to find the right
|
|
1103
|
+
* state.db; without it the reader sees ~/.hermes/state.db and misses
|
|
1104
|
+
* every coordinator-session transcript.
|
|
1105
|
+
*
|
|
1106
|
+
* Returns undefined when no SpecCliAdapter is in play (legacy
|
|
1107
|
+
* providers / CDP) or when the adapter exposes no spawn env.
|
|
1108
|
+
*/
|
|
1109
|
+
function sessionSpawnEnvFromAdapter(h: CommandHelpers, targetSessionId: string | undefined): Record<string, string> | undefined {
|
|
1110
|
+
const adapter = getTargetedCliAdapter(h, { targetSessionId }, undefined);
|
|
1111
|
+
if (!adapter || typeof adapter.getRuntimeMetadata !== 'function') return undefined;
|
|
1112
|
+
const meta = adapter.getRuntimeMetadata() as Record<string, unknown> | undefined;
|
|
1113
|
+
const env = meta && typeof meta === 'object' ? (meta as Record<string, unknown>).spawnedEnv : undefined;
|
|
1114
|
+
return env && typeof env === 'object' ? env as Record<string, string> : undefined;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
|
|
1006
1118
|
function readCliProviderNativeHistory(agentStr: string, args: {
|
|
1007
1119
|
canonicalHistory?: ProviderModule['canonicalHistory'];
|
|
1008
1120
|
historySessionId?: string;
|
|
@@ -1013,6 +1125,8 @@ function readCliProviderNativeHistory(agentStr: string, args: {
|
|
|
1013
1125
|
historyBehavior?: ProviderModule['historyBehavior'];
|
|
1014
1126
|
scripts?: ProviderScripts;
|
|
1015
1127
|
excludeInProgressTurn?: boolean;
|
|
1128
|
+
sessionStartedAtMs?: number;
|
|
1129
|
+
envOverrides?: Record<string, string>;
|
|
1016
1130
|
}): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
|
|
1017
1131
|
if (!args.historySessionId) {
|
|
1018
1132
|
return {
|
|
@@ -1033,6 +1147,8 @@ function readCliProviderNativeHistory(agentStr: string, args: {
|
|
|
1033
1147
|
historyBehavior: args.historyBehavior,
|
|
1034
1148
|
scripts: args.scripts as any,
|
|
1035
1149
|
excludeInProgressTurn: args.excludeInProgressTurn,
|
|
1150
|
+
sessionStartedAtMs: args.sessionStartedAtMs,
|
|
1151
|
+
envOverrides: args.envOverrides,
|
|
1036
1152
|
});
|
|
1037
1153
|
// Native transcripts are keyed by provider/runtime session identity. Falling
|
|
1038
1154
|
// back to workspace makes concurrent local Codex/Hermes sessions alias each
|
|
@@ -1290,7 +1406,7 @@ function collapseAdjacentDuplicateChatMessages(messages: ChatMessage[]): ChatMes
|
|
|
1290
1406
|
return result;
|
|
1291
1407
|
}
|
|
1292
1408
|
|
|
1293
|
-
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
1409
|
+
function buildReadChatCommandResult(payload: Record<string, any>, args: any, h?: CommandHelpers): CommandResult {
|
|
1294
1410
|
let validatedPayload: Record<string, any>;
|
|
1295
1411
|
const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
|
|
1296
1412
|
? payload.debugReadChat
|
|
@@ -1304,7 +1420,23 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
1304
1420
|
return { success: false, error: error?.message || String(error) };
|
|
1305
1421
|
}
|
|
1306
1422
|
const messages = normalizeReadChatMessages(validatedPayload);
|
|
1307
|
-
|
|
1423
|
+
// Last-mile coordinator-prompt filter. Different read_chat code paths
|
|
1424
|
+
// produce the final messages array (native-history main path, codex
|
|
1425
|
+
// exact-runtime-mirror fallback, daemon-side pty-parser, etc), so
|
|
1426
|
+
// applying it here means we don't have to thread the filter through
|
|
1427
|
+
// every one. Driven by the provider setting `showCoordinatorSystemPrompt`
|
|
1428
|
+
// + the coordinator-registry entry for the target session.
|
|
1429
|
+
const sessionIdHint = typeof args?.targetSessionId === 'string' ? args.targetSessionId
|
|
1430
|
+
: typeof args?.sessionId === 'string' ? args.sessionId
|
|
1431
|
+
: '';
|
|
1432
|
+
const providerHint = typeof args?.cliType === 'string' ? args.cliType
|
|
1433
|
+
: typeof args?.providerType === 'string' ? args.providerType
|
|
1434
|
+
: typeof args?.agentType === 'string' ? args.agentType
|
|
1435
|
+
: '';
|
|
1436
|
+
const filteredMessages = h
|
|
1437
|
+
? maybeHideCoordinatorPromptMessage(h, providerHint, sessionIdHint, messages)
|
|
1438
|
+
: messages;
|
|
1439
|
+
const visibleMessages = filterUserFacingChatMessages(filteredMessages);
|
|
1308
1440
|
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
1309
1441
|
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
1310
1442
|
const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
|
|
@@ -1819,6 +1951,8 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
1819
1951
|
excludeRecentCount,
|
|
1820
1952
|
historyBehavior: provider?.historyBehavior,
|
|
1821
1953
|
scripts: provider?.scripts as any,
|
|
1954
|
+
sessionStartedAtMs: sessionStartedAtMsFromRegistry(h, args?.targetSessionId),
|
|
1955
|
+
envOverrides: sessionSpawnEnvFromAdapter(h, args?.targetSessionId),
|
|
1822
1956
|
})
|
|
1823
1957
|
: readProviderChatHistory(agentStr, {
|
|
1824
1958
|
canonicalHistory: provider?.nativeHistory,
|
|
@@ -1833,7 +1967,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
1833
1967
|
if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.nativeHistory)) {
|
|
1834
1968
|
const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1835
1969
|
const messages = Array.isArray((result as any).messages)
|
|
1836
|
-
?
|
|
1970
|
+
? normalizeAndFilterNativeHistory(h, agentStr, args, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
|
|
1837
1971
|
: [];
|
|
1838
1972
|
const historyProviderSessionId = typeof (result as any)?.providerSessionId === 'string'
|
|
1839
1973
|
? (result as any).providerSessionId
|
|
@@ -2004,6 +2138,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2004
2138
|
historyBehavior: provider?.historyBehavior,
|
|
2005
2139
|
scripts: provider?.scripts as any,
|
|
2006
2140
|
excludeInProgressTurn: returnedStatus === 'waiting_approval',
|
|
2141
|
+
sessionStartedAtMs: sessionStartedAtMsFromRegistry(h, args?.targetSessionId),
|
|
2142
|
+
envOverrides: sessionSpawnEnvFromAdapter(h, args?.targetSessionId),
|
|
2007
2143
|
});
|
|
2008
2144
|
} catch (error: any) {
|
|
2009
2145
|
nativeHistoryError = error;
|
|
@@ -2014,7 +2150,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2014
2150
|
// 2. Compute safeMapping with the same rules the v1 code used so the
|
|
2015
2151
|
// machine sees the same observation it always would have.
|
|
2016
2152
|
const nativeMessages: ChatMessage[] = nativeHistory && Array.isArray(nativeHistory.messages)
|
|
2017
|
-
?
|
|
2153
|
+
? normalizeAndFilterNativeHistory(h, agentStr, args, nativeHistory.messages as ChatMessage[], nativeHistory.providerSessionId)
|
|
2018
2154
|
: [];
|
|
2019
2155
|
const historyProviderSessionId = typeof nativeHistory?.providerSessionId === 'string'
|
|
2020
2156
|
? nativeHistory.providerSessionId
|
|
@@ -2099,7 +2235,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2099
2235
|
})
|
|
2100
2236
|
: null;
|
|
2101
2237
|
const liveWorkspaceNativeMessages = Array.isArray((liveWorkspaceNativeHistory as any)?.messages)
|
|
2102
|
-
?
|
|
2238
|
+
? normalizeAndFilterNativeHistory(h, agentStr, args, (liveWorkspaceNativeHistory as any).messages as ChatMessage[], (liveWorkspaceNativeHistory as any)?.providerSessionId)
|
|
2103
2239
|
: [];
|
|
2104
2240
|
const liveWorkspaceNativeProviderSessionId = typeof (liveWorkspaceNativeHistory as any)?.providerSessionId === 'string'
|
|
2105
2241
|
? (liveWorkspaceNativeHistory as any).providerSessionId
|
|
@@ -2214,7 +2350,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2214
2350
|
...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
|
|
2215
2351
|
...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
|
|
2216
2352
|
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
2217
|
-
}, args);
|
|
2353
|
+
}, args, h);
|
|
2218
2354
|
}
|
|
2219
2355
|
// History-only path (no adapter). Same source-decision contract as
|
|
2220
2356
|
// the adapter path above, but with no PTY messages — the machine
|
|
@@ -2240,6 +2376,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2240
2376
|
excludeRecentCount: 0,
|
|
2241
2377
|
historyBehavior: provider?.historyBehavior,
|
|
2242
2378
|
scripts: provider?.scripts as any,
|
|
2379
|
+
sessionStartedAtMs: sessionStartedAtMsFromRegistry(h, args?.targetSessionId),
|
|
2380
|
+
envOverrides: sessionSpawnEnvFromAdapter(h, args?.targetSessionId),
|
|
2243
2381
|
})
|
|
2244
2382
|
: readProviderChatHistory(agentStr, {
|
|
2245
2383
|
canonicalHistory: provider?.nativeHistory,
|
|
@@ -2253,7 +2391,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2253
2391
|
});
|
|
2254
2392
|
const lookup = (history as any)?.lookup === 'workspace' ? 'workspace' : 'session';
|
|
2255
2393
|
const historyMessages = Array.isArray((history as any)?.messages)
|
|
2256
|
-
?
|
|
2394
|
+
? normalizeAndFilterNativeHistory(h, agentStr, args, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
|
|
2257
2395
|
: [];
|
|
2258
2396
|
const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
|
|
2259
2397
|
? (history as any).providerSessionId
|
|
@@ -2307,7 +2445,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2307
2445
|
? { transcriptAuthority: (provider?.historyBehavior as any).transcriptAuthority }
|
|
2308
2446
|
: {}),
|
|
2309
2447
|
coverage: 'tail',
|
|
2310
|
-
}, args);
|
|
2448
|
+
}, args, h);
|
|
2311
2449
|
} catch (error: any) {
|
|
2312
2450
|
return { success: false, error: error?.message || `${transport} adapter not found` };
|
|
2313
2451
|
}
|
|
@@ -2347,7 +2485,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2347
2485
|
args?.targetSessionId,
|
|
2348
2486
|
historySessionId,
|
|
2349
2487
|
);
|
|
2350
|
-
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
2488
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args, h);
|
|
2351
2489
|
}
|
|
2352
2490
|
if (!extensionReadChatError) {
|
|
2353
2491
|
extensionReadChatError = 'extension read_chat returned a non-object payload';
|
|
@@ -2386,7 +2524,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2386
2524
|
messages: stream.messages || [],
|
|
2387
2525
|
status: stream.status,
|
|
2388
2526
|
agentType: stream.agentType,
|
|
2389
|
-
}, args);
|
|
2527
|
+
}, args, h);
|
|
2390
2528
|
}
|
|
2391
2529
|
}
|
|
2392
2530
|
}
|
|
@@ -2426,7 +2564,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2426
2564
|
args?.targetSessionId,
|
|
2427
2565
|
historySessionId,
|
|
2428
2566
|
);
|
|
2429
|
-
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
2567
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args, h);
|
|
2430
2568
|
}
|
|
2431
2569
|
if (!webviewReadChatError) {
|
|
2432
2570
|
webviewReadChatError = 'webview read_chat returned a non-object payload';
|
|
@@ -2476,7 +2614,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2476
2614
|
args?.targetSessionId,
|
|
2477
2615
|
historySessionId,
|
|
2478
2616
|
);
|
|
2479
|
-
return buildReadChatCommandResult(validated as Record<string, any>, args);
|
|
2617
|
+
return buildReadChatCommandResult(validated as Record<string, any>, args, h);
|
|
2480
2618
|
}
|
|
2481
2619
|
if (!ideReadChatError) {
|
|
2482
2620
|
ideReadChatError = 'ide read_chat returned a non-object payload';
|
|
@@ -638,6 +638,17 @@ export class DaemonCliManager {
|
|
|
638
638
|
transport: 'pty',
|
|
639
639
|
adapterKey: key,
|
|
640
640
|
instanceKey: key,
|
|
641
|
+
// attachExisting === true means we're restoring an already-spawned
|
|
642
|
+
// hosted runtime after a daemon restart, not starting a fresh PTY.
|
|
643
|
+
// The real spawn time is in the past and we don't have it on the
|
|
644
|
+
// restored descriptor; pinning spawnedAtMs to Date.now() in that
|
|
645
|
+
// case would push the native-history session-floor cutoff past
|
|
646
|
+
// every existing transcript file, so the agy/hermes/claude reader
|
|
647
|
+
// would return null even though the transcript on disk is fresh.
|
|
648
|
+
// 0 disables the floor for this session — recent_window_ms in the
|
|
649
|
+
// spec still bounds how far back we look. Fresh launches still
|
|
650
|
+
// get a proper floor so prior-session leak protection holds.
|
|
651
|
+
spawnedAtMs: attachExisting ? 0 : Date.now(),
|
|
641
652
|
});
|
|
642
653
|
} catch (spawnErr: any) {
|
|
643
654
|
LOG.error('CLI', `[${cliType}] Spawn failed: ${spawnErr?.message}`);
|