@anhlp/alp-runtime 0.0.0-beta.1 → 0.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-transport.d.ts +16 -0
- package/dist/acp-transport.js +40 -7
- package/dist/antigravity-transport.d.ts +2 -0
- package/dist/antigravity-transport.js +12 -2
- package/dist/claude-transport.d.ts +14 -0
- package/dist/claude-transport.js +21 -9
- package/dist/contracts.d.ts +8 -2
- package/dist/contracts.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/resolve.js +3 -3
- package/dist/runtime.js +54 -24
- package/dist/session-state.d.ts +3 -0
- package/dist/tool-detail.d.ts +81 -0
- package/dist/tool-detail.js +329 -0
- package/dist/usage.d.ts +10 -0
- package/dist/usage.js +26 -1
- package/package.json +3 -3
- package/src/acp-transport.ts +44 -9
- package/src/antigravity-transport.ts +9 -0
- package/src/claude-transport.ts +24 -8
- package/src/contracts.ts +8 -2
- package/src/index.ts +2 -0
- package/src/resolve.ts +3 -3
- package/src/runtime.ts +65 -25
- package/src/session-state.ts +3 -0
- package/src/tool-detail.ts +394 -0
- package/src/usage.ts +37 -0
package/dist/acp-transport.d.ts
CHANGED
|
@@ -58,6 +58,18 @@ export declare function acpWhat(request: AcpPermission): string;
|
|
|
58
58
|
*/
|
|
59
59
|
export declare function acpEnv(provider: Pick<AcpProvider, 'command' | 'env'>, base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
60
60
|
/** Maps an ACP agent over stdio to the normalized runtime protocol. */
|
|
61
|
+
/** One turn-input part as ACP's: text, or a data-URL image as an ACP image block; anything else is dropped. */
|
|
62
|
+
export declare function acpContent(part: any): {
|
|
63
|
+
type: string;
|
|
64
|
+
text: any;
|
|
65
|
+
mimeType?: undefined;
|
|
66
|
+
data?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
type: string;
|
|
69
|
+
mimeType: string;
|
|
70
|
+
data: string;
|
|
71
|
+
text?: undefined;
|
|
72
|
+
} | null;
|
|
61
73
|
export declare class AcpTransport {
|
|
62
74
|
private readonly provider;
|
|
63
75
|
private readonly cwd;
|
|
@@ -71,6 +83,8 @@ export declare class AcpTransport {
|
|
|
71
83
|
private requestHandler?;
|
|
72
84
|
private closed;
|
|
73
85
|
private capabilities;
|
|
86
|
+
/** ACP agents say in initialize whether a prompt may hold images (promptCapabilities.image). */
|
|
87
|
+
takesImages(): boolean;
|
|
74
88
|
private sessionId;
|
|
75
89
|
private config;
|
|
76
90
|
/** Instructions go with the first prompt of a new session; a loaded one has them already. */
|
|
@@ -95,6 +109,8 @@ export declare class AcpTransport {
|
|
|
95
109
|
request(method: string, params: any): Promise<any>;
|
|
96
110
|
/** Picks the session's model when ALP was given one and the agent offers a choice. */
|
|
97
111
|
private chooseModel;
|
|
112
|
+
/** The tokens of the turn the prompt response says (ACP's unstable usage), before its end. */
|
|
113
|
+
private turnUsage;
|
|
98
114
|
private endTurn;
|
|
99
115
|
private closeMessage;
|
|
100
116
|
private update;
|
package/dist/acp-transport.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
10
|
import { commandDecision, unwrapShell } from "@anhlp/alp-core/permissions";
|
|
11
11
|
import { launchCommand } from "@anhlp/alp-core/providers";
|
|
12
|
+
import { acpText, acpToolDetail } from "./tool-detail.js";
|
|
12
13
|
var BRIDGE = `
|
|
13
14
|
const net = require('node:net');
|
|
14
15
|
const readline = require('node:readline');
|
|
@@ -92,6 +93,11 @@ function acpServers(servers, http) {
|
|
|
92
93
|
return { name, command: server.command, args: server.args ?? [], env: Object.entries(server.env ?? {}).map(([key, value]) => ({ name: key, value: String(value) })) };
|
|
93
94
|
});
|
|
94
95
|
}
|
|
96
|
+
function acpContent(part) {
|
|
97
|
+
if (typeof part?.text === "string") return { type: "text", text: part.text };
|
|
98
|
+
const image = part?.type === "image" && typeof part.url === "string" ? /^data:(image\/(?:png|jpeg|gif|webp));base64,([A-Za-z0-9+/]+={0,2})$/.exec(part.url) : null;
|
|
99
|
+
return image ? { type: "image", mimeType: image[1], data: image[2] } : null;
|
|
100
|
+
}
|
|
95
101
|
var AcpTransport = class {
|
|
96
102
|
constructor(provider, cwd, env) {
|
|
97
103
|
this.provider = provider;
|
|
@@ -107,6 +113,10 @@ var AcpTransport = class {
|
|
|
107
113
|
requestHandler;
|
|
108
114
|
closed = false;
|
|
109
115
|
capabilities = {};
|
|
116
|
+
/** ACP agents say in initialize whether a prompt may hold images (promptCapabilities.image). */
|
|
117
|
+
takesImages() {
|
|
118
|
+
return this.capabilities?.promptCapabilities?.image === true;
|
|
119
|
+
}
|
|
110
120
|
sessionId = "";
|
|
111
121
|
config;
|
|
112
122
|
/** Instructions go with the first prompt of a new session; a loaded one has them already. */
|
|
@@ -191,7 +201,8 @@ var AcpTransport = class {
|
|
|
191
201
|
const turnId = randomUUID();
|
|
192
202
|
this.activeTurn = turnId;
|
|
193
203
|
this.message = void 0;
|
|
194
|
-
const prompt = (params.input ?? []).
|
|
204
|
+
const prompt = (params.input ?? []).map(acpContent).filter(Boolean);
|
|
205
|
+
if (prompt.some((part) => part.type === "image") && !this.takesImages()) throw new Error(`${this.provider.label} takes no images`);
|
|
195
206
|
if (!this.instructionsSent && this.config?.developerInstructions) {
|
|
196
207
|
prompt.unshift({ type: "text", text: `Instructions from ALP, which runs you. Follow them for this whole session.
|
|
197
208
|
|
|
@@ -199,7 +210,10 @@ ${this.config.developerInstructions}` });
|
|
|
199
210
|
this.instructionsSent = true;
|
|
200
211
|
}
|
|
201
212
|
this.prompting = this.call("session/prompt", { sessionId: this.sessionId, prompt }).then(
|
|
202
|
-
(result) =>
|
|
213
|
+
(result) => {
|
|
214
|
+
this.turnUsage(turnId, result?.usage);
|
|
215
|
+
this.endTurn(turnId, result?.stopReason);
|
|
216
|
+
},
|
|
203
217
|
(error) => this.endTurn(turnId, "error", error instanceof Error ? error.message : String(error))
|
|
204
218
|
).finally(() => {
|
|
205
219
|
if (this.activeTurn === void 0 || this.activeTurn === turnId) this.prompting = void 0;
|
|
@@ -234,6 +248,12 @@ ${this.config.developerInstructions}` });
|
|
|
234
248
|
if (!models.availableModels.some((model) => model?.modelId === wanted)) throw new Error(`${this.provider.label} has no model '${wanted}'`);
|
|
235
249
|
await this.call("session/set_model", { sessionId: this.sessionId, modelId: wanted }, 3e4);
|
|
236
250
|
}
|
|
251
|
+
/** The tokens of the turn the prompt response says (ACP's unstable usage), before its end. */
|
|
252
|
+
turnUsage(turnId, usage) {
|
|
253
|
+
if (this.closed || this.activeTurn !== turnId || !usage || typeof usage !== "object") return;
|
|
254
|
+
const last = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, cachedInputTokens: usage.cachedReadTokens };
|
|
255
|
+
this.emit("thread/tokenUsage/updated", { threadId: this.sessionId, turnId, tokenUsage: { last } });
|
|
256
|
+
}
|
|
237
257
|
endTurn(turnId, stopReason, error) {
|
|
238
258
|
if (this.closed || this.activeTurn !== turnId) return;
|
|
239
259
|
this.closeMessage();
|
|
@@ -264,22 +284,34 @@ ${this.config.developerInstructions}` });
|
|
|
264
284
|
const id = String(update.toolCallId ?? "");
|
|
265
285
|
if (!id) return;
|
|
266
286
|
let call = this.toolCalls.get(id);
|
|
287
|
+
const first = !call;
|
|
288
|
+
if (call?.done) return;
|
|
267
289
|
if (!call) {
|
|
268
290
|
const request = acpPermission(update, this.tools.map((tool) => tool.name));
|
|
269
|
-
call = { command: request.command ?? `${request.kind}: ${request.title}`, alp: request.alpTool };
|
|
291
|
+
call = { command: request.command ?? `${request.kind}: ${request.title}`, alp: request.alpTool, state: {} };
|
|
270
292
|
this.toolCalls.set(id, call);
|
|
271
293
|
this.closeMessage();
|
|
272
|
-
if (!call.alp) this.emit("item/started", { threadId: this.sessionId, item: { type: "commandExecution", id, command: call.command, cwd: this.cwd, status: "inProgress", aggregatedOutput: "" } });
|
|
273
294
|
}
|
|
274
|
-
|
|
295
|
+
for (const field of ["kind", "title", "rawInput", "rawOutput", "locations", "content"]) if (update[field] !== void 0 && update[field] !== null) call.state[field] = update[field];
|
|
296
|
+
if (call.alp) return;
|
|
297
|
+
const detail = acpToolDetail(call.state);
|
|
298
|
+
const typed = detail ? { tool: call.state.title || call.state.kind || "tool", detail } : {};
|
|
299
|
+
const done = update.status === "completed" || update.status === "failed";
|
|
300
|
+
if (first) this.emit("item/started", { threadId: this.sessionId, item: { type: "commandExecution", id, command: call.command, cwd: this.cwd, status: "inProgress", aggregatedOutput: "", ...typed } });
|
|
301
|
+
else if (!done && detail) this.emit("item/updated", { threadId: this.sessionId, item: { type: "commandExecution", id, command: call.command, cwd: this.cwd, status: "inProgress", aggregatedOutput: "", ...typed } });
|
|
302
|
+
if (done) {
|
|
303
|
+
call.state = {};
|
|
304
|
+
call.done = true;
|
|
275
305
|
const output = (Array.isArray(update.content) ? update.content : []).map((part) => part?.type === "content" && part.content?.type === "text" ? part.content.text : part?.type === "diff" ? `diff ${part.path}` : "").filter(Boolean).join("\n");
|
|
276
|
-
|
|
306
|
+
const error = update.status === "failed" && detail ? { error: acpText(update.content) ?? (typeof update.rawOutput === "string" ? update.rawOutput : void 0) } : {};
|
|
307
|
+
this.emit("item/completed", { threadId: this.sessionId, item: { type: "commandExecution", id, command: call.command, cwd: this.cwd, status: update.status, exitCode: update.status === "failed" ? 1 : 0, aggregatedOutput: output, ...typed, ...error } });
|
|
277
308
|
}
|
|
278
309
|
return;
|
|
279
310
|
}
|
|
280
311
|
if (kind === "usage_update" && typeof update.used === "number") {
|
|
281
312
|
const last = { totalTokens: update.used };
|
|
282
|
-
|
|
313
|
+
const cost = update.cost?.currency === "USD" && typeof update.cost.amount === "number" ? { totalCostUsd: update.cost.amount } : {};
|
|
314
|
+
this.emit("thread/tokenUsage/updated", { threadId: this.sessionId, turnId: this.activeTurn, tokenUsage: { last, total: last, ...typeof update.size === "number" ? { modelContextWindow: update.size } : {}, ...cost } });
|
|
283
315
|
}
|
|
284
316
|
}
|
|
285
317
|
/** The agent asks before using a tool; ALP's runtime decides, and the answer picks one of the agent's options. */
|
|
@@ -471,6 +503,7 @@ ${this.config.developerInstructions}` });
|
|
|
471
503
|
};
|
|
472
504
|
export {
|
|
473
505
|
AcpTransport,
|
|
506
|
+
acpContent,
|
|
474
507
|
acpDecision,
|
|
475
508
|
acpEnv,
|
|
476
509
|
acpPermission,
|
|
@@ -33,6 +33,8 @@ export declare class AntigravityTransport {
|
|
|
33
33
|
private readonly cwd;
|
|
34
34
|
private readonly env;
|
|
35
35
|
private readonly provider?;
|
|
36
|
+
/** Antigravity's prompt is one line of text: no images. */
|
|
37
|
+
takesImages(): boolean;
|
|
36
38
|
private child?;
|
|
37
39
|
private lines?;
|
|
38
40
|
private exit;
|
|
@@ -8,6 +8,7 @@ import os from "node:os";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
10
|
import { openMcpTools } from "./mcp-tools.js";
|
|
11
|
+
import { antigravityToolDetail } from "./tool-detail.js";
|
|
11
12
|
var BRIDGE = `// ALP Antigravity bridge v1
|
|
12
13
|
|
|
13
14
|
const net = require('node:net');
|
|
@@ -53,6 +54,10 @@ var AntigravityTransport = class {
|
|
|
53
54
|
this.env = env;
|
|
54
55
|
this.provider = provider;
|
|
55
56
|
}
|
|
57
|
+
/** Antigravity's prompt is one line of text: no images. */
|
|
58
|
+
takesImages() {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
56
61
|
child;
|
|
57
62
|
lines;
|
|
58
63
|
exit = Promise.resolve();
|
|
@@ -331,6 +336,8 @@ ${promptText}`;
|
|
|
331
336
|
if (step.step_type === "tool") {
|
|
332
337
|
const toolName = step.tool_name || "tool";
|
|
333
338
|
const callId = `agy-tool-${this.activeTurn}-${step.step_index ?? ++this.messages}`;
|
|
339
|
+
const detail = antigravityToolDetail(step.tool_info ?? { name: toolName });
|
|
340
|
+
const typed = { tool: typeof step.tool_info?.name === "string" && step.tool_info.name ? step.tool_info.name : toolName, detail };
|
|
334
341
|
if (step.state === "ACTIVE") {
|
|
335
342
|
this.emit("item/started", {
|
|
336
343
|
threadId: this.conversationId,
|
|
@@ -340,7 +347,8 @@ ${promptText}`;
|
|
|
340
347
|
command: toolName,
|
|
341
348
|
cwd: this.cwd,
|
|
342
349
|
status: "inProgress",
|
|
343
|
-
aggregatedOutput: ""
|
|
350
|
+
aggregatedOutput: "",
|
|
351
|
+
...typed
|
|
344
352
|
}
|
|
345
353
|
});
|
|
346
354
|
} else if (step.state === "DONE" || step.state === "ERROR") {
|
|
@@ -354,7 +362,9 @@ ${promptText}`;
|
|
|
354
362
|
cwd: this.cwd,
|
|
355
363
|
status: step.state === "DONE" ? "completed" : "failed",
|
|
356
364
|
exitCode: step.state === "DONE" ? 0 : 1,
|
|
357
|
-
aggregatedOutput: output
|
|
365
|
+
aggregatedOutput: output,
|
|
366
|
+
...typed,
|
|
367
|
+
...step.state === "ERROR" ? { error: step.tool_info?.error?.message } : {}
|
|
358
368
|
}
|
|
359
369
|
});
|
|
360
370
|
}
|
|
@@ -24,6 +24,20 @@ type PermissionRules = {
|
|
|
24
24
|
/** Whether Claude Code can sandbox Bash here: Seatbelt on macOS, bubblewrap and socat on Linux. ALP_CLAUDE_SANDBOX=0 turns it off, =1 claims it (tests). */
|
|
25
25
|
export declare function claudeSandboxAvailable(): boolean;
|
|
26
26
|
/** What ALP asks the user for a permission Claude would otherwise not have. */
|
|
27
|
+
/** One turn-input part as Claude's: text, or a data-URL image as a base64 image block. */
|
|
28
|
+
export declare function claudeContent(part: any): {
|
|
29
|
+
type: string;
|
|
30
|
+
source: {
|
|
31
|
+
type: string;
|
|
32
|
+
media_type: string;
|
|
33
|
+
data: string;
|
|
34
|
+
};
|
|
35
|
+
text?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
type: string;
|
|
38
|
+
text: any;
|
|
39
|
+
source?: undefined;
|
|
40
|
+
};
|
|
27
41
|
export type PermissionRequest = {
|
|
28
42
|
tool: string;
|
|
29
43
|
input: Record<string, unknown>;
|
package/dist/claude-transport.js
CHANGED
|
@@ -7,6 +7,7 @@ import { optionalRead, claudeUsage } from "./runtime-context.js";
|
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { PIN_KINDS } from "./board.js";
|
|
10
|
+
import { claudeToolDetail, resultText } from "./tool-detail.js";
|
|
10
11
|
import { CLOSE_REASONS, GATE_KINDS, TASK_STATUSES, TASK_TYPES } from "@anhlp/alp-core/tasks";
|
|
11
12
|
import { commandDecision } from "@anhlp/alp-core/permissions";
|
|
12
13
|
var CLAUDE_SDK = ["@anthropic-ai", "claude-agent-sdk"].join("/");
|
|
@@ -158,6 +159,11 @@ function claudeSandboxAvailable() {
|
|
|
158
159
|
return onPath("bwrap") && onPath("socat");
|
|
159
160
|
}
|
|
160
161
|
var WRITERS = ["Edit", "Write", "MultiEdit", "NotebookEdit"];
|
|
162
|
+
function claudeContent(part) {
|
|
163
|
+
const image = part?.type === "image" && typeof part.url === "string" ? /^data:(image\/(?:png|jpeg|gif|webp));base64,([A-Za-z0-9+/]+={0,2})$/.exec(part.url) : null;
|
|
164
|
+
if (part?.type === "image" && !image) throw new Error("Claude takes images as inline base64 PNG, JPEG, GIF or WebP only");
|
|
165
|
+
return image ? { type: "image", source: { type: "base64", media_type: image[1], data: image[2] } } : { type: "text", text: part.text };
|
|
166
|
+
}
|
|
161
167
|
function suggestedRule(tool, input, suggestions) {
|
|
162
168
|
const suggestion = (Array.isArray(suggestions) ? suggestions : []).find((entry) => entry?.type === "addRules" && entry.behavior === "allow" && entry.rules?.length);
|
|
163
169
|
const rule = suggestion?.rules[0];
|
|
@@ -365,10 +371,7 @@ var ClaudeTransport = class {
|
|
|
365
371
|
type: "user",
|
|
366
372
|
message: {
|
|
367
373
|
role: "user",
|
|
368
|
-
content: params.input.map(
|
|
369
|
-
type: "text",
|
|
370
|
-
text: part.text
|
|
371
|
-
}))
|
|
374
|
+
content: params.input.map(claudeContent)
|
|
372
375
|
},
|
|
373
376
|
parent_tool_use_id: null,
|
|
374
377
|
uuid: randomUUID(),
|
|
@@ -558,6 +561,8 @@ var ClaudeTransport = class {
|
|
|
558
561
|
});
|
|
559
562
|
}
|
|
560
563
|
if (message.type === "result") {
|
|
564
|
+
const cost = message.total_cost_usd;
|
|
565
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) this.emit("thread/tokenUsage/updated", { threadId: this.threadId, turnId: this.activeTurn, tokenUsage: { totalCostUsd: cost } });
|
|
561
566
|
const windows = Object.values(message.modelUsage ?? {}).map((entry) => entry?.contextWindow).filter((value) => typeof value === "number" && value > 0);
|
|
562
567
|
if (windows.length && !this.compactAt) this.contextWindow = Math.max(...windows);
|
|
563
568
|
}
|
|
@@ -573,7 +578,8 @@ var ClaudeTransport = class {
|
|
|
573
578
|
});
|
|
574
579
|
} else if (block.type === "tool_use") {
|
|
575
580
|
const command = block.name === "Bash" ? String(block.input?.command ?? "") : `${block.name} ${JSON.stringify(block.input ?? {})}`;
|
|
576
|
-
this.toolCalls.set(block.id, { name: block.name, command });
|
|
581
|
+
this.toolCalls.set(block.id, { name: block.name, command, input: block.input });
|
|
582
|
+
const detail = claudeToolDetail(block.name, block.input);
|
|
577
583
|
this.emit("item/started", {
|
|
578
584
|
threadId: this.threadId,
|
|
579
585
|
item: {
|
|
@@ -582,7 +588,8 @@ var ClaudeTransport = class {
|
|
|
582
588
|
command,
|
|
583
589
|
cwd: this.cwd,
|
|
584
590
|
status: "inProgress",
|
|
585
|
-
aggregatedOutput: ""
|
|
591
|
+
aggregatedOutput: "",
|
|
592
|
+
...detail ? { tool: block.name, detail } : {}
|
|
586
593
|
}
|
|
587
594
|
});
|
|
588
595
|
}
|
|
@@ -591,11 +598,14 @@ var ClaudeTransport = class {
|
|
|
591
598
|
}
|
|
592
599
|
if (message.type === "user") {
|
|
593
600
|
const content = Array.isArray(message.message.content) ? message.message.content : [];
|
|
594
|
-
|
|
595
|
-
|
|
601
|
+
const results = content.filter((block) => block.type === "tool_result");
|
|
602
|
+
const structured = results.length === 1 ? message.tool_use_result : void 0;
|
|
603
|
+
for (const block of results) {
|
|
596
604
|
const call = this.toolCalls.get(block.tool_use_id);
|
|
597
605
|
if (!call) continue;
|
|
598
606
|
const output = typeof block.content === "string" ? block.content : JSON.stringify(block.content ?? "");
|
|
607
|
+
const text = resultText(block.content);
|
|
608
|
+
const detail = claudeToolDetail(call.name, call.input, { text, structured, error: Boolean(block.is_error) });
|
|
599
609
|
this.emit("item/completed", {
|
|
600
610
|
threadId: this.threadId,
|
|
601
611
|
item: {
|
|
@@ -605,7 +615,8 @@ var ClaudeTransport = class {
|
|
|
605
615
|
cwd: this.cwd,
|
|
606
616
|
status: block.is_error ? "failed" : "completed",
|
|
607
617
|
exitCode: block.is_error ? 1 : 0,
|
|
608
|
-
aggregatedOutput: output
|
|
618
|
+
aggregatedOutput: output,
|
|
619
|
+
...detail ? { tool: call.name, detail, ...block.is_error ? { error: text } : {} } : {}
|
|
609
620
|
}
|
|
610
621
|
});
|
|
611
622
|
this.toolCalls.delete(block.tool_use_id);
|
|
@@ -662,6 +673,7 @@ var ClaudeTransport = class {
|
|
|
662
673
|
export {
|
|
663
674
|
ClaudeTransport,
|
|
664
675
|
SKILL_PLUGIN,
|
|
676
|
+
claudeContent,
|
|
665
677
|
claudeLimited,
|
|
666
678
|
claudePermissionMode,
|
|
667
679
|
claudePermissions,
|
package/dist/contracts.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export type RuntimeTransport = {
|
|
|
15
15
|
onFailure(listener: (error: unknown) => void): void;
|
|
16
16
|
close(): Promise<void>;
|
|
17
17
|
onRequest?: (listener: (method: string, params: any) => Promise<unknown>) => void;
|
|
18
|
+
/** Whether turn input may hold images (`{ type: 'image', url: 'data:…' }`); omitted, Codex and Claude do. */
|
|
19
|
+
takesImages?: () => boolean;
|
|
18
20
|
};
|
|
19
21
|
export type RuntimeOptions = {
|
|
20
22
|
/** Native adapter overrides, isolated to this runtime; the legacy transport hook takes precedence. */
|
|
@@ -155,8 +157,12 @@ export type AlpRuntime = {
|
|
|
155
157
|
};
|
|
156
158
|
import type { SessionPreview } from '@anhlp/alp-protocol/session';
|
|
157
159
|
export type { SessionPreview } from '@anhlp/alp-protocol/session';
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
+
/**
|
|
161
|
+
* The team `alp init` names in a new project's settings (core INIT_TEAM). Kept for compatibility;
|
|
162
|
+
* it is not the default of a session: preview and create both resolve that through core
|
|
163
|
+
* `defaultTeam` (settings without workflow.mode run `custom`).
|
|
164
|
+
*/
|
|
165
|
+
export { INIT_TEAM as DEFAULT_TEAM } from '@anhlp/alp-core/workflow';
|
|
160
166
|
import type { RecoveryOutcome, Pause, PauseState } from '@anhlp/alp-protocol/session';
|
|
161
167
|
export type { RecoveryOutcome, Pause, PauseState } from '@anhlp/alp-protocol/session';
|
|
162
168
|
import type { RecallAnswer } from '@anhlp/alp-protocol/session';
|
package/dist/contracts.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -22,3 +22,5 @@ export type { Words } from './language.js';
|
|
|
22
22
|
export type { Pin, PinKind } from './board.js';
|
|
23
23
|
export { ProviderRegistry, createProviderRegistry } from './providers.js';
|
|
24
24
|
export type { ProviderContext, ProviderFactory, ProviderFactories } from './providers.js';
|
|
25
|
+
export { acpToolDetail, antigravityToolDetail, boundDetail, claudeToolDetail, clipText, codexToolCall, stripReadGutter, toolError, DETAIL_FILE_PATHS, DETAIL_TEXT_CHARS, DETAIL_WEB_RESULTS, ERROR_CHARS, TOOL_FAILED } from './tool-detail.js';
|
|
26
|
+
export type { AcpCall, ClaudeResult, CodexToolCall, ToolCallDetail } from './tool-detail.js';
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { OWN_START, processStartedAt, sameProcessAlive } from "./process-info.js
|
|
|
13
13
|
import { PIN_KINDS, renderBoard, renderPin } from "./board.js";
|
|
14
14
|
import { CHOICES, isVietnamese, languageInstruction, words } from "./language.js";
|
|
15
15
|
import { ProviderRegistry, createProviderRegistry } from "./providers.js";
|
|
16
|
+
import { acpToolDetail, antigravityToolDetail, boundDetail, claudeToolDetail, clipText, codexToolCall, stripReadGutter, toolError, DETAIL_FILE_PATHS, DETAIL_TEXT_CHARS, DETAIL_WEB_RESULTS, ERROR_CHARS, TOOL_FAILED } from "./tool-detail.js";
|
|
16
17
|
export {
|
|
17
18
|
AcpTransport,
|
|
18
19
|
AntigravityTransport,
|
|
@@ -22,6 +23,10 @@ export {
|
|
|
22
23
|
DEFAULT_CLAUDE_MODEL,
|
|
23
24
|
DEFAULT_MODEL,
|
|
24
25
|
DEFAULT_TEAM,
|
|
26
|
+
DETAIL_FILE_PATHS,
|
|
27
|
+
DETAIL_TEXT_CHARS,
|
|
28
|
+
DETAIL_WEB_RESULTS,
|
|
29
|
+
ERROR_CHARS,
|
|
25
30
|
InstructionsAdapter,
|
|
26
31
|
MAIL_BATCH_CHARS,
|
|
27
32
|
MAIL_BODY_CHARS,
|
|
@@ -29,13 +34,20 @@ export {
|
|
|
29
34
|
PIN_KINDS,
|
|
30
35
|
ProviderRegistry,
|
|
31
36
|
SKILL_PLUGIN,
|
|
37
|
+
TOOL_FAILED,
|
|
32
38
|
USER,
|
|
33
39
|
acpDecision,
|
|
34
40
|
acpEnv,
|
|
35
41
|
acpPermission,
|
|
42
|
+
acpToolDetail,
|
|
43
|
+
antigravityToolDetail,
|
|
44
|
+
boundDetail,
|
|
36
45
|
claudePermissions,
|
|
37
46
|
claudeSandboxAvailable,
|
|
47
|
+
claudeToolDetail,
|
|
38
48
|
toolShapes as claudeToolShapes,
|
|
49
|
+
clipText,
|
|
50
|
+
codexToolCall,
|
|
39
51
|
createAlpRuntime,
|
|
40
52
|
createCopy,
|
|
41
53
|
createProviderRegistry,
|
|
@@ -56,9 +68,11 @@ export {
|
|
|
56
68
|
sameProcessAlive,
|
|
57
69
|
skillDescription,
|
|
58
70
|
skillPlugin,
|
|
71
|
+
stripReadGutter,
|
|
59
72
|
takeBatch,
|
|
60
73
|
thinkingOptions,
|
|
61
74
|
thinkingOptionsFor,
|
|
75
|
+
toolError,
|
|
62
76
|
usageAllows,
|
|
63
77
|
words
|
|
64
78
|
};
|
package/dist/resolve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// packages/runtime/src/resolve.ts
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { access, readFile, stat } from "node:fs/promises";
|
|
4
|
-
import { resolveWorkflow } from "@anhlp/alp-core/workflow";
|
|
4
|
+
import { defaultTeam, resolveWorkflow } from "@anhlp/alp-core/workflow";
|
|
5
5
|
import { initProject } from "@anhlp/alp-core/init";
|
|
6
6
|
import { profileModel, agentProfiles, selectedProfile } from "@anhlp/alp-core/profiles";
|
|
7
7
|
import { jsonObject, resolveAgent } from "@anhlp/alp-core/resolver";
|
|
@@ -68,13 +68,13 @@ async function resolveSession(spec, options = {}) {
|
|
|
68
68
|
if (scaffoldMissing) await initProject(spec.cwd, { ...starter, library: options.library });
|
|
69
69
|
else if (!spec.restore) {
|
|
70
70
|
const settings = await jsonObject(settingsFile, "INVALID_SETTINGS");
|
|
71
|
-
await initProject(spec.cwd, { ...starter, library: options.library, onlyManagedProfiles: true, teamIds: [
|
|
71
|
+
await initProject(spec.cwd, { ...starter, library: options.library, onlyManagedProfiles: true, teamIds: [spec.workflow === void 0 ? defaultTeam(settings) : teamOf(spec.workflow)] });
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
if (options.library) await ensureLibrary(options.library, options.templates ? { templates: options.templates } : {});
|
|
75
75
|
const restored = spec.restore;
|
|
76
76
|
if (restored && spec.agent !== void 0 && spec.agent !== restored.agent) throw new Error("Cannot resume a thread as a different ALP agent");
|
|
77
|
-
const { team, ...workflow } = await resolveWorkflow(spec.cwd, spec.workflow, restored?.workflow, { library: options.library, templates: options.templates });
|
|
77
|
+
const { team, ...workflow } = await resolveWorkflow(spec.cwd, spec.workflow, restored?.workflow, { library: options.library, templates: options.templates, scaffold: options.initialize === false });
|
|
78
78
|
const agent = await resolveAgent(spec.cwd, { agent: spec.agent ?? restored?.agent, library: options.library, templates: options.templates });
|
|
79
79
|
const oracleModels = team?.oracleModels ?? ORACLE_MODELS;
|
|
80
80
|
if (agent.name === "oracle" && !restored && !oracleModels.includes(spec.model ?? "")) throw new Error(`Oracle runs on ${oracleModels.join(" or ")}; choose one`);
|
package/dist/runtime.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
3
|
import { errorData, plainObject, toolResult } from "./tool-results.js";
|
|
4
4
|
import { createLogger } from "@anhlp/alp-core/log";
|
|
5
|
+
import { PROMPT_IMAGES, base64Bytes } from "@anhlp/alp-protocol";
|
|
5
6
|
import { createBoardDomain } from "./board-domain.js";
|
|
6
7
|
import { createWorktreeDomain } from "./worktree-domain.js";
|
|
7
8
|
import { DEFAULT_TEAM } from "./contracts.js";
|
|
8
|
-
import { DEFAULT_TEAM as DEFAULT_TEAM2 } from "./contracts.js";
|
|
9
9
|
import { verdictLine, parseHandoff } from "./handoff.js";
|
|
10
10
|
import { TASK_ACTIONS, TASK_PAST, TASK_FIELDS, mainOf, roleOf, taskActions, taskBrief } from "./tools.js";
|
|
11
11
|
import { nativeEnvironment, createTransport } from "./providers.js";
|
|
@@ -36,9 +36,10 @@ import { OWN_START, sameProcessAlive } from "./process-info.js";
|
|
|
36
36
|
import { createRecallBook, recallPrompt, RECALL_KEEP_MS, RECALL_QUESTION_CHARS, RECALL_TIMEOUT_MS } from "./recall.js";
|
|
37
37
|
import { branchExists, checkoutFingerprint, checkoutKey, createCopy, createWorktree, reattachWorktree, removeCopy, removeWorktree } from "./workspace.js";
|
|
38
38
|
import { claudeSandboxAvailable, SKILL_PLUGIN } from "./claude-transport.js";
|
|
39
|
+
import { boundDetail, codexToolCall, toolError } from "./tool-detail.js";
|
|
39
40
|
import { ALP_REPO, gh, projectRepo } from "./github.js";
|
|
40
41
|
import { optionalRead, usageAllows, COMPACT_SHARE } from "./runtime-context.js";
|
|
41
|
-
import { providerUsage } from "./usage.js";
|
|
42
|
+
import { providerUsage, sessionUsage } from "./usage.js";
|
|
42
43
|
import { parse as toml } from "smol-toml";
|
|
43
44
|
import { findFormula, formulaDirs, listFormulas, pourFormula } from "@anhlp/alp-core/formulas";
|
|
44
45
|
import { addAllowRule, capMode, commandDecision, profileFor, unwrapShell } from "@anhlp/alp-core/permissions";
|
|
@@ -119,6 +120,9 @@ function createAlpRuntime(options = {}) {
|
|
|
119
120
|
});
|
|
120
121
|
return run;
|
|
121
122
|
}
|
|
123
|
+
function takesImages(session) {
|
|
124
|
+
return session.runtime.takesImages ? session.runtime.takesImages() : session.runtimeKind === "codex" || session.runtimeKind === "claude";
|
|
125
|
+
}
|
|
122
126
|
function snapshot(sessionId, session) {
|
|
123
127
|
return {
|
|
124
128
|
id: sessionId,
|
|
@@ -137,6 +141,8 @@ function createAlpRuntime(options = {}) {
|
|
|
137
141
|
...session.active ? { activeTurnId: session.active } : {},
|
|
138
142
|
...session.parked ? { parked: session.parked.reason } : {},
|
|
139
143
|
...session.instructionsSha ? { instructionsSha: session.instructionsSha } : {},
|
|
144
|
+
...takesImages(session) ? { promptImages: true } : {},
|
|
145
|
+
...session.usage ? { usage: session.usage } : {},
|
|
140
146
|
busy: !!(session.active || session.pending || session.children.size || session.assignments.size || hasActiveMail(session) || reviewing(session))
|
|
141
147
|
};
|
|
142
148
|
}
|
|
@@ -186,7 +192,7 @@ function createAlpRuntime(options = {}) {
|
|
|
186
192
|
}
|
|
187
193
|
session.settle?.(state, error);
|
|
188
194
|
}
|
|
189
|
-
function nativeItem(sessionId, session, item) {
|
|
195
|
+
function nativeItem(sessionId, session, item, done = false) {
|
|
190
196
|
if (item.type === "agentMessage") {
|
|
191
197
|
if (session.userWaiting !== void 0 && String(item.text ?? "").trim()) {
|
|
192
198
|
if (session.supervisor) session.journal.push(stamp(`${session.mapping.agent.name} answered the user ${span(Date.now() - session.userWaiting)} after their message of ${clock(session.userWaiting)}`));
|
|
@@ -198,7 +204,8 @@ function createAlpRuntime(options = {}) {
|
|
|
198
204
|
}
|
|
199
205
|
if (item.type === "userMessage") {
|
|
200
206
|
const text2 = (item.content ?? []).filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
201
|
-
|
|
207
|
+
const images = (item.content ?? []).filter((c) => c.type === "image" || c.type === "localImage").length;
|
|
208
|
+
emit(sessionId, { type: "item", item: { kind: "user_message", id: item.id, text: text2, ...images ? { images } : {} } });
|
|
202
209
|
return;
|
|
203
210
|
}
|
|
204
211
|
if (item.type === "dynamicToolCall") {
|
|
@@ -229,24 +236,33 @@ function createAlpRuntime(options = {}) {
|
|
|
229
236
|
if (session.supervisor && status !== "running") {
|
|
230
237
|
session.journal.push(stamp(`${session.mapping.agent.name} shell ${status}${item.exitCode != null ? ` (exit ${item.exitCode})` : ""}: ${clip(String(item.command ?? ""))}`));
|
|
231
238
|
}
|
|
239
|
+
const typed = item.detail && typeof item.detail === "object" && item.detail.type !== "shell" ? boundDetail(item.detail) : void 0;
|
|
232
240
|
emit(sessionId, {
|
|
233
241
|
type: "item",
|
|
234
242
|
item: {
|
|
235
243
|
kind: "tool_call",
|
|
236
244
|
id: item.id,
|
|
237
245
|
callId: item.id,
|
|
238
|
-
name: "shell",
|
|
246
|
+
name: typed ? String(item.tool || "tool") : "shell",
|
|
239
247
|
status,
|
|
240
|
-
...status === "failed" ? { error: "Command failed" } : {},
|
|
241
|
-
detail: {
|
|
248
|
+
...status === "failed" ? { error: typed ? toolError(item.error) : "Command failed" } : {},
|
|
249
|
+
detail: typed ?? (item.detail?.type === "shell" ? item.detail : {
|
|
242
250
|
type: "shell",
|
|
243
251
|
command: item.command,
|
|
244
252
|
cwd: item.cwd,
|
|
245
253
|
output: item.aggregatedOutput ?? "",
|
|
246
254
|
exitCode: item.exitCode
|
|
247
|
-
}
|
|
255
|
+
})
|
|
248
256
|
}
|
|
249
257
|
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const call = codexToolCall(item, done);
|
|
261
|
+
if (call) {
|
|
262
|
+
emit(sessionId, {
|
|
263
|
+
type: "item",
|
|
264
|
+
item: { kind: "tool_call", id: item.id, callId: item.id, name: call.name, status: call.status, ...call.error ? { error: call.error } : {}, detail: call.detail }
|
|
265
|
+
});
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
268
|
function notification(sessionId, session, method, params) {
|
|
@@ -270,17 +286,26 @@ function createAlpRuntime(options = {}) {
|
|
|
270
286
|
if (method === "item/completed" || method === "item/started") {
|
|
271
287
|
if (method === "item/completed") session.progressAt = Date.now();
|
|
272
288
|
if (params.item?.type !== "userMessage") {
|
|
273
|
-
nativeItem(sessionId, session, params.item);
|
|
289
|
+
nativeItem(sessionId, session, params.item, method === "item/completed");
|
|
274
290
|
}
|
|
275
291
|
if (method === "item/started" && params.item?.type === "commandExecution") watchCopy(sessionId, session, params.item);
|
|
276
292
|
if (method === "item/started" && params.item) skillUse(sessionId, session, params.item);
|
|
277
293
|
return;
|
|
278
294
|
}
|
|
295
|
+
if (method === "item/updated") {
|
|
296
|
+
if (params.item?.type === "commandExecution" && params.item.status === "inProgress") nativeItem(sessionId, session, params.item);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
279
299
|
if (method === "account/rateLimits/updated") {
|
|
280
300
|
usageReport(session, params);
|
|
281
301
|
return;
|
|
282
302
|
}
|
|
283
303
|
if (method === "thread/tokenUsage/updated") {
|
|
304
|
+
const usage2 = sessionUsage(session.usage, params?.tokenUsage);
|
|
305
|
+
if (usage2) {
|
|
306
|
+
session.usage = usage2;
|
|
307
|
+
emit(sessionId, { type: "usage", usage: usage2 });
|
|
308
|
+
}
|
|
284
309
|
contextReport(sessionId, session, params?.tokenUsage);
|
|
285
310
|
return;
|
|
286
311
|
}
|
|
@@ -2656,12 +2681,20 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
|
|
|
2656
2681
|
)) {
|
|
2657
2682
|
return;
|
|
2658
2683
|
}
|
|
2659
|
-
if (prompt.content.some(
|
|
2660
|
-
(
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
);
|
|
2684
|
+
if (prompt.content.some((content) => content.type !== "text" && content.type !== "image")) {
|
|
2685
|
+
throw new Error("Only text and image prompts are supported");
|
|
2686
|
+
}
|
|
2687
|
+
const images = prompt.content.filter((content) => content.type === "image");
|
|
2688
|
+
if (images.length) {
|
|
2689
|
+
if (!takesImages(session)) throw new Error(`${session.runtimeKind === "acp" ? "This ACP agent" : session.runtimeKind[0].toUpperCase() + session.runtimeKind.slice(1)} takes no images; send the message as text`);
|
|
2690
|
+
if (images.length > PROMPT_IMAGES.maxCount) throw new Error(`A prompt takes at most ${PROMPT_IMAGES.maxCount} images`);
|
|
2691
|
+
if (images.reduce((sum, image) => sum + (typeof image.data === "string" ? base64Bytes(image.data) : 0), 0) > PROMPT_IMAGES.maxTotalBytes) throw new Error(`The images of a prompt are at most ${PROMPT_IMAGES.maxTotalBytes} bytes together`);
|
|
2692
|
+
for (const image of images) {
|
|
2693
|
+
const keys = Object.keys(image).filter((key) => !["type", "mimeType", "data", "name"].includes(key));
|
|
2694
|
+
if (keys.length || !PROMPT_IMAGES.mimeTypes.includes(image.mimeType) || typeof image.data !== "string" || !/^[A-Za-z0-9+/]+={0,2}$/.test(image.data) || base64Bytes(image.data) > PROMPT_IMAGES.maxBytes) {
|
|
2695
|
+
throw new Error(`An image must be inline base64 ${PROMPT_IMAGES.mimeTypes.join(", ")} of at most ${PROMPT_IMAGES.maxBytes} bytes`);
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2665
2698
|
}
|
|
2666
2699
|
if (prompt.delivery === "steer" && !session.active) {
|
|
2667
2700
|
throw new Error(
|
|
@@ -2673,9 +2706,7 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
|
|
|
2673
2706
|
"Turn already active; use steering or interrupt first"
|
|
2674
2707
|
);
|
|
2675
2708
|
}
|
|
2676
|
-
const text2 = prompt.content.map(
|
|
2677
|
-
(content) => content.text ?? ""
|
|
2678
|
-
).join("\n");
|
|
2709
|
+
const text2 = prompt.content.filter((content) => content.type === "text").map((content) => content.text ?? "").join("\n");
|
|
2679
2710
|
const mail = wake ?? (prompt.delivery === "steer" ? [] : takeBatch(session.mail, () => true));
|
|
2680
2711
|
for (const event of mail) event.deliveredTurn = STARTING;
|
|
2681
2712
|
session.pending = true;
|
|
@@ -2712,6 +2743,7 @@ ${interrupted}`), text_elements: [] }] : [],
|
|
|
2712
2743
|
text: origin === "user" ? text2 : promptSafe(text2),
|
|
2713
2744
|
text_elements: []
|
|
2714
2745
|
},
|
|
2746
|
+
...images.map((image) => ({ type: "image", url: `data:${image.mimeType};base64,${image.data}` })),
|
|
2715
2747
|
...mail.length && !wake ? [{ type: "text", text: promptSafe(renderMail(mail, session.parentAgent)), text_elements: [] }] : []
|
|
2716
2748
|
];
|
|
2717
2749
|
const result = prompt.delivery === "steer" ? await session.runtime.request(
|
|
@@ -2744,7 +2776,8 @@ ${interrupted}`), text_elements: [] }] : [],
|
|
|
2744
2776
|
kind: "user_message",
|
|
2745
2777
|
id: `user:${prompt.clientMessageId}`,
|
|
2746
2778
|
clientMessageId: prompt.clientMessageId,
|
|
2747
|
-
text: text2
|
|
2779
|
+
text: text2,
|
|
2780
|
+
...images.length ? { images: images.length } : {}
|
|
2748
2781
|
}
|
|
2749
2782
|
});
|
|
2750
2783
|
if (origin === "user" && !session.parent) session.userWaiting ??= Date.now();
|
|
@@ -2893,10 +2926,7 @@ ${interrupted}`), text_elements: [] }] : [],
|
|
|
2893
2926
|
},
|
|
2894
2927
|
async preview(spec) {
|
|
2895
2928
|
const resolveOptions = { templates: options.templates, library: options.libraryDir, language: options.language };
|
|
2896
|
-
const
|
|
2897
|
-
const named = typeof settings?.workflow?.mode === "string" && settings.workflow.mode !== "custom" ? settings.workflow.mode : void 0;
|
|
2898
|
-
const workflow = spec.workflow ?? named ?? DEFAULT_TEAM;
|
|
2899
|
-
const mapping = await resolveSession({ ...spec, workflow }, { ...resolveOptions, initialize: false });
|
|
2929
|
+
const mapping = await resolveSession(spec, { ...resolveOptions, initialize: false });
|
|
2900
2930
|
const teams = (await listTeams(spec.cwd, { library: options.libraryDir, templates: options.templates })).filter((team) => !team.error).map((team) => ({ id: team.id, label: team.label ?? team.id, ...team.description ? { description: team.description } : {} }));
|
|
2901
2931
|
return {
|
|
2902
2932
|
teams,
|
|
@@ -2974,6 +3004,6 @@ ${interrupted}`), text_elements: [] }] : [],
|
|
|
2974
3004
|
};
|
|
2975
3005
|
}
|
|
2976
3006
|
export {
|
|
2977
|
-
|
|
3007
|
+
DEFAULT_TEAM,
|
|
2978
3008
|
createAlpRuntime
|
|
2979
3009
|
};
|