@aeondave/pi-persona 1.15.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/AGENTS.md +249 -0
- package/CHANGELOG.md +260 -0
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/agents/balthasar.md +32 -0
- package/agents/casper.md +33 -0
- package/agents/evidence-verifier.md +23 -0
- package/agents/melchior.md +32 -0
- package/agents/operator.md +63 -0
- package/agents/research.md +58 -0
- package/agents/reviewer.md +49 -0
- package/agents/scout.md +23 -0
- package/agents/verifier.md +21 -0
- package/assets/banner.png +0 -0
- package/assets/demo1.gif +0 -0
- package/assets/demo1.mp4 +0 -0
- package/assets/workflow.png +0 -0
- package/contracts/finding.contract.json +9 -0
- package/docs/ARCHITECTURE.md +727 -0
- package/docs/EXPERIENCE.md +96 -0
- package/docs/MONITORS.md +84 -0
- package/docs/README.md +53 -0
- package/docs/REFERENCE.md +408 -0
- package/docs/SPINE.md +152 -0
- package/docs/STRATEGIES.md +215 -0
- package/docs/TELEMETRY.md +9 -0
- package/flows/deep-review.flow.json +8 -0
- package/flows/gated-build.flow.json +9 -0
- package/package.json +106 -0
- package/personas/audit.md +29 -0
- package/personas/dev.md +52 -0
- package/personas/elite.md +256 -0
- package/personas/judge.md +32 -0
- package/personas/magi.md +41 -0
- package/personas/planner.md +37 -0
- package/personas/researcher.md +37 -0
- package/personas/swarm.md +38 -0
- package/personas/verify.md +31 -0
- package/presets/magi-rounds.preset.json +5 -0
- package/prompts/spine.md +11 -0
- package/prompts/spine.worker.md +9 -0
- package/src/agents/agent.ts +78 -0
- package/src/bridge.ts +209 -0
- package/src/bus/broker/client.ts +379 -0
- package/src/bus/broker/framing.ts +43 -0
- package/src/bus/broker/host.ts +407 -0
- package/src/bus/broker/messages.ts +87 -0
- package/src/bus/broker/paths.ts +31 -0
- package/src/bus/contact.ts +145 -0
- package/src/bus/inproc.ts +279 -0
- package/src/bus/peers.ts +114 -0
- package/src/core/brief.ts +356 -0
- package/src/core/capabilities.ts +114 -0
- package/src/core/config.ts +228 -0
- package/src/core/contract.ts +391 -0
- package/src/core/data-root.ts +260 -0
- package/src/core/discovery.ts +33 -0
- package/src/core/display-label.ts +47 -0
- package/src/core/fence.ts +151 -0
- package/src/core/format.ts +13 -0
- package/src/core/frontmatter.ts +202 -0
- package/src/core/models.ts +192 -0
- package/src/core/monitor.ts +415 -0
- package/src/core/naming.ts +42 -0
- package/src/core/nudge.ts +238 -0
- package/src/core/ownership.ts +102 -0
- package/src/core/permissions.ts +56 -0
- package/src/core/pi-compat.ts +50 -0
- package/src/core/seed.ts +770 -0
- package/src/core/session-identity.ts +89 -0
- package/src/core/time.ts +202 -0
- package/src/core/timer.ts +221 -0
- package/src/core/types.ts +28 -0
- package/src/engine/adapter.ts +257 -0
- package/src/engine/async.ts +898 -0
- package/src/engine/change-report.ts +143 -0
- package/src/engine/child.ts +532 -0
- package/src/engine/errors.ts +30 -0
- package/src/engine/fallback.ts +57 -0
- package/src/engine/handles.ts +21 -0
- package/src/engine/inproc.ts +857 -0
- package/src/engine/signals.ts +11 -0
- package/src/engine/spec-preflight.ts +33 -0
- package/src/engine/stream.ts +369 -0
- package/src/engine/worktree.ts +144 -0
- package/src/exocom/activation.ts +31 -0
- package/src/exocom/codes.ts +261 -0
- package/src/exocom/envelope.ts +180 -0
- package/src/exocom/gate.ts +6 -0
- package/src/exocom/guards.ts +45 -0
- package/src/exocom/inbound.ts +141 -0
- package/src/exocom/install.ts +970 -0
- package/src/exocom/ledger.ts +603 -0
- package/src/exocom/limits.ts +20 -0
- package/src/exocom/paths.ts +35 -0
- package/src/exocom/plane.ts +1116 -0
- package/src/exocom/registry.ts +307 -0
- package/src/exocom/scope.ts +42 -0
- package/src/exocom/status.ts +86 -0
- package/src/exocom/untrusted.ts +40 -0
- package/src/exocom/wait.ts +23 -0
- package/src/exocom/write-guard.ts +89 -0
- package/src/extension/broker-host.ts +181 -0
- package/src/extension/clock.ts +14 -0
- package/src/extension/engine.ts +321 -0
- package/src/extension/hooks.ts +796 -0
- package/src/extension/identity.ts +244 -0
- package/src/extension/shared.ts +175 -0
- package/src/extension.ts +2221 -0
- package/src/loader.ts +218 -0
- package/src/monitor/process.ts +116 -0
- package/src/monitor/session.ts +61 -0
- package/src/orchestration/flow-journal.ts +86 -0
- package/src/orchestration/flow-run.ts +301 -0
- package/src/orchestration/flow.ts +125 -0
- package/src/orchestration/judge.ts +78 -0
- package/src/orchestration/model-retry.ts +85 -0
- package/src/orchestration/parallel.ts +107 -0
- package/src/orchestration/params.ts +23 -0
- package/src/orchestration/reducers.ts +123 -0
- package/src/orchestration/render.ts +127 -0
- package/src/orchestration/roster.ts +133 -0
- package/src/orchestration/sdk.ts +326 -0
- package/src/orchestration/strategies/compete.ts +182 -0
- package/src/orchestration/strategies/council-rounds.ts +124 -0
- package/src/orchestration/strategies/critic-loop.ts +168 -0
- package/src/orchestration/strategies/debate.ts +126 -0
- package/src/orchestration/strategies/fanout.ts +20 -0
- package/src/orchestration/strategies/judge.ts +117 -0
- package/src/orchestration/strategies/magi.ts +239 -0
- package/src/orchestration/strategies/map.ts +327 -0
- package/src/orchestration/strategies/pair.ts +72 -0
- package/src/orchestration/strategies/pipeline.ts +91 -0
- package/src/orchestration/strategies/synthesize.ts +99 -0
- package/src/orchestration/strategy.ts +49 -0
- package/src/orchestration/types.ts +31 -0
- package/src/orchestration/voting.ts +126 -0
- package/src/persona/config-store.ts +215 -0
- package/src/persona/controller.ts +173 -0
- package/src/persona/gating.ts +82 -0
- package/src/persona/orchestrate.ts +83 -0
- package/src/persona/persona.ts +294 -0
- package/src/persona/spine.ts +221 -0
- package/src/persona/state.ts +28 -0
- package/src/telemetry/contract.ts +244 -0
- package/src/telemetry/paths.ts +14 -0
- package/src/telemetry/producer.ts +390 -0
- package/src/tools/council.ts +200 -0
- package/src/tools/delegate-tool.ts +637 -0
- package/src/tools/delegate.ts +661 -0
- package/src/tools/exocom-work.ts +328 -0
- package/src/tools/exocom.ts +476 -0
- package/src/tools/flow.ts +117 -0
- package/src/tools/intercom-tool.ts +292 -0
- package/src/tools/intercom.ts +181 -0
- package/src/tools/models.ts +39 -0
- package/src/tools/monitor.ts +66 -0
- package/src/tools/timer.ts +105 -0
- package/src/ui/agent-overlay.ts +526 -0
- package/src/ui/agent-tree.ts +307 -0
- package/src/ui/live-clock.ts +42 -0
- package/src/ui/model-picker.ts +160 -0
- package/src/ui/presentation.ts +170 -0
- package/src/ui/usage.ts +133 -0
- package/teams.yaml +27 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure monitor lifecycle and admission state.
|
|
3
|
+
*
|
|
4
|
+
* The process adapter is injected so this module can enforce limits, deadlines,
|
|
5
|
+
* cancellation, and event shaping without touching the operating system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type MonitorMode = "output" | "exit";
|
|
9
|
+
|
|
10
|
+
export interface MonitorSpec {
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd: string;
|
|
14
|
+
label: string;
|
|
15
|
+
mode: MonitorMode;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
maxEvents?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MonitorEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
command: string;
|
|
24
|
+
args: string[];
|
|
25
|
+
cwd: string;
|
|
26
|
+
mode: MonitorMode;
|
|
27
|
+
startedAtEpochMs: number;
|
|
28
|
+
expiresAtEpochMs: number;
|
|
29
|
+
maxEvents: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MonitorEvent {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
kind: "output" | "exit" | "error" | "timeout";
|
|
36
|
+
atEpochMs: number;
|
|
37
|
+
text: string;
|
|
38
|
+
terminal: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MonitorProcessHandlers {
|
|
42
|
+
onStdout: (chunk: string) => void;
|
|
43
|
+
onStderr: (chunk: string) => void;
|
|
44
|
+
onExit: (code: number | null, signal?: string) => void;
|
|
45
|
+
onError: (error: Error) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MonitorProcess {
|
|
49
|
+
stop: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MonitorManagerDeps {
|
|
53
|
+
now: () => number;
|
|
54
|
+
setTimer: (fn: () => void, ms: number) => unknown;
|
|
55
|
+
clearTimer: (handle: unknown) => void;
|
|
56
|
+
start: (spec: MonitorSpec, handlers: MonitorProcessHandlers) => MonitorProcess;
|
|
57
|
+
onEvent: (event: MonitorEvent) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type MonitorArmResult =
|
|
61
|
+
| { ok: true; entry: MonitorEntry }
|
|
62
|
+
| { ok: false; error: string };
|
|
63
|
+
|
|
64
|
+
export const MONITOR_MAX_ACTIVE = 8;
|
|
65
|
+
export const MONITOR_DEFAULT_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
66
|
+
export const MONITOR_MIN_TIMEOUT_MS = 1_000;
|
|
67
|
+
export const MONITOR_MAX_TIMEOUT_MS = 24 * 60 * 60 * 1_000;
|
|
68
|
+
export const MONITOR_DEFAULT_MAX_EVENTS = 1;
|
|
69
|
+
export const MONITOR_MAX_EVENTS = 20;
|
|
70
|
+
export const MONITOR_MAX_LINE_CHARS = 4_000;
|
|
71
|
+
export const MONITOR_MAX_DIAGNOSTIC_CHARS = 4_000;
|
|
72
|
+
export const MONITOR_MAX_COMMAND_CHARS = 4_096;
|
|
73
|
+
export const MONITOR_MAX_ARGS = 64;
|
|
74
|
+
export const MONITOR_MAX_ARG_CHARS = 8_192;
|
|
75
|
+
export const MONITOR_MAX_ARGV_CHARS = 24_000;
|
|
76
|
+
export const MONITOR_MAX_LABEL_CHARS = 120;
|
|
77
|
+
export const MONITOR_TRUNCATION_MARKER = "…[truncated]";
|
|
78
|
+
|
|
79
|
+
interface ActiveMonitor {
|
|
80
|
+
entry: MonitorEntry;
|
|
81
|
+
handle?: unknown;
|
|
82
|
+
hasHandle: boolean;
|
|
83
|
+
starting: boolean;
|
|
84
|
+
startError?: string;
|
|
85
|
+
process?: MonitorProcess;
|
|
86
|
+
terminal: boolean;
|
|
87
|
+
stdoutBuffer: string;
|
|
88
|
+
stdoutTail: string;
|
|
89
|
+
stderrTail: string;
|
|
90
|
+
seenLines: Set<string>;
|
|
91
|
+
eventsSeen: number;
|
|
92
|
+
lineHash: number;
|
|
93
|
+
lineLength: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function appendTail(current: string, chunk: string, limit: number): string {
|
|
97
|
+
if (!chunk) return current;
|
|
98
|
+
if (current.length + chunk.length <= limit) return current + chunk;
|
|
99
|
+
const keep = Math.max(0, limit - MONITOR_TRUNCATION_MARKER.length);
|
|
100
|
+
if (chunk.length >= keep) return `${MONITOR_TRUNCATION_MARKER}${chunk.slice(-keep)}`;
|
|
101
|
+
const room = keep - chunk.length;
|
|
102
|
+
return `${MONITOR_TRUNCATION_MARKER}${current.length > room ? current.slice(-room) : current}${chunk}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function appendBoundedLine(current: string, chunk: string): string {
|
|
106
|
+
if (chunk.length === 0) return current;
|
|
107
|
+
if (current.length + chunk.length <= MONITOR_MAX_LINE_CHARS) return current + chunk;
|
|
108
|
+
const keep = MONITOR_MAX_LINE_CHARS - MONITOR_TRUNCATION_MARKER.length;
|
|
109
|
+
const prefix = current.length >= keep ? current.slice(0, keep) : current + chunk.slice(0, keep - current.length);
|
|
110
|
+
return `${prefix}${MONITOR_TRUNCATION_MARKER}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hashLine(hash: number, text: string): number {
|
|
114
|
+
let value = hash;
|
|
115
|
+
for (let i = 0; i < text.length; i++) value = Math.imul(value ^ text.charCodeAt(i), 16_777_619) >>> 0;
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function errorText(error: unknown): string {
|
|
120
|
+
return error instanceof Error ? error.message : String(error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function copyEntry(entry: MonitorEntry): MonitorEntry {
|
|
124
|
+
return { ...entry, args: [...entry.args] };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class MonitorManager {
|
|
128
|
+
private readonly deps: MonitorManagerDeps;
|
|
129
|
+
private readonly monitors = new Map<string, ActiveMonitor>();
|
|
130
|
+
private sequence = 0;
|
|
131
|
+
|
|
132
|
+
constructor(deps: MonitorManagerDeps) {
|
|
133
|
+
this.deps = deps;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
arm(request: MonitorSpec): MonitorArmResult {
|
|
137
|
+
const validation = this.validate(request);
|
|
138
|
+
if (validation) return { ok: false, error: validation };
|
|
139
|
+
if (this.monitors.size >= MONITOR_MAX_ACTIVE) {
|
|
140
|
+
return { ok: false, error: `too many active monitors (maximum ${MONITOR_MAX_ACTIVE}); cancel one before arming another.` };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const timeoutMs = request.timeoutMs ?? MONITOR_DEFAULT_TIMEOUT_MS;
|
|
144
|
+
const maxEvents = request.maxEvents ?? MONITOR_DEFAULT_MAX_EVENTS;
|
|
145
|
+
const now = this.deps.now();
|
|
146
|
+
const id = `monitor-${++this.sequence}`;
|
|
147
|
+
const entry: MonitorEntry = {
|
|
148
|
+
id,
|
|
149
|
+
label: request.label.trim(),
|
|
150
|
+
command: request.command,
|
|
151
|
+
args: [...(request.args ?? [])],
|
|
152
|
+
cwd: request.cwd,
|
|
153
|
+
mode: request.mode,
|
|
154
|
+
startedAtEpochMs: now,
|
|
155
|
+
expiresAtEpochMs: now + timeoutMs,
|
|
156
|
+
maxEvents,
|
|
157
|
+
};
|
|
158
|
+
const active: ActiveMonitor = {
|
|
159
|
+
entry,
|
|
160
|
+
hasHandle: false,
|
|
161
|
+
starting: true,
|
|
162
|
+
terminal: false,
|
|
163
|
+
stdoutBuffer: "",
|
|
164
|
+
stdoutTail: "",
|
|
165
|
+
stderrTail: "",
|
|
166
|
+
seenLines: new Set<string>(),
|
|
167
|
+
eventsSeen: 0,
|
|
168
|
+
lineHash: 2_166_136_261,
|
|
169
|
+
lineLength: 0,
|
|
170
|
+
};
|
|
171
|
+
this.monitors.set(id, active);
|
|
172
|
+
|
|
173
|
+
const handlers: MonitorProcessHandlers = {
|
|
174
|
+
onStdout: (chunk) => this.receiveStdout(id, chunk),
|
|
175
|
+
onStderr: (chunk) => this.receiveStderr(id, chunk),
|
|
176
|
+
onExit: (code, signal) => this.receiveExit(id, code, signal),
|
|
177
|
+
onError: (error) => this.receiveError(id, error),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const process = this.deps.start(
|
|
182
|
+
{
|
|
183
|
+
...request,
|
|
184
|
+
args: [...(request.args ?? [])],
|
|
185
|
+
timeoutMs,
|
|
186
|
+
maxEvents,
|
|
187
|
+
},
|
|
188
|
+
handlers,
|
|
189
|
+
);
|
|
190
|
+
active.process = process;
|
|
191
|
+
active.starting = false;
|
|
192
|
+
if (active.startError !== undefined) {
|
|
193
|
+
try {
|
|
194
|
+
process.stop();
|
|
195
|
+
} catch {
|
|
196
|
+
/* best effort; the synchronous start failure is already terminal */
|
|
197
|
+
}
|
|
198
|
+
return { ok: false, error: `could not start monitor: ${active.startError}` };
|
|
199
|
+
}
|
|
200
|
+
// A synchronous fake or spawn wrapper can finish before start() returns. In that
|
|
201
|
+
// case terminal() already removed the monitor, but the returned process still needs
|
|
202
|
+
// the cleanup call so it cannot keep producing late callbacks.
|
|
203
|
+
if (active.terminal) {
|
|
204
|
+
process.stop();
|
|
205
|
+
return { ok: true, entry: copyEntry(entry) };
|
|
206
|
+
}
|
|
207
|
+
const handle = this.deps.setTimer(() => this.timeout(id), timeoutMs);
|
|
208
|
+
if (active.terminal || this.monitors.get(id) !== active) {
|
|
209
|
+
this.deps.clearTimer(handle);
|
|
210
|
+
} else {
|
|
211
|
+
active.handle = handle;
|
|
212
|
+
active.hasHandle = true;
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this.removeWithoutEvent(active);
|
|
216
|
+
return { ok: false, error: `could not start monitor: ${errorText(error)}` };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { ok: true, entry: copyEntry(entry) };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
list(): MonitorEntry[] {
|
|
223
|
+
return [...this.monitors.values()]
|
|
224
|
+
.sort((a, b) => a.entry.startedAtEpochMs - b.entry.startedAtEpochMs || a.entry.id.localeCompare(b.entry.id))
|
|
225
|
+
.map((active) => copyEntry(active.entry));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
cancel(id: string): boolean {
|
|
229
|
+
const active = this.monitors.get(id);
|
|
230
|
+
if (!active) return false;
|
|
231
|
+
this.removeWithoutEvent(active);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
cancelAll(): void {
|
|
236
|
+
for (const active of [...this.monitors.values()]) this.removeWithoutEvent(active);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private validate(request: MonitorSpec): string | undefined {
|
|
240
|
+
if (!request || typeof request !== "object") return "monitor spec is required.";
|
|
241
|
+
if (typeof request.command !== "string" || request.command.trim().length === 0) return "monitor command must be a non-empty executable name.";
|
|
242
|
+
if (request.command.length > MONITOR_MAX_COMMAND_CHARS) return `monitor command exceeds the maximum ${MONITOR_MAX_COMMAND_CHARS} characters.`;
|
|
243
|
+
if (request.command.includes("\0")) return "monitor command cannot contain NUL bytes.";
|
|
244
|
+
if (typeof request.cwd !== "string" || request.cwd.trim().length === 0) return "monitor cwd must be a non-empty directory.";
|
|
245
|
+
if (request.cwd.includes("\0")) return "monitor cwd cannot contain NUL bytes.";
|
|
246
|
+
if (typeof request.label !== "string" || request.label.trim().length === 0) return "monitor label must be non-empty.";
|
|
247
|
+
if (request.label.length > MONITOR_MAX_LABEL_CHARS) return `monitor label exceeds the maximum ${MONITOR_MAX_LABEL_CHARS} characters.`;
|
|
248
|
+
if (request.label.includes("\0")) return "monitor label cannot contain NUL bytes.";
|
|
249
|
+
if (request.mode !== "output" && request.mode !== "exit") return "monitor mode must be `output` or `exit`.";
|
|
250
|
+
if (request.args !== undefined && (!Array.isArray(request.args) || request.args.some((arg) => typeof arg !== "string"))) return "monitor args must be an array of strings.";
|
|
251
|
+
if (request.args !== undefined && request.args.length > MONITOR_MAX_ARGS) return `monitor args exceeds the maximum ${MONITOR_MAX_ARGS} items.`;
|
|
252
|
+
if (request.args?.some((arg) => arg.includes("\0"))) return "monitor args cannot contain NUL bytes.";
|
|
253
|
+
if (request.args?.some((arg) => arg.length > MONITOR_MAX_ARG_CHARS)) return `a monitor argument exceeds the maximum ${MONITOR_MAX_ARG_CHARS} characters.`;
|
|
254
|
+
const argvChars = (request.args ?? []).reduce((total, arg) => total + arg.length, 0);
|
|
255
|
+
if (argvChars > MONITOR_MAX_ARGV_CHARS) return `monitor arguments exceed the maximum ${MONITOR_MAX_ARGV_CHARS} characters combined.`;
|
|
256
|
+
|
|
257
|
+
const timeoutMs = request.timeoutMs ?? MONITOR_DEFAULT_TIMEOUT_MS;
|
|
258
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < MONITOR_MIN_TIMEOUT_MS) return `monitor timeoutMs must be at least ${MONITOR_MIN_TIMEOUT_MS}ms.`;
|
|
259
|
+
if (timeoutMs > MONITOR_MAX_TIMEOUT_MS) return `monitor timeoutMs exceeds the maximum ${MONITOR_MAX_TIMEOUT_MS}ms.`;
|
|
260
|
+
|
|
261
|
+
const maxEvents = request.maxEvents ?? MONITOR_DEFAULT_MAX_EVENTS;
|
|
262
|
+
if (!Number.isInteger(maxEvents) || maxEvents < 1) return "monitor maxEvents must be a positive integer.";
|
|
263
|
+
if (maxEvents > MONITOR_MAX_EVENTS) return `monitor maxEvents exceeds the maximum ${MONITOR_MAX_EVENTS}.`;
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private timeout(id: string): void {
|
|
268
|
+
const active = this.monitors.get(id);
|
|
269
|
+
if (!active || active.terminal) return;
|
|
270
|
+
const diagnostics = this.diagnostics(active);
|
|
271
|
+
const suffix = diagnostics ? `\n${diagnostics}` : "";
|
|
272
|
+
this.finish(active, {
|
|
273
|
+
id: active.entry.id,
|
|
274
|
+
label: active.entry.label,
|
|
275
|
+
kind: "timeout",
|
|
276
|
+
atEpochMs: this.deps.now(),
|
|
277
|
+
text: `monitor timed out after ${active.entry.expiresAtEpochMs - active.entry.startedAtEpochMs}ms${suffix}`,
|
|
278
|
+
terminal: true,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private receiveStdout(id: string, chunk: string): void {
|
|
283
|
+
const active = this.monitors.get(id);
|
|
284
|
+
if (!active || active.terminal || !chunk) return;
|
|
285
|
+
if (active.entry.mode === "exit") {
|
|
286
|
+
active.stdoutTail = appendTail(active.stdoutTail, chunk, MONITOR_MAX_DIAGNOSTIC_CHARS);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let start = 0;
|
|
291
|
+
while (true) {
|
|
292
|
+
const newline = chunk.indexOf("\n", start);
|
|
293
|
+
if (newline < 0) {
|
|
294
|
+
const remainder = chunk.slice(start);
|
|
295
|
+
active.stdoutBuffer = appendBoundedLine(active.stdoutBuffer, remainder);
|
|
296
|
+
active.lineHash = hashLine(active.lineHash, remainder);
|
|
297
|
+
active.lineLength += remainder.length;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// Hash the same bytes regardless of where the stream chunk split landed. Display
|
|
301
|
+
// trimming happens at emitOutput, after a CR from either chunk has contributed.
|
|
302
|
+
const line = chunk.slice(start, newline);
|
|
303
|
+
active.stdoutBuffer = appendBoundedLine(active.stdoutBuffer, line);
|
|
304
|
+
active.lineHash = hashLine(active.lineHash, line);
|
|
305
|
+
active.lineLength += line.length;
|
|
306
|
+
this.emitOutput(active, active.stdoutBuffer, `${active.lineLength}:${active.lineHash}`);
|
|
307
|
+
active.stdoutBuffer = "";
|
|
308
|
+
active.lineHash = 2_166_136_261;
|
|
309
|
+
active.lineLength = 0;
|
|
310
|
+
if (active.terminal) return;
|
|
311
|
+
start = newline + 1;
|
|
312
|
+
if (start >= chunk.length) {
|
|
313
|
+
active.stdoutBuffer = "";
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private receiveStderr(id: string, chunk: string): void {
|
|
320
|
+
const active = this.monitors.get(id);
|
|
321
|
+
if (!active || active.terminal) return;
|
|
322
|
+
active.stderrTail = appendTail(active.stderrTail, chunk, MONITOR_MAX_DIAGNOSTIC_CHARS);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private receiveExit(id: string, code: number | null, signal?: string): void {
|
|
326
|
+
const active = this.monitors.get(id);
|
|
327
|
+
if (!active || active.terminal) return;
|
|
328
|
+
this.finish(active, {
|
|
329
|
+
id: active.entry.id,
|
|
330
|
+
label: active.entry.label,
|
|
331
|
+
kind: "exit",
|
|
332
|
+
atEpochMs: this.deps.now(),
|
|
333
|
+
text: this.exitText(active, code, signal),
|
|
334
|
+
terminal: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private receiveError(id: string, error: Error): void {
|
|
339
|
+
const active = this.monitors.get(id);
|
|
340
|
+
if (!active || active.terminal) return;
|
|
341
|
+
if (active.starting) {
|
|
342
|
+
active.startError = error.message;
|
|
343
|
+
this.removeWithoutEvent(active);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const diagnostics = this.diagnostics(active);
|
|
347
|
+
this.finish(active, {
|
|
348
|
+
id: active.entry.id,
|
|
349
|
+
label: active.entry.label,
|
|
350
|
+
kind: "error",
|
|
351
|
+
atEpochMs: this.deps.now(),
|
|
352
|
+
text: `monitor process error: ${error.message}${diagnostics ? `\n${diagnostics}` : ""}`,
|
|
353
|
+
terminal: true,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private emitOutput(active: ActiveMonitor, rawLine: string, identity: string): void {
|
|
358
|
+
const line = rawLine.trim();
|
|
359
|
+
if (!line || active.terminal || active.seenLines.has(identity)) return;
|
|
360
|
+
active.seenLines.add(identity);
|
|
361
|
+
active.eventsSeen++;
|
|
362
|
+
const terminal = active.eventsSeen >= active.entry.maxEvents;
|
|
363
|
+
const event: MonitorEvent = {
|
|
364
|
+
id: active.entry.id,
|
|
365
|
+
label: active.entry.label,
|
|
366
|
+
kind: "output",
|
|
367
|
+
atEpochMs: this.deps.now(),
|
|
368
|
+
text: line,
|
|
369
|
+
terminal,
|
|
370
|
+
};
|
|
371
|
+
if (terminal) this.finish(active, event);
|
|
372
|
+
else this.deps.onEvent(event);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private exitText(active: ActiveMonitor, code: number | null, signal?: string): string {
|
|
376
|
+
const status = code === null
|
|
377
|
+
? `process exited by ${signal ?? "unknown signal"}`
|
|
378
|
+
: `process exited with exit code ${code}${signal ? ` (signal ${signal})` : ""}`;
|
|
379
|
+
const diagnostics = this.diagnostics(active);
|
|
380
|
+
return diagnostics ? `${status}\n${diagnostics}` : status;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private diagnostics(active: ActiveMonitor): string {
|
|
384
|
+
const parts: string[] = [];
|
|
385
|
+
if (active.stdoutTail) parts.push(`stdout:\n${active.stdoutTail}`);
|
|
386
|
+
if (active.entry.mode === "output" && active.stdoutBuffer) parts.push(`partial stdout (unterminated):\n${active.stdoutBuffer}`);
|
|
387
|
+
if (active.stderrTail) parts.push(`stderr:\n${active.stderrTail}`);
|
|
388
|
+
return parts.join("\n");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private finish(active: ActiveMonitor, event: MonitorEvent): void {
|
|
392
|
+
if (active.terminal || this.monitors.get(active.entry.id) !== active) return;
|
|
393
|
+
active.terminal = true;
|
|
394
|
+
this.monitors.delete(active.entry.id);
|
|
395
|
+
if (active.hasHandle) this.deps.clearTimer(active.handle);
|
|
396
|
+
try {
|
|
397
|
+
active.process?.stop();
|
|
398
|
+
} catch {
|
|
399
|
+
/* cleanup must not suppress the terminal wake */
|
|
400
|
+
}
|
|
401
|
+
this.deps.onEvent(event);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private removeWithoutEvent(active: ActiveMonitor): void {
|
|
405
|
+
if (active.terminal || this.monitors.get(active.entry.id) !== active) return;
|
|
406
|
+
active.terminal = true;
|
|
407
|
+
this.monitors.delete(active.entry.id);
|
|
408
|
+
if (active.hasHandle) this.deps.clearTimer(active.handle);
|
|
409
|
+
try {
|
|
410
|
+
active.process?.stop();
|
|
411
|
+
} catch {
|
|
412
|
+
/* best effort; cancellation remains terminal */
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming hints for two independent identity scopes:
|
|
3
|
+
* - intercom / `delegate`: the supervisor invents a fresh label per leg
|
|
4
|
+
* - exocom / `exocom_name`: the top-level instance derives its own handle from its task
|
|
5
|
+
*
|
|
6
|
+
* Neither path carries a catalog of example handles. Pure module (no Pi imports).
|
|
7
|
+
*/
|
|
8
|
+
import { sanitizeDisplayLabel } from "./display-label.ts";
|
|
9
|
+
|
|
10
|
+
/** Both engines use identical bounded identity metadata without changing routing handles. */
|
|
11
|
+
export function assignedIdentityPrompt(name: string | undefined): string | undefined {
|
|
12
|
+
if (!name?.trim()) return undefined;
|
|
13
|
+
const safe = sanitizeDisplayLabel(name, "agent");
|
|
14
|
+
return `Your supervisor assigned the following name for this run. Keep it; your agent template describes your role.\n[pi-persona assigned identity]\n${JSON.stringify({ name: safe })}\n[/pi-persona assigned identity]`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const CALL_SIGN_PROMPT =
|
|
18
|
+
"Invent a short distinctive call-sign from whatever this moment suggests — a mood, a joke, a snack, the weather, a place, a song fragment, a half-remembered character, a tool on the bench. Anything that fits. One or two words, or a compact compound. Fresh every time; never reuse a handle.";
|
|
19
|
+
|
|
20
|
+
/** Tool-schema / standing-brief hint for a delegated intercom leg. */
|
|
21
|
+
export function inventedLegNameHint(): string {
|
|
22
|
+
return `${CALL_SIGN_PROMPT} Then hyphen the purpose of this leg (\`<call-sign>-<purpose>\`). A new invention every leg. A UI label only, not a task description.`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Tool-schema / per-turn hint for this instance's exocom handle. */
|
|
26
|
+
export function inventedExocomNameHint(): string {
|
|
27
|
+
return (
|
|
28
|
+
"Invent a short distinctive call-sign inspired by the current task or inbound peer request that triggered this turn; " +
|
|
29
|
+
"use one or two words or a compact compound, with no built-in list or catalog. Your persona describes your role, not your personal name. " +
|
|
30
|
+
"Keep this identity across turns and persona changes."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Local widget: mark this instance without using "you" as the identity. */
|
|
35
|
+
export function exocomSelfWidgetLabel(namedByModel: boolean, name: string, persona = ""): string {
|
|
36
|
+
return `${exocomSelfStatusLabel(namedByModel, name, persona)} (you)`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Status / telemetry identity stays separate from the persona, including provisional labels. */
|
|
40
|
+
export function exocomSelfStatusLabel(_namedByModel: boolean, name: string, _persona = ""): string {
|
|
41
|
+
return name.trim() || "unnamed";
|
|
42
|
+
}
|