chore(deps): upgrade pi-ai and pi-agent-core to 0.52.9
Upgrade @mariozechner/pi-ai and @mariozechner/pi-agent-core from 0.50.3 to 0.52.9 to support latest models (claude-opus-4-6, o3, o3-mini). Breaking type changes addressed: - exactOptionalPropertyTypes: use conditional spread or `| undefined` - TOOL_PROFILES removed: strip all profile references from CLI - AgentMessage union requires timestamp: cast test fixtures - AsyncAgent.id → sessionId - Add explicit callback parameter types for SDK event handlers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28a14a4beb
commit
5380b146b3
28 changed files with 1095 additions and 327 deletions
|
|
@ -48,9 +48,9 @@
|
|||
"vitest": "^4.0.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent-core": "^0.50.3",
|
||||
"@mariozechner/pi-ai": "^0.50.3",
|
||||
"@mariozechner/pi-coding-agent": "^0.50.3",
|
||||
"@mariozechner/pi-agent-core": "^0.52.9",
|
||||
"@mariozechner/pi-ai": "^0.52.9",
|
||||
"@mariozechner/pi-coding-agent": "^0.52.9",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@multica/sdk": "workspace:*",
|
||||
"@nestjs/common": "^11.1.12",
|
||||
|
|
|
|||
1022
pnpm-lock.yaml
generated
1022
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,13 +21,13 @@ import {
|
|||
} from "../../providers/index.js";
|
||||
|
||||
type ChatOptions = {
|
||||
profile?: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
system?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
session?: string;
|
||||
profile?: string | undefined;
|
||||
provider?: string | undefined;
|
||||
model?: string | undefined;
|
||||
system?: string | undefined;
|
||||
thinking?: string | undefined;
|
||||
cwd?: string | undefined;
|
||||
session?: string | undefined;
|
||||
help?: boolean;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ interface CredentialsOptions {
|
|||
force: boolean;
|
||||
coreOnly: boolean;
|
||||
skillsOnly: boolean;
|
||||
pathOverride?: string;
|
||||
skillsPathOverride?: string;
|
||||
pathOverride?: string | undefined;
|
||||
skillsPathOverride?: string | undefined;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
|
|
|
|||
|
|
@ -12,18 +12,17 @@ import type { ToolsConfig } from "../../tools/policy.js";
|
|||
import { cyan, yellow, dim } from "../colors.js";
|
||||
|
||||
type RunOptions = {
|
||||
profile?: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
system?: string;
|
||||
thinking?: string;
|
||||
reasoning?: string;
|
||||
cwd?: string;
|
||||
session?: string;
|
||||
profile?: string | undefined;
|
||||
provider?: string | undefined;
|
||||
model?: string | undefined;
|
||||
apiKey?: string | undefined;
|
||||
baseUrl?: string | undefined;
|
||||
system?: string | undefined;
|
||||
thinking?: string | undefined;
|
||||
reasoning?: string | undefined;
|
||||
cwd?: string | undefined;
|
||||
session?: string | undefined;
|
||||
debug?: boolean;
|
||||
toolsProfile?: string;
|
||||
toolsAllow?: string[];
|
||||
toolsDeny?: string[];
|
||||
help?: boolean;
|
||||
|
|
@ -49,7 +48,6 @@ ${cyan("Options:")}
|
|||
${yellow("--help")}, -h Show this help
|
||||
|
||||
${cyan("Tools Configuration:")}
|
||||
${yellow("--tools-profile")} P Tool profile (minimal, coding, web, full)
|
||||
${yellow("--tools-allow")} T Allow specific tools (comma-separated)
|
||||
${yellow("--tools-deny")} T Deny specific tools (comma-separated)
|
||||
|
||||
|
|
@ -125,10 +123,6 @@ function parseArgs(argv: string[]): { opts: RunOptions; prompt: string } {
|
|||
opts.debug = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--tools-profile") {
|
||||
opts.toolsProfile = args.shift();
|
||||
continue;
|
||||
}
|
||||
if (arg === "--tools-allow") {
|
||||
const value = args.shift();
|
||||
opts.toolsAllow = value?.split(",").map((s) => s.trim()) ?? [];
|
||||
|
|
@ -178,11 +172,8 @@ export async function runCommand(args: string[]): Promise<void> {
|
|||
|
||||
// Build tools config if any tools options are set
|
||||
let toolsConfig: ToolsConfig | undefined;
|
||||
if (opts.toolsProfile || opts.toolsAllow || opts.toolsDeny) {
|
||||
if (opts.toolsAllow || opts.toolsDeny) {
|
||||
toolsConfig = {};
|
||||
if (opts.toolsProfile) {
|
||||
toolsConfig.profile = opts.toolsProfile as ToolsConfig["profile"];
|
||||
}
|
||||
if (opts.toolsAllow) {
|
||||
toolsConfig.allow = opts.toolsAllow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ function cmdShow(sessionId: string | undefined, showInternal = false) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const session = matches[0];
|
||||
const session = matches[0]!;
|
||||
const content = readFileSync(session.path, "utf8");
|
||||
const lines = content.trim().split("\n").filter(Boolean);
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ function cmdDelete(sessionId: string | undefined) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const session = matches[0];
|
||||
const session = matches[0]!;
|
||||
|
||||
try {
|
||||
unlinkSync(session.path);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ interface ParsedArgs {
|
|||
args: string[];
|
||||
verbose: boolean;
|
||||
force: boolean;
|
||||
profile?: string;
|
||||
profile?: string | undefined;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
|
|
|
|||
|
|
@ -4,22 +4,20 @@
|
|||
* Usage:
|
||||
* multica tools list [options] List available tools
|
||||
* multica tools groups Show all tool groups
|
||||
* multica tools profiles Show all tool profiles
|
||||
*/
|
||||
|
||||
import { createAllTools } from "../../tools.js";
|
||||
import { filterTools, type ToolsConfig } from "../../tools/policy.js";
|
||||
import { TOOL_GROUPS, TOOL_PROFILES, expandToolGroups } from "../../tools/groups.js";
|
||||
import { TOOL_GROUPS, expandToolGroups } from "../../tools/groups.js";
|
||||
import { cyan, yellow, green, dim } from "../colors.js";
|
||||
|
||||
type Command = "list" | "groups" | "profiles" | "help";
|
||||
type Command = "list" | "groups" | "help";
|
||||
|
||||
interface ToolsOptions {
|
||||
command: Command;
|
||||
profile?: string;
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
provider?: string;
|
||||
provider?: string | undefined;
|
||||
isSubagent?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -30,11 +28,9 @@ ${cyan("Usage:")} multica tools <command> [options]
|
|||
${cyan("Commands:")}
|
||||
${yellow("list")} List available tools (with optional filtering)
|
||||
${yellow("groups")} Show all tool groups
|
||||
${yellow("profiles")} Show all tool profiles
|
||||
${yellow("help")} Show this help
|
||||
|
||||
${cyan("Options for 'list':")}
|
||||
${yellow("--profile")} PROFILE Apply profile filter (minimal, coding, web, full)
|
||||
${yellow("--allow")} TOOLS Allow specific tools (comma-separated)
|
||||
${yellow("--deny")} TOOLS Deny specific tools (comma-separated)
|
||||
${yellow("--provider")} NAME Apply provider-specific rules
|
||||
|
|
@ -44,11 +40,8 @@ ${cyan("Examples:")}
|
|||
${dim("# List all tools")}
|
||||
multica tools list
|
||||
|
||||
${dim("# List tools with profile")}
|
||||
multica tools list --profile coding
|
||||
|
||||
${dim("# List tools with allow/deny")}
|
||||
multica tools list --profile coding --deny exec
|
||||
multica tools list --deny exec
|
||||
multica tools list --allow group:fs,web_fetch
|
||||
|
||||
${dim("# Show tool groups")}
|
||||
|
|
@ -58,12 +51,13 @@ ${cyan("Examples:")}
|
|||
|
||||
function parseArgs(argv: string[]): ToolsOptions {
|
||||
const args = [...argv];
|
||||
const command = (args.shift() || "help") as Command;
|
||||
const raw = args.shift() || "help";
|
||||
|
||||
if (command === "--help" || command === "-h") {
|
||||
if (raw === "--help" || raw === "-h") {
|
||||
return { command: "help" };
|
||||
}
|
||||
|
||||
const command = raw as Command;
|
||||
const opts: ToolsOptions = { command };
|
||||
|
||||
while (args.length > 0) {
|
||||
|
|
@ -73,10 +67,6 @@ function parseArgs(argv: string[]): ToolsOptions {
|
|||
if (arg === "--help" || arg === "-h") {
|
||||
return { command: "help" };
|
||||
}
|
||||
if (arg === "--profile") {
|
||||
opts.profile = args.shift();
|
||||
continue;
|
||||
}
|
||||
if (arg === "--allow") {
|
||||
const value = args.shift();
|
||||
opts.allow = value?.split(",").map((s) => s.trim()) ?? [];
|
||||
|
|
@ -108,11 +98,8 @@ function cmdList(opts: ToolsOptions) {
|
|||
|
||||
// Build config
|
||||
let config: ToolsConfig | undefined;
|
||||
if (opts.profile || opts.allow || opts.deny) {
|
||||
if (opts.allow || opts.deny) {
|
||||
config = {};
|
||||
if (opts.profile) {
|
||||
config.profile = opts.profile as ToolsConfig["profile"];
|
||||
}
|
||||
if (opts.allow) {
|
||||
config.allow = opts.allow;
|
||||
}
|
||||
|
|
@ -136,7 +123,6 @@ function cmdList(opts: ToolsOptions) {
|
|||
|
||||
if (config || opts.provider || opts.isSubagent) {
|
||||
console.log("Applied filters:");
|
||||
if (opts.profile) console.log(` ${dim("Profile:")} ${yellow(opts.profile)}`);
|
||||
if (opts.allow) console.log(` ${dim("Allow:")} ${opts.allow.join(", ")}`);
|
||||
if (opts.deny) console.log(` ${dim("Deny:")} ${opts.deny.join(", ")}`);
|
||||
if (opts.provider) console.log(` ${dim("Provider:")} ${opts.provider}`);
|
||||
|
|
@ -171,24 +157,6 @@ function cmdGroups() {
|
|||
}
|
||||
}
|
||||
|
||||
function cmdProfiles() {
|
||||
console.log(`\n${cyan("Tool Profiles:")}\n`);
|
||||
for (const [name, policy] of Object.entries(TOOL_PROFILES)) {
|
||||
console.log(` ${yellow(name)}:`);
|
||||
if (policy.allow) {
|
||||
const expanded = expandToolGroups(policy.allow);
|
||||
console.log(` ${dim("Allow:")} ${policy.allow.join(", ")}`);
|
||||
console.log(` ${dim("Expands to:")} ${expanded.join(", ")}`);
|
||||
} else {
|
||||
console.log(` ${dim("Allow:")} (all tools)`);
|
||||
}
|
||||
if (policy.deny) {
|
||||
console.log(` ${dim("Deny:")} ${policy.deny.join(", ")}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
}
|
||||
|
||||
export async function toolsCommand(args: string[]): Promise<void> {
|
||||
const opts = parseArgs(args);
|
||||
|
||||
|
|
@ -199,9 +167,6 @@ export async function toolsCommand(args: string[]): Promise<void> {
|
|||
case "groups":
|
||||
cmdGroups();
|
||||
break;
|
||||
case "profiles":
|
||||
cmdProfiles();
|
||||
break;
|
||||
case "help":
|
||||
default:
|
||||
printHelp();
|
||||
|
|
|
|||
|
|
@ -156,11 +156,8 @@ async function main() {
|
|||
|
||||
// Build tools config if any tools options are set
|
||||
let toolsConfig: import("../tools/policy.js").ToolsConfig | undefined;
|
||||
if (opts.toolsProfile || opts.toolsAllow || opts.toolsDeny) {
|
||||
if (opts.toolsAllow || opts.toolsDeny) {
|
||||
toolsConfig = {};
|
||||
if (opts.toolsProfile) {
|
||||
toolsConfig.profile = opts.toolsProfile as any;
|
||||
}
|
||||
if (opts.toolsAllow) {
|
||||
toolsConfig.allow = opts.toolsAllow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,22 @@
|
|||
*
|
||||
* Usage:
|
||||
* pnpm tools:cli list # List all available tools
|
||||
* pnpm tools:cli list --profile coding # List tools after applying profile
|
||||
* pnpm tools:cli list --allow group:fs # List tools after allowing fs group
|
||||
* pnpm tools:cli list --deny exec # List tools after denying exec
|
||||
* pnpm tools:cli groups # Show all tool groups
|
||||
* pnpm tools:cli profiles # Show all profiles
|
||||
*/
|
||||
|
||||
import { createAllTools } from "../tools.js";
|
||||
import { filterTools, type ToolsConfig } from "../tools/policy.js";
|
||||
import { TOOL_GROUPS, TOOL_PROFILES, expandToolGroups } from "../tools/groups.js";
|
||||
import { TOOL_GROUPS, expandToolGroups } from "../tools/groups.js";
|
||||
|
||||
type Command = "list" | "groups" | "profiles" | "help";
|
||||
type Command = "list" | "groups" | "help";
|
||||
|
||||
interface CliOptions {
|
||||
command: Command;
|
||||
profile?: string;
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
provider?: string;
|
||||
provider?: string | undefined;
|
||||
isSubagent?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -31,11 +29,9 @@ function printUsage() {
|
|||
console.log("Commands:");
|
||||
console.log(" list List available tools (with optional filtering)");
|
||||
console.log(" groups Show all tool groups");
|
||||
console.log(" profiles Show all profiles");
|
||||
console.log(" help Show this help");
|
||||
console.log("");
|
||||
console.log("Options for 'list':");
|
||||
console.log(" --profile PROFILE Apply profile filter (minimal, coding, web, full)");
|
||||
console.log(" --allow TOOLS Allow specific tools (comma-separated)");
|
||||
console.log(" --deny TOOLS Deny specific tools (comma-separated)");
|
||||
console.log(" --provider NAME Apply provider-specific rules");
|
||||
|
|
@ -43,8 +39,7 @@ function printUsage() {
|
|||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" pnpm tools:cli list");
|
||||
console.log(" pnpm tools:cli list --profile coding");
|
||||
console.log(" pnpm tools:cli list --profile coding --deny exec");
|
||||
console.log(" pnpm tools:cli list --deny exec");
|
||||
console.log(" pnpm tools:cli list --allow group:fs,web_fetch");
|
||||
console.log(" pnpm tools:cli groups");
|
||||
}
|
||||
|
|
@ -59,11 +54,6 @@ function parseArgs(argv: string[]): CliOptions {
|
|||
const arg = args.shift();
|
||||
if (!arg) break;
|
||||
|
||||
if (arg === "--profile") {
|
||||
const value = args.shift();
|
||||
if (value) opts.profile = value;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--allow") {
|
||||
const value = args.shift();
|
||||
opts.allow = value?.split(",").map((s) => s.trim()) ?? [];
|
||||
|
|
@ -75,8 +65,7 @@ function parseArgs(argv: string[]): CliOptions {
|
|||
continue;
|
||||
}
|
||||
if (arg === "--provider") {
|
||||
const value = args.shift();
|
||||
if (value) opts.provider = value;
|
||||
opts.provider = args.shift();
|
||||
continue;
|
||||
}
|
||||
if (arg === "--subagent") {
|
||||
|
|
@ -96,11 +85,8 @@ function listTools(opts: CliOptions) {
|
|||
|
||||
// Build config
|
||||
let config: ToolsConfig | undefined;
|
||||
if (opts.profile || opts.allow || opts.deny) {
|
||||
if (opts.allow || opts.deny) {
|
||||
config = {};
|
||||
if (opts.profile) {
|
||||
config.profile = opts.profile as any;
|
||||
}
|
||||
if (opts.allow) {
|
||||
config.allow = opts.allow;
|
||||
}
|
||||
|
|
@ -124,7 +110,6 @@ function listTools(opts: CliOptions) {
|
|||
|
||||
if (config || opts.provider || opts.isSubagent) {
|
||||
console.log("Applied filters:");
|
||||
if (opts.profile) console.log(` Profile: ${opts.profile}`);
|
||||
if (opts.allow) console.log(` Allow: ${opts.allow.join(", ")}`);
|
||||
if (opts.deny) console.log(` Deny: ${opts.deny.join(", ")}`);
|
||||
if (opts.provider) console.log(` Provider: ${opts.provider}`);
|
||||
|
|
@ -160,25 +145,6 @@ function showGroups() {
|
|||
}
|
||||
}
|
||||
|
||||
function showProfiles() {
|
||||
console.log("Tool Profiles:");
|
||||
console.log("");
|
||||
for (const [name, policy] of Object.entries(TOOL_PROFILES)) {
|
||||
console.log(` ${name}:`);
|
||||
if (policy.allow) {
|
||||
const expanded = expandToolGroups(policy.allow);
|
||||
console.log(` Allow: ${policy.allow.join(", ")}`);
|
||||
console.log(` Expands to: ${expanded.join(", ")}`);
|
||||
} else {
|
||||
console.log(` Allow: (all tools)`);
|
||||
}
|
||||
if (policy.deny) {
|
||||
console.log(` Deny: ${policy.deny.join(", ")}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const opts = parseArgs(process.argv.slice(2));
|
||||
|
||||
|
|
@ -189,9 +155,6 @@ async function main() {
|
|||
case "groups":
|
||||
showGroups();
|
||||
break;
|
||||
case "profiles":
|
||||
showProfiles();
|
||||
break;
|
||||
case "help":
|
||||
default:
|
||||
printUsage();
|
||||
|
|
|
|||
|
|
@ -82,10 +82,10 @@ describe("token-estimation", () => {
|
|||
|
||||
describe("estimateTokenUsage", () => {
|
||||
it("should calculate token usage correctly", () => {
|
||||
const messages: AgentMessage[] = [
|
||||
const messages = [
|
||||
{ role: "user", content: "Hello world" }, // ~3 tokens
|
||||
{ role: "assistant", content: "Hi there!" }, // ~3 tokens
|
||||
];
|
||||
] as AgentMessage[];
|
||||
|
||||
const result = estimateTokenUsage({
|
||||
messages,
|
||||
|
|
@ -130,9 +130,9 @@ describe("token-estimation", () => {
|
|||
});
|
||||
|
||||
it("should calculate utilization ratio with safety margin", () => {
|
||||
const messages: AgentMessage[] = [
|
||||
const messages = [
|
||||
{ role: "user", content: "a".repeat(400) }, // ~100 tokens
|
||||
];
|
||||
] as AgentMessage[];
|
||||
|
||||
const result = estimateTokenUsage({
|
||||
messages,
|
||||
|
|
@ -184,7 +184,7 @@ describe("token-estimation", () => {
|
|||
return Array.from({ length: count }, (_, i) => ({
|
||||
role: "user" as const,
|
||||
content: `Message ${i}: ${"x".repeat(100)}`, // Each ~28 tokens
|
||||
}));
|
||||
})) as AgentMessage[];
|
||||
}
|
||||
|
||||
it("should return null if too few messages", () => {
|
||||
|
|
@ -228,7 +228,7 @@ describe("token-estimation", () => {
|
|||
});
|
||||
|
||||
it("should keep newest messages (from the end)", () => {
|
||||
const messages: AgentMessage[] = [
|
||||
const messages = [
|
||||
{ role: "user", content: "Old message 1" },
|
||||
{ role: "user", content: "Old message 2" },
|
||||
{ role: "user", content: "Old message 3" },
|
||||
|
|
@ -242,7 +242,7 @@ describe("token-estimation", () => {
|
|||
{ role: "user", content: "Old message 11" },
|
||||
{ role: "user", content: "Newer message 12" },
|
||||
{ role: "user", content: "Newest message 13" },
|
||||
];
|
||||
] as AgentMessage[];
|
||||
|
||||
const result = compactMessagesTokenAware(messages, 50, {
|
||||
targetRatio: 0.5,
|
||||
|
|
@ -268,29 +268,29 @@ describe("token-estimation", () => {
|
|||
|
||||
describe("isMessageOversized", () => {
|
||||
it("should return true for oversized message", () => {
|
||||
const message: AgentMessage = {
|
||||
const message = {
|
||||
role: "user",
|
||||
content: "x".repeat(4000), // ~1000 tokens
|
||||
};
|
||||
} as AgentMessage;
|
||||
|
||||
// With default maxRatio 0.5, 1000 tokens in 1000 context = 100% > 50%
|
||||
expect(isMessageOversized(message, 1000)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for small message", () => {
|
||||
const message: AgentMessage = {
|
||||
const message = {
|
||||
role: "user",
|
||||
content: "Hello", // ~2 tokens
|
||||
};
|
||||
} as AgentMessage;
|
||||
|
||||
expect(isMessageOversized(message, 10000)).toBe(false);
|
||||
});
|
||||
|
||||
it("should use custom maxRatio", () => {
|
||||
const message: AgentMessage = {
|
||||
const message = {
|
||||
role: "user",
|
||||
content: "x".repeat(400), // ~100 tokens
|
||||
};
|
||||
} as AgentMessage;
|
||||
|
||||
// With safety margin 1.2, 100 * 1.2 = 120 tokens
|
||||
// 120 > 1000 * 0.1 = 100, so oversized
|
||||
|
|
@ -301,10 +301,10 @@ describe("token-estimation", () => {
|
|||
});
|
||||
|
||||
it("should apply safety margin to token count", () => {
|
||||
const message: AgentMessage = {
|
||||
const message = {
|
||||
role: "user",
|
||||
content: "x".repeat(400), // ~100 tokens, with margin ~120
|
||||
};
|
||||
} as AgentMessage;
|
||||
|
||||
// Without margin: 100 < 250 (50% of 500)
|
||||
// With margin: 120 < 250, still ok
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
|
|||
id: "claude-code",
|
||||
name: "Claude Code (OAuth)",
|
||||
authMethod: "oauth",
|
||||
defaultModel: "claude-opus-4-5",
|
||||
models: ["claude-opus-4-5", "claude-opus-4-1", "claude-sonnet-4-5", "claude-sonnet-4-0", "claude-haiku-4-5"],
|
||||
defaultModel: "claude-opus-4-6",
|
||||
models: ["claude-opus-4-6", "claude-opus-4-5", "claude-sonnet-4-5", "claude-sonnet-4-0", "claude-haiku-4-5"],
|
||||
loginCommand: "claude login",
|
||||
},
|
||||
"openai-codex": {
|
||||
|
|
@ -67,7 +67,7 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
|
|||
name: "Anthropic (API Key)",
|
||||
authMethod: "api-key",
|
||||
defaultModel: "claude-sonnet-4-5",
|
||||
models: ["claude-opus-4-5", "claude-opus-4-1", "claude-sonnet-4-5", "claude-sonnet-4-0", "claude-haiku-4-5"],
|
||||
models: ["claude-opus-4-6", "claude-opus-4-5", "claude-sonnet-4-5", "claude-sonnet-4-0", "claude-haiku-4-5"],
|
||||
loginUrl: "https://console.anthropic.com/",
|
||||
},
|
||||
"openai": {
|
||||
|
|
@ -75,7 +75,7 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
|
|||
name: "OpenAI",
|
||||
authMethod: "api-key",
|
||||
defaultModel: "gpt-4o",
|
||||
models: ["gpt-5.2", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini"],
|
||||
models: ["gpt-5.2", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini"],
|
||||
loginUrl: "https://platform.openai.com/api-keys",
|
||||
},
|
||||
"kimi-coding": {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ describe("compaction", () => {
|
|||
return Array.from({ length: count }, (_, i) => ({
|
||||
role: (i % 2 === 0 ? "user" : "assistant") as "user" | "assistant",
|
||||
content: `${prefix} ${i}`,
|
||||
}));
|
||||
})) as AgentMessage[];
|
||||
}
|
||||
|
||||
function createMessagesWithToolUse(): AgentMessage[] {
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ describe("SessionManager display content view", () => {
|
|||
const entries: SessionEntry[] = [
|
||||
{
|
||||
type: "message",
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:37 GMT+8] hi" },
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:37 GMT+8] hi" } as any,
|
||||
displayContent: "hi",
|
||||
timestamp: 1,
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
message: { role: "assistant", content: "hello there" },
|
||||
message: { role: "assistant", content: "hello there" } as any,
|
||||
timestamp: 2,
|
||||
},
|
||||
];
|
||||
|
|
@ -43,9 +43,9 @@ describe("SessionManager display content view", () => {
|
|||
const raw = session.loadMessages();
|
||||
const display = session.loadMessagesForDisplay();
|
||||
|
||||
expect(raw[0]?.content).toBe("[Mon 2026-02-09 14:37 GMT+8] hi");
|
||||
expect(display[0]?.content).toBe("hi");
|
||||
expect(display[1]?.content).toBe("hello there");
|
||||
expect((raw[0] as any)?.content).toBe("[Mon 2026-02-09 14:37 GMT+8] hi");
|
||||
expect((display[0] as any)?.content).toBe("hi");
|
||||
expect((display[1] as any)?.content).toBe("hello there");
|
||||
});
|
||||
|
||||
it("keeps internal filtering behavior in display view", async () => {
|
||||
|
|
@ -54,14 +54,14 @@ describe("SessionManager display content view", () => {
|
|||
const entries: SessionEntry[] = [
|
||||
{
|
||||
type: "message",
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:37 GMT+8] hidden" },
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:37 GMT+8] hidden" } as any,
|
||||
displayContent: "hidden",
|
||||
internal: true,
|
||||
timestamp: 1,
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:38 GMT+8] visible" },
|
||||
message: { role: "user", content: "[Mon 2026-02-09 14:38 GMT+8] visible" } as any,
|
||||
displayContent: "visible",
|
||||
timestamp: 2,
|
||||
},
|
||||
|
|
@ -72,9 +72,9 @@ describe("SessionManager display content view", () => {
|
|||
const includeInternalView = session.loadMessagesForDisplay({ includeInternal: true });
|
||||
|
||||
expect(defaultView).toHaveLength(1);
|
||||
expect(defaultView[0]?.content).toBe("visible");
|
||||
expect((defaultView[0] as any)?.content).toBe("visible");
|
||||
expect(includeInternalView).toHaveLength(2);
|
||||
expect(includeInternalView[0]?.content).toBe("hidden");
|
||||
expect((includeInternalView[0] as any)?.content).toBe("hidden");
|
||||
});
|
||||
|
||||
it("persists displayContent on saveMessage", async () => {
|
||||
|
|
@ -82,7 +82,7 @@ describe("SessionManager display content view", () => {
|
|||
const session = new SessionManager({ sessionId, baseDir: testBaseDir });
|
||||
|
||||
session.saveMessage(
|
||||
{ role: "user", content: "[Mon 2026-02-09 14:39 GMT+8] save me" },
|
||||
{ role: "user", content: "[Mon 2026-02-09 14:39 GMT+8] save me" } as any,
|
||||
{ displayContent: "save me" },
|
||||
);
|
||||
await session.flush();
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export class SessionManager {
|
|||
|
||||
async repairIfNeeded(warn?: (message: string) => void): Promise<RepairReport> {
|
||||
const filePath = resolveSessionPath(this.sessionId, { baseDir: this.baseDir });
|
||||
return repairSessionFileIfNeeded({ sessionFile: filePath, warn });
|
||||
return repairSessionFileIfNeeded({ sessionFile: filePath, ...(warn !== undefined ? { warn } : {}) });
|
||||
}
|
||||
|
||||
loadMessages(options?: { includeInternal?: boolean }): AgentMessage[] {
|
||||
|
|
@ -274,7 +274,7 @@ export class SessionManager {
|
|||
const pruneResult = pruneToolResults({
|
||||
messages: workingMessages,
|
||||
contextWindowTokens: this.contextWindowTokens,
|
||||
settings: this.toolResultPruning,
|
||||
...(this.toolResultPruning !== undefined ? { settings: this.toolResultPruning } : {}),
|
||||
});
|
||||
|
||||
if (pruneResult.changed) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
content: [{ type: "text", text: "ok" }],
|
||||
isError: false,
|
||||
},
|
||||
] satisfies AgentMessage[];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
expect(out[0]?.role).toBe("assistant");
|
||||
|
|
@ -55,7 +55,7 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
isError: false,
|
||||
},
|
||||
{ role: "user", content: "ok" },
|
||||
] satisfies AgentMessage[];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
expect(out.filter((m) => m.role === "toolResult")).toHaveLength(1);
|
||||
|
|
@ -82,7 +82,7 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
content: [{ type: "text", text: "second (duplicate)" }],
|
||||
isError: false,
|
||||
},
|
||||
] satisfies AgentMessage[];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
const results = out.filter((m) => m.role === "toolResult") as Array<{
|
||||
|
|
@ -106,7 +106,7 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
role: "assistant",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
},
|
||||
] satisfies AgentMessage[];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
expect(out.some((m) => m.role === "toolResult")).toBe(false);
|
||||
|
|
@ -116,20 +116,20 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
|
||||
describe("sanitizeToolCallInputs", () => {
|
||||
it("drops tool calls missing input or arguments", () => {
|
||||
const input: AgentMessage[] = [
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "toolCall", id: "call_1", name: "read" }],
|
||||
},
|
||||
{ role: "user", content: "hello" },
|
||||
];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolCallInputs(input);
|
||||
expect(out.map((m) => m.role)).toEqual(["user"]);
|
||||
});
|
||||
|
||||
it("keeps valid tool calls and preserves text blocks", () => {
|
||||
const input: AgentMessage[] = [
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
|
|
@ -138,7 +138,7 @@ describe("sanitizeToolCallInputs", () => {
|
|||
{ type: "toolCall", id: "call_drop", name: "read" },
|
||||
],
|
||||
},
|
||||
];
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolCallInputs(input);
|
||||
const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|||
|
||||
type ToolCallLike = {
|
||||
id: string;
|
||||
name?: string;
|
||||
name?: string | undefined;
|
||||
};
|
||||
|
||||
const TOOL_CALL_TYPES = new Set(["toolCall", "toolUse", "functionCall"]);
|
||||
|
|
@ -72,7 +72,7 @@ function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>)
|
|||
|
||||
function makeMissingToolResult(params: {
|
||||
toolCallId: string;
|
||||
toolName?: string;
|
||||
toolName?: string | undefined;
|
||||
}): Extract<AgentMessage, { role: "toolResult" }> {
|
||||
return {
|
||||
role: "toolResult",
|
||||
|
|
@ -188,7 +188,6 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
|
|||
for (let i = 0; i < messages.length; i += 1) {
|
||||
const msg = messages[i];
|
||||
if (!msg || typeof msg !== "object") {
|
||||
out.push(msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +218,6 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
|
|||
for (; j < messages.length; j += 1) {
|
||||
const next = messages[j];
|
||||
if (!next || typeof next !== "object") {
|
||||
remainder.push(next);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,12 +108,12 @@ describe("session/storage", () => {
|
|||
|
||||
const entry1: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "user", content: "Hello" },
|
||||
message: { role: "user", content: "Hello" } as any,
|
||||
timestamp: 1000,
|
||||
};
|
||||
const entry2: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "assistant", content: "Hi there" },
|
||||
message: { role: "assistant", content: "Hi there" } as any,
|
||||
timestamp: 2000,
|
||||
};
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ describe("session/storage", () => {
|
|||
|
||||
const validEntry: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "user", content: "Valid" },
|
||||
message: { role: "user", content: "Valid" } as any,
|
||||
timestamp: 1000,
|
||||
};
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ describe("session/storage", () => {
|
|||
const sessionId = "append-session";
|
||||
const entry: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "user", content: "Hello" },
|
||||
message: { role: "user", content: "Hello" } as any,
|
||||
timestamp: 1000,
|
||||
};
|
||||
|
||||
|
|
@ -212,12 +212,12 @@ describe("session/storage", () => {
|
|||
const sessionId = "append-existing";
|
||||
const entry1: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "user", content: "First" },
|
||||
message: { role: "user", content: "First" } as any,
|
||||
timestamp: 1000,
|
||||
};
|
||||
const entry2: SessionEntry = {
|
||||
type: "message",
|
||||
message: { role: "assistant", content: "Second" },
|
||||
message: { role: "assistant", content: "Second" } as any,
|
||||
timestamp: 2000,
|
||||
};
|
||||
|
||||
|
|
@ -235,8 +235,8 @@ describe("session/storage", () => {
|
|||
it("should write all entries to file", async () => {
|
||||
const sessionId = "write-session";
|
||||
const entries: SessionEntry[] = [
|
||||
{ type: "message", message: { role: "user", content: "One" }, timestamp: 1000 },
|
||||
{ type: "message", message: { role: "assistant", content: "Two" }, timestamp: 2000 },
|
||||
{ type: "message", message: { role: "user", content: "One" } as any, timestamp: 1000 },
|
||||
{ type: "message", message: { role: "assistant", content: "Two" } as any, timestamp: 2000 },
|
||||
];
|
||||
|
||||
await writeEntries(sessionId, entries, { baseDir: testBaseDir });
|
||||
|
|
@ -251,12 +251,12 @@ describe("session/storage", () => {
|
|||
|
||||
await writeEntries(
|
||||
sessionId,
|
||||
[{ type: "message", message: { role: "user", content: "Old" }, timestamp: 1000 }],
|
||||
[{ type: "message", message: { role: "user", content: "Old" } as any, timestamp: 1000 }],
|
||||
{ baseDir: testBaseDir }
|
||||
);
|
||||
|
||||
const newEntries: SessionEntry[] = [
|
||||
{ type: "message", message: { role: "user", content: "New" }, timestamp: 2000 },
|
||||
{ type: "message", message: { role: "user", content: "New" } as any, timestamp: 2000 },
|
||||
];
|
||||
await writeEntries(sessionId, newEntries, { baseDir: testBaseDir });
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ export interface CreateToolsOptions {
|
|||
type ToolErrorPayload = {
|
||||
error: true;
|
||||
message: string;
|
||||
name?: string;
|
||||
code?: string;
|
||||
retryable?: boolean;
|
||||
details?: Record<string, unknown>;
|
||||
name?: string | undefined;
|
||||
code?: string | undefined;
|
||||
retryable?: boolean | undefined;
|
||||
details?: Record<string, unknown> | undefined;
|
||||
};
|
||||
|
||||
function toToolErrorPayload(error: unknown): ToolErrorPayload {
|
||||
|
|
@ -130,12 +130,12 @@ export function createAllTools(options: CreateToolsOptions | string): AgentTool<
|
|||
// Add sessions_spawn tool (will be filtered by policy for subagents)
|
||||
const sessionsSpawnTool = createSessionsSpawnTool({
|
||||
isSubagent: isSubagent ?? false,
|
||||
sessionId,
|
||||
...(sessionId !== undefined ? { sessionId } : {}),
|
||||
});
|
||||
tools.push(sessionsSpawnTool as AgentTool<any>);
|
||||
|
||||
// Add sessions_list tool
|
||||
const sessionsListTool = createSessionsListTool({ sessionId });
|
||||
const sessionsListTool = createSessionsListTool({ ...(sessionId !== undefined ? { sessionId } : {}) });
|
||||
tools.push(sessionsListTool as AgentTool<any>);
|
||||
|
||||
return tools;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export interface ExecApprovalRequest {
|
|||
/** Shell command to execute */
|
||||
command: string;
|
||||
/** Working directory */
|
||||
cwd?: string;
|
||||
cwd?: string | undefined;
|
||||
/** Evaluated risk level */
|
||||
riskLevel: "safe" | "needs-review" | "dangerous";
|
||||
/** Reasons for the risk assessment */
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ type SessionsListArgs = {
|
|||
export type SessionsListResult = {
|
||||
runs: Array<{
|
||||
runId: string;
|
||||
label?: string;
|
||||
label?: string | undefined;
|
||||
task: string;
|
||||
status: "running" | "ok" | "error" | "timeout" | "unknown";
|
||||
startedAt?: number;
|
||||
endedAt?: number;
|
||||
findings?: string;
|
||||
startedAt?: number | undefined;
|
||||
endedAt?: number | undefined;
|
||||
findings?: string | undefined;
|
||||
}>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class AppController {
|
|||
@Post("agents")
|
||||
createAgent(@Body() body?: { id?: string }) {
|
||||
const agent = this.hub.createAgent(body?.id);
|
||||
return { id: agent.id };
|
||||
return { id: agent.sessionId };
|
||||
}
|
||||
|
||||
@Delete("agents/:id")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import {
|
||||
GatewayClient,
|
||||
HelloAction,
|
||||
HelloResponseAction,
|
||||
type HelloPayload,
|
||||
type HelloResponsePayload,
|
||||
type ConnectionState,
|
||||
type RoutedMessage,
|
||||
type SendErrorResponse,
|
||||
} from "@multica/sdk";
|
||||
|
||||
const HelloAction = "hello";
|
||||
const HelloResponseAction = "hello:response";
|
||||
type HelloPayload = { greeting: string };
|
||||
type HelloResponsePayload = { reply: string };
|
||||
|
||||
// 模拟一个 Client
|
||||
const client = new GatewayClient({
|
||||
url: "http://localhost:3000",
|
||||
|
|
@ -22,11 +26,11 @@ const agent = new GatewayClient({
|
|||
|
||||
// Agent 监听消息
|
||||
agent
|
||||
.onStateChange((state) => console.log("[Agent] State:", state))
|
||||
.onRegistered((deviceId) => {
|
||||
.onStateChange((state: ConnectionState) => console.log("[Agent] State:", state))
|
||||
.onRegistered((deviceId: string) => {
|
||||
console.log("[Agent] Registered as:", deviceId);
|
||||
})
|
||||
.onMessage((message) => {
|
||||
.onMessage((message: RoutedMessage) => {
|
||||
console.log("[Agent] Received message:", message);
|
||||
|
||||
// 回复消息
|
||||
|
|
@ -38,13 +42,13 @@ agent
|
|||
});
|
||||
}
|
||||
})
|
||||
.onSendError((error) => console.error("[Agent] Send error:", error))
|
||||
.onSendError((error: SendErrorResponse) => console.error("[Agent] Send error:", error))
|
||||
.connect();
|
||||
|
||||
// Client 监听消息
|
||||
client
|
||||
.onStateChange((state) => console.log("[Client] State:", state))
|
||||
.onRegistered((deviceId) => {
|
||||
.onStateChange((state: ConnectionState) => console.log("[Client] State:", state))
|
||||
.onRegistered((deviceId: string) => {
|
||||
console.log("[Client] Registered as:", deviceId);
|
||||
|
||||
// 注册后发送消息给 Agent
|
||||
|
|
@ -55,10 +59,10 @@ client
|
|||
});
|
||||
}, 500);
|
||||
})
|
||||
.onMessage((message) => {
|
||||
.onMessage((message: RoutedMessage) => {
|
||||
console.log("[Client] Received message:", message);
|
||||
})
|
||||
.onSendError((error) => console.error("[Client] Send error:", error))
|
||||
.onSendError((error: SendErrorResponse) => console.error("[Client] Send error:", error))
|
||||
.connect();
|
||||
|
||||
// 5秒后断开
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ function detectFenceAt(text: string, upTo: number): FenceInfo | null {
|
|||
for (const line of lines) {
|
||||
const match = line.match(/^(`{3,}|~{3,})(\S*)\s*$/);
|
||||
if (!match) continue;
|
||||
const marker = match[1];
|
||||
const marker = match[1]!;
|
||||
const lang = match[2] ?? "";
|
||||
const markerChar = marker[0];
|
||||
const markerChar = marker[0]!;
|
||||
|
||||
if (openFence === null) {
|
||||
// Opening a new fence
|
||||
|
|
@ -242,7 +242,7 @@ function findSentenceBreak(buffer: string, start: number, end: number, bufLen: n
|
|||
const ch = buffer[i];
|
||||
if (ch === "." || ch === "!" || ch === "?") {
|
||||
const next = i + 1;
|
||||
if (next < bufLen && /\s/.test(buffer[next])) {
|
||||
if (next < bufLen && /\s/.test(buffer[next]!)) {
|
||||
// Break after the whitespace
|
||||
const idx = next + 1;
|
||||
if (idx < bufLen) return idx;
|
||||
|
|
@ -261,7 +261,7 @@ function findSentenceBreak(buffer: string, start: number, end: number, bufLen: n
|
|||
*/
|
||||
function findWordBreak(buffer: string, start: number, end: number, bufLen: number): number {
|
||||
for (let i = end - 1; i >= start; i--) {
|
||||
if (/\s/.test(buffer[i])) {
|
||||
if (/\s/.test(buffer[i]!)) {
|
||||
const idx = i + 1;
|
||||
if (idx < bufLen) return idx;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export interface DeviceEntry {
|
|||
deviceId: string;
|
||||
agentId: string;
|
||||
addedAt: number;
|
||||
meta?: DeviceMeta;
|
||||
meta?: DeviceMeta | undefined;
|
||||
}
|
||||
|
||||
// ============ Persistence ============
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe("ExecApprovalManager", () => {
|
|||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
sendToClient = vi.fn();
|
||||
manager = new ExecApprovalManager(sendToClient, 5000); // 5s timeout for tests
|
||||
manager = new ExecApprovalManager(sendToClient as any, 5000); // 5s timeout for tests
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { v7 as uuidv7 } from "uuid";
|
|||
import {
|
||||
GatewayClient,
|
||||
type ConnectionState,
|
||||
type RoutedMessage,
|
||||
type SendErrorResponse,
|
||||
RequestAction,
|
||||
ResponseAction,
|
||||
StreamAction,
|
||||
|
|
@ -204,22 +206,22 @@ export class Hub {
|
|||
reconnectDelay: 1000,
|
||||
});
|
||||
|
||||
client.onStateChange((state) => {
|
||||
client.onStateChange((state: ConnectionState) => {
|
||||
console.log(`[Hub] Connection state: ${state}`);
|
||||
for (const listener of this._stateChangeListeners) {
|
||||
listener(state);
|
||||
}
|
||||
});
|
||||
|
||||
client.onRegistered((deviceId) => {
|
||||
client.onRegistered((deviceId: string) => {
|
||||
console.log(`[Hub] Registered as: ${deviceId}`);
|
||||
});
|
||||
|
||||
client.onError((err) => {
|
||||
client.onError((err: Error) => {
|
||||
console.error(`[Hub] Connection error:`, err.message);
|
||||
});
|
||||
|
||||
client.onMessage((msg) => {
|
||||
client.onMessage((msg: RoutedMessage) => {
|
||||
console.log(`[Hub] Received message: id=${msg.id} from=${msg.from} to=${msg.to} action=${msg.action} payload=${JSON.stringify(msg.payload)}`);
|
||||
|
||||
// RPC request
|
||||
|
|
@ -272,7 +274,7 @@ export class Hub {
|
|||
}
|
||||
});
|
||||
|
||||
client.onSendError((err) => {
|
||||
client.onSendError((err: SendErrorResponse) => {
|
||||
console.error(`[Hub] Send error: messageId=${err.messageId} code=${err.code} error=${err.error}`);
|
||||
});
|
||||
|
||||
|
|
@ -592,12 +594,12 @@ export class Hub {
|
|||
const result = await this.approvalManager.requestApproval({
|
||||
agentId: sessionId,
|
||||
command,
|
||||
cwd,
|
||||
...(cwd !== undefined ? { cwd } : {}),
|
||||
riskLevel: evaluation.riskLevel,
|
||||
riskReasons: evaluation.reasons,
|
||||
timeoutMs: config.timeoutMs,
|
||||
askFallback: config.askFallback,
|
||||
allowlistSatisfied: evaluation.allowlistSatisfied,
|
||||
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
||||
...(config.askFallback !== undefined ? { askFallback: config.askFallback } : {}),
|
||||
...(evaluation.allowlistSatisfied !== undefined ? { allowlistSatisfied: evaluation.allowlistSatisfied } : {}),
|
||||
});
|
||||
|
||||
// Handle allow-always: persist to profile allowlist
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ function smallConfig(overrides?: Partial<BlockChunkerConfig>): BlockChunkerConfi
|
|||
describe("MessageAggregator", () => {
|
||||
let blocks: BlockReply[];
|
||||
let passedThrough: Array<AgentEvent | MulticaEvent>;
|
||||
let onBlock: ReturnType<typeof vi.fn>;
|
||||
let onPassthrough: ReturnType<typeof vi.fn>;
|
||||
let onBlock: any;
|
||||
let onPassthrough: any;
|
||||
|
||||
beforeEach(() => {
|
||||
blocks = [];
|
||||
|
|
@ -154,8 +154,8 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(endEvent);
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("Hello world");
|
||||
expect(blocks[0].isFinal).toBe(true);
|
||||
expect(blocks[0]!.text).toBe("Hello world");
|
||||
expect(blocks[0]!.isFinal).toBe(true);
|
||||
|
||||
// message_start + message_end both passed through
|
||||
const passthroughTypes = passedThrough.map((e) => e.type);
|
||||
|
|
@ -178,7 +178,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("Hello world"));
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("Hello world");
|
||||
expect(blocks[0]!.text).toBe("Hello world");
|
||||
});
|
||||
|
||||
it("ignores ThinkingContent blocks, only extracts text", () => {
|
||||
|
|
@ -190,8 +190,8 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("visible text"));
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("visible text");
|
||||
expect(blocks[0].text).not.toContain("internal thinking");
|
||||
expect(blocks[0]!.text).toBe("visible text");
|
||||
expect(blocks[0]!.text).not.toContain("internal thinking");
|
||||
});
|
||||
|
||||
it("handles empty delta (duplicate event) gracefully", () => {
|
||||
|
|
@ -205,7 +205,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("Hello"));
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("Hello");
|
||||
expect(blocks[0]!.text).toBe("Hello");
|
||||
});
|
||||
|
||||
it("handles monotonically growing text correctly", () => {
|
||||
|
|
@ -221,7 +221,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("Hello"));
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("Hello");
|
||||
expect(blocks[0]!.text).toBe("Hello");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -241,8 +241,8 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageUpdate(text));
|
||||
|
||||
expect(blocks.length).toBeGreaterThanOrEqual(1);
|
||||
expect(blocks[0].text).toContain("first paragraph");
|
||||
expect(blocks[0].isFinal).toBe(false);
|
||||
expect(blocks[0]!.text).toContain("first paragraph");
|
||||
expect(blocks[0]!.isFinal).toBe(false);
|
||||
});
|
||||
|
||||
it("emits multiple blocks for very long text", () => {
|
||||
|
|
@ -255,7 +255,7 @@ describe("MessageAggregator", () => {
|
|||
expect(blocks.length).toBeGreaterThanOrEqual(2);
|
||||
// All blocks except the last should have isFinal=false
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
expect(blocks[i].isFinal).toBe(false);
|
||||
expect(blocks[i]!.isFinal).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd(text));
|
||||
|
||||
const finalBlock = blocks[blocks.length - 1];
|
||||
expect(finalBlock.isFinal).toBe(true);
|
||||
expect(finalBlock!.isFinal).toBe(true);
|
||||
});
|
||||
|
||||
it("increments block index for each emitted block", () => {
|
||||
|
|
@ -280,7 +280,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd(text));
|
||||
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
expect(blocks[i].index).toBe(i);
|
||||
expect(blocks[i]!.index).toBe(i);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("First message text."));
|
||||
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].index).toBe(0);
|
||||
expect(blocks[0]!.index).toBe(0);
|
||||
|
||||
// Second message cycle — index should reset
|
||||
agg.handleEvent(makeMessageStart("msg-2"));
|
||||
|
|
@ -301,7 +301,7 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("Second message text."));
|
||||
|
||||
expect(blocks).toHaveLength(2);
|
||||
expect(blocks[1].index).toBe(0); // Reset after new message_start
|
||||
expect(blocks[1]!.index).toBe(0); // Reset after new message_start
|
||||
});
|
||||
|
||||
it("does not emit empty block on message_end with no content", () => {
|
||||
|
|
@ -335,7 +335,7 @@ describe("MessageAggregator", () => {
|
|||
|
||||
// Final block should contain all text
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].text).toBe("Before tool call. After tool result.");
|
||||
expect(blocks[0]!.text).toBe("Before tool call. After tool result.");
|
||||
});
|
||||
|
||||
it("handles multiple message cycles (reset between)", () => {
|
||||
|
|
@ -352,11 +352,11 @@ describe("MessageAggregator", () => {
|
|||
agg.handleEvent(makeMessageEnd("Second response."));
|
||||
|
||||
expect(blocks).toHaveLength(2);
|
||||
expect(blocks[0].text).toBe("First response.");
|
||||
expect(blocks[1].text).toBe("Second response.");
|
||||
expect(blocks[0]!.text).toBe("First response.");
|
||||
expect(blocks[1]!.text).toBe("Second response.");
|
||||
// Both should be final (flushed on message_end)
|
||||
expect(blocks[0].isFinal).toBe(true);
|
||||
expect(blocks[1].isFinal).toBe(true);
|
||||
expect(blocks[0]!.isFinal).toBe(true);
|
||||
expect(blocks[1]!.isFinal).toBe(true);
|
||||
});
|
||||
|
||||
it("handles compaction events between messages", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue