@fastagent-sh/fastagent 0.18.0 → 0.19.0
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/README.md +25 -7
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +19 -0
- package/dist/atomic-write.js +48 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-service.d.ts +54 -0
- package/dist/channels/agentcore-service.js +111 -0
- package/dist/channels/agentcore.d.ts +12 -2
- package/dist/channels/agentcore.js +6 -4
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +16 -6
- package/dist/channels/control.js +106 -4
- package/dist/{engines/pi/channel.d.ts → channels/discover.d.ts} +2 -2
- package/dist/{engines/pi/channel.js → channels/discover.js} +8 -12
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +1 -1
- package/dist/channels/feishu/context-buffer.js +4 -4
- package/dist/channels/feishu/feishu-api.js +1 -1
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +7 -7
- package/dist/channels/feishu/invoke-turn.d.ts +1 -1
- package/dist/channels/feishu/invoke-turn.js +3 -4
- package/dist/channels/feishu/preview.d.ts +1 -1
- package/dist/channels/feishu/preview.js +7 -3
- package/dist/channels/feishu/ws-ingress.d.ts +1 -1
- package/dist/channels/github/github.d.ts +1 -1
- package/dist/channels/http.d.ts +11 -7
- package/dist/channels/http.js +1 -109
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +2 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +14 -3
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +17 -6
- package/dist/channels/{preview-kit.d.ts → kit/preview-kit.d.ts} +3 -1
- package/dist/channels/{seen.js → kit/seen.js} +1 -1
- package/dist/channels/{state.js → kit/state.js} +4 -7
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +2 -2
- package/dist/channels/{tasks.js → kit/tasks.js} +1 -1
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +1 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.js → kit/turn-store.js} +1 -1
- package/dist/channels/lark/lark.d.ts +1 -1
- package/dist/channels/serve.d.ts +73 -0
- package/dist/channels/serve.js +243 -0
- package/dist/channels/slack/bot-auth.js +3 -14
- package/dist/channels/slack/context-buffer.d.ts +2 -2
- package/dist/channels/slack/context-buffer.js +2 -2
- package/dist/channels/slack/invoke-turn.d.ts +1 -1
- package/dist/channels/slack/invoke-turn.js +8 -5
- package/dist/channels/slack/parse.js +2 -2
- package/dist/channels/slack/preview.d.ts +1 -1
- package/dist/channels/slack/preview.js +11 -3
- package/dist/channels/slack/slack-api.js +1 -1
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +8 -8
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +2 -2
- package/dist/channels/telegram/context-buffer.js +2 -2
- package/dist/channels/telegram/invoke-turn.d.ts +1 -1
- package/dist/channels/telegram/invoke-turn.js +1 -1
- package/dist/channels/telegram/parse.js +2 -2
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/telegram.d.ts +1 -1
- package/dist/channels/telegram/telegram.js +3 -3
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/dev.js +25 -13
- package/dist/cli/commands/fire.js +1 -1
- package/dist/cli/commands/info.js +7 -4
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +63 -94
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +18 -57
- package/dist/cli/serve.js +57 -243
- package/dist/cli/shared.d.ts +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/preflight.js +1 -1
- package/dist/dev-supervisor.js +9 -3
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +314 -0
- package/dist/engines/pi/config.d.ts +4 -4
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +56 -43
- package/dist/engines/pi/create.js +141 -71
- package/dist/engines/pi/definition.d.ts +41 -7
- package/dist/engines/pi/definition.js +115 -11
- package/dist/engines/pi/invoke-session.d.ts +22 -19
- package/dist/engines/pi/invoke-session.js +286 -100
- package/dist/engines/pi/login.js +7 -4
- package/dist/engines/pi/models.d.ts +14 -0
- package/dist/engines/pi/models.js +9 -1
- package/dist/engines/pi/open.d.ts +9 -6
- package/dist/engines/pi/open.js +20 -10
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +19 -0
- package/dist/engines/pi/session-builder.js +84 -88
- package/dist/engines/pi/session-control.d.ts +10 -9
- package/dist/engines/pi/session-control.js +114 -77
- package/dist/engines/pi/session-inheritance.d.ts +44 -0
- package/dist/engines/pi/session-inheritance.js +242 -0
- package/dist/engines/pi/session-settings.d.ts +14 -2
- package/dist/engines/pi/session-settings.js +18 -0
- package/dist/engines/pi/session-store.d.ts +70 -0
- package/dist/engines/pi/session-store.js +313 -0
- package/dist/engines/pi/tool-context.d.ts +18 -8
- package/dist/engines/pi/tool-context.js +23 -1
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +1 -1
- package/dist/engines/pi/turn-kit.d.ts +32 -6
- package/dist/engines/pi/turn-kit.js +28 -1
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -1
- package/dist/node.d.ts +16 -0
- package/dist/node.js +20 -0
- package/dist/paths.d.ts +7 -0
- package/dist/paths.js +10 -0
- package/dist/pi.d.ts +14 -8
- package/dist/pi.js +6 -5
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +1 -1
- package/dist/service.d.ts +158 -0
- package/dist/service.js +354 -0
- package/dist/session-remote.d.ts +1 -1
- package/dist/session.d.ts +4 -2
- package/dist/session.js +1 -1
- package/package.json +14 -5
- package/dist/engines/pi/harness.d.ts +0 -115
- package/dist/engines/pi/harness.js +0 -154
- package/dist/engines/pi/invoke.d.ts +0 -60
- package/dist/engines/pi/invoke.js +0 -459
- package/dist/engines/pi/read-image.d.ts +0 -4
- package/dist/engines/pi/read-image.js +0 -62
- package/dist/engines/pi/sessions.d.ts +0 -60
- package/dist/engines/pi/sessions.js +0 -464
- package/dist/host/node.js +0 -52
- /package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +0 -0
- /package/dist/channels/{preview-kit.js → kit/preview-kit.js} +0 -0
- /package/dist/channels/{seen.d.ts → kit/seen.d.ts} +0 -0
- /package/dist/channels/{state.d.ts → kit/state.d.ts} +0 -0
- /package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +0 -0
- /package/dist/channels/{text.d.ts → kit/text.d.ts} +0 -0
- /package/dist/channels/{text.js → kit/text.js} +0 -0
- /package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
- /package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +0 -0
|
@@ -1,35 +1,100 @@
|
|
|
1
|
-
import { SESSION_BUSY_CODE } from "../../agent.js";
|
|
1
|
+
import { ABORTED_CODE, SESSION_BUSY_CODE, } from "../../agent.js";
|
|
2
2
|
import { cancellableStream } from "../../collect.js";
|
|
3
3
|
import { log } from "../../log.js";
|
|
4
|
-
import { EventQueue, errorToTerminal, inProcessLease, toPiPromptOptions, toTerminal } from "./turn-kit.js";
|
|
4
|
+
import { EventQueue, errorToTerminal, inProcessLease, projectAgentEvent, toPiPromptOptions, toTerminal, } from "./turn-kit.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* pi session events into the rich `SessionEvent` vocabulary — the SINGLE translation point. Events
|
|
7
|
+
* with no vocabulary yet (agent_start, turn_start, entry_appended, …) are dropped.
|
|
8
|
+
*
|
|
9
|
+
* `auto_retry_start` reports as a `retry_scheduled` with `operation: "assistant"`: pi retries a
|
|
10
|
+
* failed ANSWER request itself, which the summarization-only cases of that vocabulary predate.
|
|
8
11
|
*/
|
|
9
|
-
function
|
|
12
|
+
function toSessionEvent(event, runId) {
|
|
13
|
+
const at = Date.now();
|
|
10
14
|
switch (event.type) {
|
|
15
|
+
case "message_start":
|
|
16
|
+
// Assistant streaming only — a user/toolResult message is not a live message boundary.
|
|
17
|
+
if (event.message.role !== "assistant")
|
|
18
|
+
return null;
|
|
19
|
+
return { type: "message_started", timestamp: at, runId, data: {} };
|
|
11
20
|
case "message_update": {
|
|
12
21
|
// An empty delta is not output: it moves no consumer's state, and treating it as output would
|
|
13
22
|
// spend the silent window that auto-retry is allowed to use (see runOnSession).
|
|
14
23
|
const ev = event.assistantMessageEvent;
|
|
15
|
-
if (ev.type === "text_delta")
|
|
16
|
-
return ev.delta === ""
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
if (ev.type === "text_delta") {
|
|
25
|
+
return ev.delta === ""
|
|
26
|
+
? null
|
|
27
|
+
: { type: "message_delta", timestamp: at, runId, data: { channel: "text", delta: ev.delta } };
|
|
28
|
+
}
|
|
29
|
+
if (ev.type === "thinking_delta") {
|
|
30
|
+
return ev.delta === ""
|
|
31
|
+
? null
|
|
32
|
+
: { type: "message_delta", timestamp: at, runId, data: { channel: "thinking", delta: ev.delta } };
|
|
33
|
+
}
|
|
19
34
|
return null;
|
|
20
35
|
}
|
|
36
|
+
case "message_end":
|
|
37
|
+
if (event.message.role !== "assistant")
|
|
38
|
+
return null;
|
|
39
|
+
return { type: "message_finished", timestamp: at, runId, data: {} };
|
|
21
40
|
case "tool_execution_start":
|
|
22
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
type: "tool_started",
|
|
43
|
+
timestamp: at,
|
|
44
|
+
runId,
|
|
45
|
+
data: { id: event.toolCallId, name: event.toolName, args: event.args },
|
|
46
|
+
};
|
|
47
|
+
case "tool_execution_update":
|
|
48
|
+
return {
|
|
49
|
+
type: "tool_progress",
|
|
50
|
+
timestamp: at,
|
|
51
|
+
runId,
|
|
52
|
+
data: { id: event.toolCallId, name: event.toolName, partialResult: event.partialResult },
|
|
53
|
+
};
|
|
23
54
|
case "tool_execution_end":
|
|
24
|
-
return { type: "tool_ended", id: event.toolCallId, isError: event.isError, content: event.result };
|
|
25
|
-
case "auto_retry_start":
|
|
26
55
|
return {
|
|
27
|
-
type: "
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
type: "tool_finished",
|
|
57
|
+
timestamp: at,
|
|
58
|
+
runId,
|
|
59
|
+
data: { id: event.toolCallId, isError: event.isError, content: event.result },
|
|
60
|
+
};
|
|
61
|
+
case "queue_update":
|
|
62
|
+
return {
|
|
63
|
+
type: "queue_changed",
|
|
64
|
+
timestamp: at,
|
|
65
|
+
runId,
|
|
66
|
+
data: { steering: event.steering.length, followUp: event.followUp.length },
|
|
32
67
|
};
|
|
68
|
+
case "auto_retry_start": {
|
|
69
|
+
const retry = {
|
|
70
|
+
type: "retry_scheduled",
|
|
71
|
+
timestamp: at,
|
|
72
|
+
runId,
|
|
73
|
+
data: {
|
|
74
|
+
operation: "assistant",
|
|
75
|
+
attempt: event.attempt,
|
|
76
|
+
maxAttempts: event.maxAttempts,
|
|
77
|
+
delayMs: event.delayMs,
|
|
78
|
+
error: event.errorMessage,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
return retry;
|
|
82
|
+
}
|
|
83
|
+
case "summarization_retry_scheduled": {
|
|
84
|
+
const retry = {
|
|
85
|
+
type: "retry_scheduled",
|
|
86
|
+
timestamp: at,
|
|
87
|
+
runId,
|
|
88
|
+
data: {
|
|
89
|
+
operation: "compaction",
|
|
90
|
+
attempt: event.attempt,
|
|
91
|
+
maxAttempts: event.maxAttempts,
|
|
92
|
+
delayMs: event.delayMs,
|
|
93
|
+
error: event.errorMessage,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return retry;
|
|
97
|
+
}
|
|
33
98
|
default:
|
|
34
99
|
return null;
|
|
35
100
|
}
|
|
@@ -37,7 +102,7 @@ function toAgentEvent(event) {
|
|
|
37
102
|
/**
|
|
38
103
|
* The turn's outcome. `prompt()` resolves void and never throws for an engine-side failure (measured:
|
|
39
104
|
* a provider error and an abort both resolve normally), so the terminal comes from the assistant
|
|
40
|
-
* message the run ended on
|
|
105
|
+
* message the run ended on.
|
|
41
106
|
*
|
|
42
107
|
* That message is taken from the EVENT STREAM, not from `session.state.messages`. Session state is
|
|
43
108
|
* mutable mid-turn: compaction replaces the array, and overflow recovery splices the last assistant
|
|
@@ -54,11 +119,11 @@ const ENGINE_PRODUCED_NOTHING = {
|
|
|
54
119
|
retryable: false,
|
|
55
120
|
};
|
|
56
121
|
export function createPiAgentFromSession(options) {
|
|
57
|
-
const { sessionFactory, lease = inProcessLease() } = options;
|
|
58
|
-
|
|
59
|
-
async function* turn(scope, prompt, hooks) {
|
|
122
|
+
const { sessionFactory, lease = inProcessLease(), observer } = options;
|
|
123
|
+
async function* turn(scope, prompt, { onCancelReady, wasCancelled }) {
|
|
60
124
|
const release = lease.tryAcquire(scope.session);
|
|
61
125
|
if (!release) {
|
|
126
|
+
// Rejected BEFORE acceptance: no run exists, so the observer sees nothing (replay-safe).
|
|
62
127
|
yield {
|
|
63
128
|
type: "failed",
|
|
64
129
|
details: "session busy: a turn is already in flight for this session",
|
|
@@ -67,19 +132,213 @@ export function createPiAgentFromSession(options) {
|
|
|
67
132
|
};
|
|
68
133
|
return;
|
|
69
134
|
}
|
|
135
|
+
// The run exists from here: exactly one run_started, exactly one run_settled. The settlement is
|
|
136
|
+
// emitted in the outer finally, immediately before the lease releases, so the observation
|
|
137
|
+
// plane's "running" window equals the lease window — state() must never read idle while a new
|
|
138
|
+
// invoke would still be rejected session_busy. A run with no recorded outcome was cancelled by
|
|
139
|
+
// the caller (SPEC: cancellation has no terminal event), which settles as aborted.
|
|
140
|
+
const runId = crypto.randomUUID();
|
|
141
|
+
let outcome;
|
|
142
|
+
const observe = (event, run) => {
|
|
143
|
+
if (!event || !observer)
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
observer(scope.session, event, run);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// The observation plane must never break the data plane; a broken hub is its own problem.
|
|
150
|
+
log.warn(`[fastagent] session observer threw (event ${event.type}): ${String(error)}`);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
// run_started is published BEFORE the session is built, so no early event can outrun the
|
|
154
|
+
// registration — which means the controls have to await the build rather than reject during it:
|
|
155
|
+
// a dispatch that races it simply queues on the freshly bound session. A build failure rejects
|
|
156
|
+
// the gate, so a pending dispatch learns why instead of hanging.
|
|
157
|
+
let sessionReady;
|
|
158
|
+
let sessionFailed;
|
|
159
|
+
const bound = new Promise((resolve, reject) => {
|
|
160
|
+
sessionReady = resolve;
|
|
161
|
+
sessionFailed = reject;
|
|
162
|
+
});
|
|
163
|
+
bound.catch(() => { }); // observed through the controls only when a dispatch actually happens
|
|
164
|
+
// Stale-controls guard: after settlement pi's steer()/followUp()/abort() would still resolve
|
|
165
|
+
// (they queue onto a session about to be disposed), which is a silent acceptance of a command
|
|
166
|
+
// that can never take effect. The check and the engine call share one synchronous block — pi
|
|
167
|
+
// enqueues at method entry, so a check behind its own await would only shrink the race.
|
|
168
|
+
let settled = false;
|
|
169
|
+
const settledError = () => new Error("run already settled; the command cannot take effect");
|
|
170
|
+
// Aborted classification has two sources, either sufficient: pi's own stopReason "aborted", and
|
|
171
|
+
// control-plane INTENT — providers do not uniformly attribute an aborted stream, so an abort
|
|
172
|
+
// that was still in flight when the terminal arrived counts too.
|
|
173
|
+
let abortsInFlight = 0;
|
|
174
|
+
let abortSucceeded = false;
|
|
175
|
+
const controls = {
|
|
176
|
+
async steer(p) {
|
|
177
|
+
const opts = await toPiPromptOptions(p);
|
|
178
|
+
const session = await bound;
|
|
179
|
+
if (settled)
|
|
180
|
+
throw settledError();
|
|
181
|
+
await session.steer(p.text, opts?.images);
|
|
182
|
+
},
|
|
183
|
+
async followUp(p) {
|
|
184
|
+
const opts = await toPiPromptOptions(p);
|
|
185
|
+
const session = await bound;
|
|
186
|
+
if (settled)
|
|
187
|
+
throw settledError();
|
|
188
|
+
await session.followUp(p.text, opts?.images);
|
|
189
|
+
},
|
|
190
|
+
async abort() {
|
|
191
|
+
const session = await bound;
|
|
192
|
+
if (settled)
|
|
193
|
+
throw settledError();
|
|
194
|
+
abortsInFlight++;
|
|
195
|
+
try {
|
|
196
|
+
await session.abort();
|
|
197
|
+
abortSucceeded = true;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
abortsInFlight--;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
observe({ type: "run_started", timestamp: Date.now(), runId, data: {} }, controls);
|
|
70
205
|
try {
|
|
71
206
|
let session;
|
|
72
207
|
try {
|
|
73
|
-
|
|
208
|
+
// The scope's lineage reaches the store's CREATE path only — an existing session opens
|
|
209
|
+
// exactly as before, whatever the scope names.
|
|
210
|
+
session = await sessionFactory(scope.session, scope.parentSession === undefined
|
|
211
|
+
? undefined
|
|
212
|
+
: {
|
|
213
|
+
parentSession: scope.parentSession,
|
|
214
|
+
...(scope.branchHints !== undefined ? { branchHints: scope.branchHints } : {}),
|
|
215
|
+
});
|
|
216
|
+
sessionReady(session);
|
|
74
217
|
}
|
|
75
218
|
catch (error) {
|
|
76
|
-
|
|
219
|
+
// Setup failures (session open, auth, a broken definition) are EVENTS, never throws
|
|
220
|
+
// (MUST 2) — and they settle the run as failed: an unrecorded outcome means the caller
|
|
221
|
+
// cancelled, which this is not.
|
|
222
|
+
sessionFailed(error); // a pending dispatch learns the run cannot take commands
|
|
223
|
+
const terminal = errorToTerminal(error);
|
|
224
|
+
outcome = { status: "failed", error: { message: terminal.details, retryable: terminal.retryable } };
|
|
225
|
+
settled = true;
|
|
226
|
+
yield terminal;
|
|
77
227
|
return;
|
|
78
228
|
}
|
|
79
229
|
try {
|
|
80
|
-
|
|
230
|
+
const abort = () => session.abort().catch(() => { });
|
|
231
|
+
onCancelReady(() => void abort());
|
|
232
|
+
const queue = new EventQueue();
|
|
233
|
+
let finalAssistant;
|
|
234
|
+
/** Whether any of THIS attempt's answer has been streamed — the only output a retry duplicates. */
|
|
235
|
+
let streamedAnswer = false;
|
|
236
|
+
/** Set when a retry is refused because the answer already streamed — carries the ending error. */
|
|
237
|
+
let retriedAfterAnswer;
|
|
238
|
+
const unsub = session.subscribe((event) => {
|
|
239
|
+
if (retriedAfterAnswer !== undefined)
|
|
240
|
+
return; // decided; the retry's output is not ours
|
|
241
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
242
|
+
finalAssistant = event.message;
|
|
243
|
+
}
|
|
244
|
+
// pi retries a failed assistant request by DISCARDING that attempt's assistant message and
|
|
245
|
+
// asking again. Everything the turn achieved before it survives — executed tools keep
|
|
246
|
+
// their persisted results, and the retry resumes from them — so the only thing a retry can
|
|
247
|
+
// duplicate is answer text already streamed, which SPEC deltas cannot retract. Refuse it
|
|
248
|
+
// exactly there: refusing on tool events instead would push the retry out to the CALLER,
|
|
249
|
+
// who can only re-run the whole prompt and execute the tool a second time.
|
|
250
|
+
if (event.type === "auto_retry_start" && streamedAnswer) {
|
|
251
|
+
retriedAfterAnswer = event.errorMessage;
|
|
252
|
+
// Not synchronously: pi emits this event BEFORE creating the controller that makes its
|
|
253
|
+
// backoff abortable, so an abort from inside the listener would find nothing to cancel
|
|
254
|
+
// and the turn would still pay the delay and burn a provider call on a discarded answer.
|
|
255
|
+
queueMicrotask(() => void abort());
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const rich = toSessionEvent(event, runId);
|
|
259
|
+
if (!rich)
|
|
260
|
+
return;
|
|
261
|
+
observe(rich);
|
|
262
|
+
const projected = projectAgentEvent(rich);
|
|
263
|
+
if (!projected)
|
|
264
|
+
return;
|
|
265
|
+
if (projected.type === "text" || projected.type === "thinking")
|
|
266
|
+
streamedAnswer = true;
|
|
267
|
+
queue.push(projected);
|
|
268
|
+
});
|
|
269
|
+
try {
|
|
270
|
+
// Resolving prompt options lazy-loads the image pipeline and re-encodes every attachment,
|
|
271
|
+
// so it both takes time and can throw before any engine work exists to fail. Hence the two
|
|
272
|
+
// guards, in this order and no earlier: its failure is a turn failure (MUST 2), and the
|
|
273
|
+
// latch has to be read after the LAST await before the call — the door armed above only
|
|
274
|
+
// stops a RUNNING session, so a consumer who walked away during the build or the resize
|
|
275
|
+
// would knock on an idle one and have the turn start anyway.
|
|
276
|
+
let promptOptions;
|
|
277
|
+
try {
|
|
278
|
+
promptOptions = await toPiPromptOptions(prompt);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const terminal = errorToTerminal(error);
|
|
282
|
+
outcome = { status: "failed", error: { message: terminal.details, retryable: terminal.retryable } };
|
|
283
|
+
settled = true;
|
|
284
|
+
yield terminal;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (wasCancelled()) {
|
|
288
|
+
settled = true;
|
|
289
|
+
await abort();
|
|
290
|
+
return; // cancelled: the outer finally settles it as aborted
|
|
291
|
+
}
|
|
292
|
+
const run = session.prompt(prompt.text, promptOptions);
|
|
293
|
+
yield* queue.drainUntil(run);
|
|
294
|
+
let terminal;
|
|
295
|
+
try {
|
|
296
|
+
await run;
|
|
297
|
+
terminal =
|
|
298
|
+
retriedAfterAnswer !== undefined
|
|
299
|
+
? { type: "failed", details: retriedAfterAnswer, retryable: true }
|
|
300
|
+
: finalAssistant
|
|
301
|
+
? toTerminal(finalAssistant)
|
|
302
|
+
: ENGINE_PRODUCED_NOTHING;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
terminal = errorToTerminal(error);
|
|
306
|
+
}
|
|
307
|
+
if ((abortSucceeded || abortsInFlight > 0) && terminal.type === "failed") {
|
|
308
|
+
terminal = { type: "failed", details: terminal.details, retryable: false, code: ABORTED_CODE };
|
|
309
|
+
}
|
|
310
|
+
if (terminal.type === "failed") {
|
|
311
|
+
outcome =
|
|
312
|
+
terminal.code === ABORTED_CODE
|
|
313
|
+
? // Carry the detail: an independent error that raced an accepted abort must stay
|
|
314
|
+
// diagnosable in the settlement, which is what audit consumers read.
|
|
315
|
+
{ status: "aborted", error: { message: terminal.details, retryable: false } }
|
|
316
|
+
: {
|
|
317
|
+
status: "failed",
|
|
318
|
+
error: { code: terminal.code, message: terminal.details, retryable: terminal.retryable },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
outcome = { status: "completed" };
|
|
323
|
+
}
|
|
324
|
+
// Commands become ineffective the moment the run resolved — not at the outer finally,
|
|
325
|
+
// which sits behind a consumer-paced `yield`.
|
|
326
|
+
settled = true;
|
|
327
|
+
yield terminal;
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
settled = true;
|
|
331
|
+
unsub();
|
|
332
|
+
}
|
|
81
333
|
}
|
|
82
334
|
finally {
|
|
335
|
+
// NO session_shutdown here, deliberately. A per-invoke session makes one look right, but the
|
|
336
|
+
// extension INSTANCE it would tear down is not per-invoke: extensions belong to the agent's
|
|
337
|
+
// assembly and every turn shares one. Emitting a shutdown per turn had a finished
|
|
338
|
+
// turn clearing a timer a concurrent turn had just opened (measured, and pinned in
|
|
339
|
+
// definition-extensions.test.ts). The lifecycle has to match the instance, not the session
|
|
340
|
+
// wrapper: one agent, one instance, no per-turn teardown. Extensions that need per-turn
|
|
341
|
+
// cleanup do it in the tool or handler that opened the resource.
|
|
83
342
|
try {
|
|
84
343
|
session.dispose();
|
|
85
344
|
}
|
|
@@ -89,83 +348,10 @@ export function createPiAgentFromSession(options) {
|
|
|
89
348
|
}
|
|
90
349
|
}
|
|
91
350
|
finally {
|
|
92
|
-
|
|
351
|
+
settled = true;
|
|
352
|
+
observe({ type: "run_settled", timestamp: Date.now(), runId, data: outcome ?? { status: "aborted" } });
|
|
353
|
+
release(); // after the settlement, so the next invoke for this session cannot outrun it
|
|
93
354
|
}
|
|
94
355
|
}
|
|
95
356
|
return { invoke: (scope, prompt) => cancellableStream((hooks) => turn(scope, prompt, hooks)) };
|
|
96
357
|
}
|
|
97
|
-
/** One turn on a session someone else owns: subscribe, prompt, stream, terminal. */
|
|
98
|
-
async function* runOnSession(session, prompt, { onCancelReady, wasCancelled }) {
|
|
99
|
-
const abort = () => session.abort().catch(() => { });
|
|
100
|
-
onCancelReady(() => void abort());
|
|
101
|
-
const queue = new EventQueue();
|
|
102
|
-
let finalAssistant;
|
|
103
|
-
/** Whether any of THIS attempt's answer has been streamed — the only output a retry would duplicate. */
|
|
104
|
-
let streamedAnswer = false;
|
|
105
|
-
/** Set when a retry is refused because the answer already streamed — carries the error that ends it. */
|
|
106
|
-
let retriedAfterAnswer;
|
|
107
|
-
const unsub = session.subscribe((event) => {
|
|
108
|
-
if (retriedAfterAnswer !== undefined)
|
|
109
|
-
return; // the turn is decided; the retry's output is not ours
|
|
110
|
-
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
111
|
-
finalAssistant = event.message;
|
|
112
|
-
}
|
|
113
|
-
// pi retries a failed assistant request by DISCARDING that attempt's assistant message and asking
|
|
114
|
-
// again. Everything the turn achieved before it survives - executed tools keep their persisted
|
|
115
|
-
// results, and the retry resumes from them - so the only thing a retry can duplicate is answer
|
|
116
|
-
// text this L0 already streamed, which SPEC deltas cannot retract. Refuse it exactly there:
|
|
117
|
-
// refusing on tool events instead would push the retry out to the CALLER, who can only re-run the
|
|
118
|
-
// whole prompt and execute the tool a second time.
|
|
119
|
-
if (event.type === "auto_retry_start" && streamedAnswer) {
|
|
120
|
-
retriedAfterAnswer = event.errorMessage;
|
|
121
|
-
// Not synchronously: pi emits this event BEFORE creating the controller that makes its backoff
|
|
122
|
-
// abortable, so an abort from inside the listener would find nothing to cancel and the turn
|
|
123
|
-
// would still pay the full delay and burn a provider call on an answer we must discard.
|
|
124
|
-
queueMicrotask(() => void abort());
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const projected = toAgentEvent(event);
|
|
128
|
-
if (!projected)
|
|
129
|
-
return;
|
|
130
|
-
if (projected.type === "text" || projected.type === "thinking")
|
|
131
|
-
streamedAnswer = true;
|
|
132
|
-
queue.push(projected);
|
|
133
|
-
});
|
|
134
|
-
try {
|
|
135
|
-
// Resolving prompt options lazy-loads the image pipeline and re-encodes every attachment, so it
|
|
136
|
-
// both takes time and can throw before any engine work exists to fail. Hence the two guards, in
|
|
137
|
-
// this order and no earlier: its failure is a turn failure (MUST 2), and the latch has to be read
|
|
138
|
-
// after the LAST await before the call — the door armed above only stops a RUNNING session, so a
|
|
139
|
-
// consumer who walked away during the build or the resize would knock on an idle one and have
|
|
140
|
-
// the turn start anyway.
|
|
141
|
-
let options;
|
|
142
|
-
try {
|
|
143
|
-
options = await toPiPromptOptions(prompt);
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
yield errorToTerminal(error);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (wasCancelled()) {
|
|
150
|
-
await abort();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const run = session.prompt(prompt.text, options);
|
|
154
|
-
yield* queue.drainUntil(run);
|
|
155
|
-
try {
|
|
156
|
-
await run;
|
|
157
|
-
if (retriedAfterAnswer !== undefined) {
|
|
158
|
-
yield { type: "failed", details: retriedAfterAnswer, retryable: true };
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
yield finalAssistant ? toTerminal(finalAssistant) : ENGINE_PRODUCED_NOTHING;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
yield errorToTerminal(error);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
unsub();
|
|
170
|
-
}
|
|
171
|
-
}
|
package/dist/engines/pi/login.js
CHANGED
|
@@ -18,13 +18,16 @@ function anySignal(...signals) {
|
|
|
18
18
|
return present.length === 0 ? undefined : present.length === 1 ? present[0] : AbortSignal.any(present);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Map pi-ai's `
|
|
22
|
-
* resolves, cancelling a prompt the provider left pending (a manual-code paste racing a
|
|
23
|
-
* server it just won) so the one-shot CLI exits instead of hanging on stdin.
|
|
21
|
+
* Map pi-ai's `ProviderAuthInteraction` onto the injected {@link LoginIO}. `doneSignal` fires when
|
|
22
|
+
* the flow resolves, cancelling a prompt the provider left pending (a manual-code paste racing a
|
|
23
|
+
* callback server it just won) so the one-shot CLI exits instead of hanging on stdin.
|
|
24
|
+
*
|
|
25
|
+
* The signal is REQUIRED by that type (pi normalizes it before calling a provider), so a caller that
|
|
26
|
+
* passes none gets one that never fires — the same "no cancellation" the optional field meant.
|
|
24
27
|
*/
|
|
25
28
|
function authCallbacks(io, userSignal, doneSignal) {
|
|
26
29
|
return {
|
|
27
|
-
signal: userSignal,
|
|
30
|
+
signal: userSignal ?? new AbortController().signal,
|
|
28
31
|
prompt: async (p) => {
|
|
29
32
|
if (p.type === "select") {
|
|
30
33
|
const v = await io.select(p.message, p.options.map((o) => ({ value: o.id, label: o.label, hint: o.description })));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
1
2
|
import { type Api, type Model, type Models, type Provider } from "@earendil-works/pi-ai";
|
|
2
3
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import { type FastagentAuthOptions } from "./auth.ts";
|
|
@@ -26,6 +27,19 @@ export interface CreatePiModelsOptions extends FastagentAuthOptions {
|
|
|
26
27
|
* is upstream-owned).
|
|
27
28
|
*/
|
|
28
29
|
export declare function createPiModels(options?: CreatePiModelsOptions): Models;
|
|
30
|
+
/**
|
|
31
|
+
* pi's Model with the API-shape generic erased — fastagent only passes models through to the engine,
|
|
32
|
+
* so the generic carries no information. One alias keeps the `any` auditable.
|
|
33
|
+
*/
|
|
34
|
+
export type AnyModel = Model<any>;
|
|
35
|
+
/**
|
|
36
|
+
* The serving default for reasoning effort, pinned to what pi's TUI defaults to (its own
|
|
37
|
+
* DEFAULT_THINKING_LEVEL) — NOT inherited from the engine, whose fallback is "off": an author vibes
|
|
38
|
+
* at "medium" in pi and must get "medium" when served (fidelity), and pinning the value here means
|
|
39
|
+
* an upstream default change in either place cannot silently alter deployments. Models that do not
|
|
40
|
+
* support a level are clamped by pi per model.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_THINKING_LEVEL: ThinkingLevel;
|
|
29
43
|
/**
|
|
30
44
|
* The `ModelRuntime`-shaped sibling of {@link createPiModels} — the SAME hub semantics (built-in
|
|
31
45
|
* providers + fastagent's credential store at `authPath`) in the type pi's session services require
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The pi `Models` collection — the single hub that owns BOTH model resolution (provider/modelId
|
|
3
3
|
* lookup) AND auth (per-request credential resolution). fastagent builds one per opener and threads
|
|
4
|
-
* it into the
|
|
4
|
+
* it into the engine alongside the selected `model`; the two must come from the same collection so
|
|
5
5
|
* the model's provider auth is in scope.
|
|
6
6
|
*/
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -28,6 +28,14 @@ export function createPiModels(options = {}) {
|
|
|
28
28
|
models.setProvider(provider);
|
|
29
29
|
return models;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The serving default for reasoning effort, pinned to what pi's TUI defaults to (its own
|
|
33
|
+
* DEFAULT_THINKING_LEVEL) — NOT inherited from the engine, whose fallback is "off": an author vibes
|
|
34
|
+
* at "medium" in pi and must get "medium" when served (fidelity), and pinning the value here means
|
|
35
|
+
* an upstream default change in either place cannot silently alter deployments. Models that do not
|
|
36
|
+
* support a level are clamped by pi per model.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_THINKING_LEVEL = "medium";
|
|
31
39
|
/**
|
|
32
40
|
* The `ModelRuntime`-shaped sibling of {@link createPiModels} — the SAME hub semantics (built-in
|
|
33
41
|
* providers + fastagent's credential store at `authPath`) in the type pi's session services require
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Agent } from "../../agent.ts";
|
|
2
2
|
import { type FastagentConfig } from "./config.ts";
|
|
3
3
|
import type { SessionControl } from "../../session.ts";
|
|
4
|
-
import type { SessionObserver } from "./
|
|
5
|
-
import type { PiSessionReader, PiSessionStore } from "./sessions.ts";
|
|
4
|
+
import type { SessionObserver } from "./turn-kit.ts";
|
|
6
5
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
7
6
|
import { type LoadedDefinition } from "./definition.ts";
|
|
7
|
+
import { type PiSessionRecordStore } from "./session-store.ts";
|
|
8
8
|
import type { ToolCollision } from "./tool.ts";
|
|
9
9
|
import type { MountedTool } from "./tool.ts";
|
|
10
10
|
export interface CreatePiAgentFromDirOptions {
|
|
@@ -44,7 +44,7 @@ export interface CreatePiAgentFromDirOptions {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* The agent assembly FRONT HALF — everything that is independent of how pi consumes the
|
|
47
|
-
* definition (
|
|
47
|
+
* definition (a per-invoke session for serving vs a resident one for chat):
|
|
48
48
|
* placement resolution → config → model spec → the full tool surface ({@link resolveAgentTools} — the
|
|
49
49
|
* ONE place it is computed) → state root → auth path. Both {@link createPiAgentFromDir} and the
|
|
50
50
|
* session builder (session-builder.ts) consume this, so THESE inputs cannot drift between the two
|
|
@@ -65,7 +65,7 @@ export interface AgentAssembly {
|
|
|
65
65
|
stateRoot: string;
|
|
66
66
|
/** Absolute credentials file (--auth-path/authPath option > FASTAGENT_AUTH_PATH > <agentDir>/.secrets/auth.json). */
|
|
67
67
|
authPath: string;
|
|
68
|
-
/** The full mounted tool surface (config.tools + discovered tools/, search_tools applied). */
|
|
68
|
+
/** The full mounted tool surface (all coding tools + config.tools + discovered tools/, search_tools applied). */
|
|
69
69
|
tools: MountedTool[];
|
|
70
70
|
toolNames: string[];
|
|
71
71
|
deferredToolNames: string[];
|
|
@@ -99,10 +99,13 @@ export declare function createPiAgentFromDir(dir: string, options?: CreatePiAgen
|
|
|
99
99
|
sessionsDir: string;
|
|
100
100
|
/** Absolute credentials file in use (for the startup report). */
|
|
101
101
|
authPath: string;
|
|
102
|
-
/** The session store in use
|
|
103
|
-
sessions:
|
|
102
|
+
/** The session store in use. */
|
|
103
|
+
sessions: PiSessionRecordStore;
|
|
104
104
|
/** The observation plane over this agent's sessions; present iff `options.sessionControl`. */
|
|
105
105
|
sessionControl?: SessionControl;
|
|
106
|
+
/** Whether the agent schedules its own follow-up turns — read from the config, so a caller
|
|
107
|
+
* assembling a service does not have to reach back into it (MountableAgent). */
|
|
108
|
+
selfSchedule: boolean;
|
|
106
109
|
/** Non-default, active-by-default tool names in effect: config.tools + discovered tools/. Each name
|
|
107
110
|
* lives in exactly one report slot — deferred names are in {@link deferredToolNames} instead. */
|
|
108
111
|
toolNames: string[];
|
package/dist/engines/pi/open.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createPiSessionControl } from "./session-control.js";
|
|
|
16
16
|
import { withWakeTool } from "./wake-tool.js";
|
|
17
17
|
import { loadAgentSkills } from "./definition.js";
|
|
18
18
|
import { reportFindingsIfChanged } from "./report.js";
|
|
19
|
-
import {
|
|
19
|
+
import { piSessionRecordStore } from "./session-store.js";
|
|
20
20
|
export async function resolveAgentAssembly(dir, options = {}) {
|
|
21
21
|
// Placement is structural (resolvePlacement): the AGENT DIR carries definition + config + machinery;
|
|
22
22
|
// its parent — the WORKSPACE — is what the agent works on: its cwd and the start of the ②-context
|
|
@@ -27,7 +27,7 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
27
27
|
if (!modelSpec) {
|
|
28
28
|
throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
|
|
29
29
|
}
|
|
30
|
-
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir);
|
|
30
|
+
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace);
|
|
31
31
|
// The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it
|
|
32
32
|
// in one knob — a container points it at its volume); the finer overrides below still win.
|
|
33
33
|
const stateRoot = resolveStateRoot(agentDir);
|
|
@@ -62,13 +62,14 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
62
62
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving && !!config.selfSchedule);
|
|
63
63
|
const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
|
|
64
64
|
await mkdir(sessionsDir, { recursive: true });
|
|
65
|
-
const sessions =
|
|
65
|
+
const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
|
|
66
66
|
// The hub is wired HERE because the store is created here: chicken-and-egg otherwise (the hub
|
|
67
|
-
// needs the store; the agent needs the hub's observer). Boundary parts (
|
|
67
|
+
// needs the store; the agent needs the hub's observer). Boundary parts (factory/lease/registry)
|
|
68
68
|
// only exist after the assembly below — the hub takes them as a lazy thunk, filled by the
|
|
69
69
|
// assembly's onAssembly callback (assembly completes before this function returns, so every
|
|
70
70
|
// dispatch sees them). An extra caller observer composes after the hub's (TRUSTED seam).
|
|
71
71
|
let boundaryParts;
|
|
72
|
+
let assembled;
|
|
72
73
|
const caller = options.observer;
|
|
73
74
|
const wantControl = options.sessionControl ?? (config.sessionControl === true && options.serving === true);
|
|
74
75
|
const hub = wantControl
|
|
@@ -117,20 +118,29 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
117
118
|
observer,
|
|
118
119
|
onAssembly: hub
|
|
119
120
|
? (parts) => {
|
|
120
|
-
|
|
121
|
-
lease: parts.lease,
|
|
122
|
-
models: parts.models,
|
|
123
|
-
harnessFactory: parts.harnessFactory,
|
|
124
|
-
defaults: parts.defaults,
|
|
125
|
-
};
|
|
121
|
+
assembled = parts;
|
|
126
122
|
}
|
|
127
123
|
: undefined,
|
|
128
124
|
});
|
|
125
|
+
if (hub && assembled) {
|
|
126
|
+
// The hub's surface is synchronous (`capabilities()` lists the allowed models), while building
|
|
127
|
+
// the registry reads credentials and is not. Resolve it ONCE here — the opener is async anyway,
|
|
128
|
+
// and the first turn would have paid the same read — so every boundary mutation validates
|
|
129
|
+
// against the same registry the runs use.
|
|
130
|
+
const { modelRuntime, model } = await assembled.engine();
|
|
131
|
+
boundaryParts = {
|
|
132
|
+
lease: assembled.lease,
|
|
133
|
+
models: modelRuntime,
|
|
134
|
+
sessionFactory: assembled.sessionFactory,
|
|
135
|
+
defaults: { model, thinkingLevel: assembled.thinkingLevel },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
129
138
|
return {
|
|
130
139
|
agent,
|
|
131
140
|
definition,
|
|
132
141
|
sessions,
|
|
133
142
|
sessionControl: hub?.control,
|
|
143
|
+
selfSchedule: config.selfSchedule ?? false,
|
|
134
144
|
agentDir,
|
|
135
145
|
workspace,
|
|
136
146
|
config,
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import type { SkillCollision } from "./definition.ts";
|
|
7
|
-
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
8
7
|
import type { ToolCollision } from "./tool.ts";
|
|
9
8
|
type Findings = {
|
|
10
9
|
collisions: SkillCollision[];
|
|
@@ -23,7 +22,4 @@ type Findings = {
|
|
|
23
22
|
export declare function reportFindingsIfChanged(dir: string, def: Findings): void;
|
|
24
23
|
export declare function reportDefinitionWarnings(collisions: SkillCollision[], diagnostics: SkillDiagnostic[]): void;
|
|
25
24
|
export declare function reportToolCollisions(collisions: ToolCollision[]): void;
|
|
26
|
-
/** Report per-file module failures. The caller decides whether they are degradations (tools/schedules)
|
|
27
|
-
* or fatal (declared channels on the serving path). */
|
|
28
|
-
export declare function reportModuleLoadFailures(failures: ModuleLoadFailure[]): void;
|
|
29
25
|
export {};
|