@poncho-ai/harness 0.16.0 → 0.17.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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +58 -14
- package/dist/index.js +261 -12
- package/package.json +2 -2
- package/src/harness.ts +51 -3
- package/src/index.ts +2 -0
- package/src/latitude-capture.ts +3 -13
- package/src/state.ts +111 -0
- package/src/subagent-manager.ts +30 -0
- package/src/subagent-tools.ts +160 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -139
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.17.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
11
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mESM[39m [1mdist/index.js [22m[32m196.31 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 155ms
|
|
12
12
|
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in
|
|
14
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 6569ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m23.97 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#16](https://github.com/cesr/poncho-ai/pull/16) [`972577d`](https://github.com/cesr/poncho-ai/commit/972577d255ab43c2c56f3c3464042a8a617b7f9e) Thanks [@cesr](https://github.com/cesr)! - Add subagent support: agents can spawn recursive copies of themselves as independent sub-conversations with blocking tool calls, read-only memory, approval tunneling to the parent thread, and nested sidebar display in the web UI. Also adds ConversationStore.listSummaries() for fast sidebar loading without reading full conversation files from disk.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`972577d`](https://github.com/cesr/poncho-ai/commit/972577d255ab43c2c56f3c3464042a8a617b7f9e)]:
|
|
12
|
+
- @poncho-ai/sdk@1.2.0
|
|
13
|
+
|
|
14
|
+
## 0.16.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [`7475da5`](https://github.com/cesr/poncho-ai/commit/7475da5c0c2399e79064a2622137c0eb2fb16871) Thanks [@cesr](https://github.com/cesr)! - Inject browser usage context into agent system prompt (auth flow, session persistence, tool selection guidance).
|
|
19
|
+
|
|
3
20
|
## 0.16.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _poncho_ai_sdk from '@poncho-ai/sdk';
|
|
2
|
+
import { Message, ToolDefinition, RunResult, AgentFailure, ToolContext, RunInput, AgentEvent, JsonSchema } from '@poncho-ai/sdk';
|
|
2
3
|
export { ToolDefinition, defineTool } from '@poncho-ai/sdk';
|
|
3
4
|
import { LanguageModel } from 'ai';
|
|
4
5
|
import { z } from 'zod';
|
|
@@ -96,11 +97,21 @@ interface Conversation {
|
|
|
96
97
|
}>;
|
|
97
98
|
ownerId: string;
|
|
98
99
|
tenantId: string | null;
|
|
100
|
+
contextTokens?: number;
|
|
101
|
+
contextWindow?: number;
|
|
102
|
+
parentConversationId?: string;
|
|
103
|
+
subagentMeta?: {
|
|
104
|
+
task: string;
|
|
105
|
+
status: "running" | "completed" | "error" | "stopped";
|
|
106
|
+
result?: _poncho_ai_sdk.RunResult;
|
|
107
|
+
error?: _poncho_ai_sdk.AgentFailure;
|
|
108
|
+
};
|
|
99
109
|
createdAt: number;
|
|
100
110
|
updatedAt: number;
|
|
101
111
|
}
|
|
102
112
|
interface ConversationStore {
|
|
103
113
|
list(ownerId?: string): Promise<Conversation[]>;
|
|
114
|
+
listSummaries(ownerId?: string): Promise<ConversationSummary[]>;
|
|
104
115
|
get(conversationId: string): Promise<Conversation | undefined>;
|
|
105
116
|
create(ownerId?: string, title?: string): Promise<Conversation>;
|
|
106
117
|
update(conversation: Conversation): Promise<void>;
|
|
@@ -132,12 +143,23 @@ declare class InMemoryConversationStore implements ConversationStore {
|
|
|
132
143
|
private isExpired;
|
|
133
144
|
private purgeExpired;
|
|
134
145
|
list(ownerId?: string): Promise<Conversation[]>;
|
|
146
|
+
listSummaries(ownerId?: string): Promise<ConversationSummary[]>;
|
|
135
147
|
get(conversationId: string): Promise<Conversation | undefined>;
|
|
136
148
|
create(ownerId?: string, title?: string): Promise<Conversation>;
|
|
137
149
|
update(conversation: Conversation): Promise<void>;
|
|
138
150
|
rename(conversationId: string, title: string): Promise<Conversation | undefined>;
|
|
139
151
|
delete(conversationId: string): Promise<boolean>;
|
|
140
152
|
}
|
|
153
|
+
type ConversationSummary = {
|
|
154
|
+
conversationId: string;
|
|
155
|
+
title: string;
|
|
156
|
+
updatedAt: number;
|
|
157
|
+
createdAt?: number;
|
|
158
|
+
ownerId: string;
|
|
159
|
+
parentConversationId?: string;
|
|
160
|
+
messageCount?: number;
|
|
161
|
+
hasPendingApprovals?: boolean;
|
|
162
|
+
};
|
|
141
163
|
declare const createStateStore: (config?: StateConfig, options?: {
|
|
142
164
|
workingDir?: string;
|
|
143
165
|
agentId?: string;
|
|
@@ -400,6 +422,30 @@ interface ProviderConfig {
|
|
|
400
422
|
*/
|
|
401
423
|
declare const createModelProvider: (provider?: string, config?: ProviderConfig) => ModelProviderFactory;
|
|
402
424
|
|
|
425
|
+
interface SubagentResult {
|
|
426
|
+
subagentId: string;
|
|
427
|
+
status: "completed" | "error" | "stopped";
|
|
428
|
+
latestMessages?: Message[];
|
|
429
|
+
result?: RunResult;
|
|
430
|
+
error?: AgentFailure;
|
|
431
|
+
}
|
|
432
|
+
interface SubagentSummary {
|
|
433
|
+
subagentId: string;
|
|
434
|
+
task: string;
|
|
435
|
+
status: string;
|
|
436
|
+
messageCount: number;
|
|
437
|
+
}
|
|
438
|
+
interface SubagentManager {
|
|
439
|
+
spawn(opts: {
|
|
440
|
+
task: string;
|
|
441
|
+
parentConversationId: string;
|
|
442
|
+
ownerId: string;
|
|
443
|
+
}): Promise<SubagentResult>;
|
|
444
|
+
sendMessage(subagentId: string, message: string): Promise<SubagentResult>;
|
|
445
|
+
stop(subagentId: string): Promise<void>;
|
|
446
|
+
list(parentConversationId: string): Promise<SubagentSummary[]>;
|
|
447
|
+
}
|
|
448
|
+
|
|
403
449
|
interface ToolCall {
|
|
404
450
|
id: string;
|
|
405
451
|
name: string;
|
|
@@ -456,6 +502,7 @@ declare class AgentHarness {
|
|
|
456
502
|
private _browserMod?;
|
|
457
503
|
private parsedAgent?;
|
|
458
504
|
private mcpBridge?;
|
|
505
|
+
private subagentManager?;
|
|
459
506
|
private resolveToolAccess;
|
|
460
507
|
private isToolEnabled;
|
|
461
508
|
private registerIfMissing;
|
|
@@ -465,6 +512,8 @@ declare class AgentHarness {
|
|
|
465
512
|
* Tools disabled via `tools` config are skipped.
|
|
466
513
|
*/
|
|
467
514
|
registerTools(tools: ToolDefinition[]): void;
|
|
515
|
+
unregisterTools(names: string[]): void;
|
|
516
|
+
setSubagentManager(manager: SubagentManager): void;
|
|
468
517
|
private registerConfiguredBuiltInTools;
|
|
469
518
|
private shouldEnableWriteTool;
|
|
470
519
|
constructor(options?: HarnessOptions);
|
|
@@ -490,6 +539,8 @@ declare class AgentHarness {
|
|
|
490
539
|
private initBrowserTools;
|
|
491
540
|
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
492
541
|
private _currentRunConversationId?;
|
|
542
|
+
/** Owner ID of the currently executing run (used by subagent tools). */
|
|
543
|
+
private _currentRunOwnerId?;
|
|
493
544
|
get browserSession(): unknown;
|
|
494
545
|
shutdown(): Promise<void>;
|
|
495
546
|
listTools(): ToolDefinition[];
|
|
@@ -515,16 +566,6 @@ declare class AgentHarness {
|
|
|
515
566
|
runToCompletion(input: RunInput): Promise<HarnessRunOutput>;
|
|
516
567
|
}
|
|
517
568
|
|
|
518
|
-
/**
|
|
519
|
-
* Latitude telemetry integration for Vercel AI SDK
|
|
520
|
-
*
|
|
521
|
-
* TODO: Implement proper Vercel AI SDK telemetry integration using:
|
|
522
|
-
* - LatitudeTelemetry.capture() wrapper around streamText()
|
|
523
|
-
* - experimental_telemetry: { isEnabled: true } in streamText() options
|
|
524
|
-
*
|
|
525
|
-
* This requires @latitude-data/telemetry package which has official
|
|
526
|
-
* Vercel AI SDK support.
|
|
527
|
-
*/
|
|
528
569
|
interface LatitudeCaptureConfig {
|
|
529
570
|
apiKeyEnv?: string;
|
|
530
571
|
projectIdEnv?: string;
|
|
@@ -532,8 +573,9 @@ interface LatitudeCaptureConfig {
|
|
|
532
573
|
defaultPath?: string;
|
|
533
574
|
}
|
|
534
575
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
576
|
+
* Reads and validates Latitude telemetry configuration from environment
|
|
577
|
+
* variables. The actual telemetry capture is handled by LatitudeTelemetry
|
|
578
|
+
* from @latitude-data/telemetry in harness.ts (via runWithTelemetry).
|
|
537
579
|
*/
|
|
538
580
|
declare class LatitudeCapture {
|
|
539
581
|
private readonly apiKey?;
|
|
@@ -636,4 +678,6 @@ declare class TelemetryEmitter {
|
|
|
636
678
|
private sendOtlp;
|
|
637
679
|
}
|
|
638
680
|
|
|
639
|
-
|
|
681
|
+
declare const createSubagentTools: (manager: SubagentManager, getConversationId: () => string | undefined, getOwnerId: () => string) => ToolDefinition[];
|
|
682
|
+
|
|
683
|
+
export { type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, type BuiltInToolToggles, type Conversation, type ConversationState, type ConversationStore, type ConversationSummary, type CronJobConfig, type HarnessOptions, type HarnessRunOutput, InMemoryConversationStore, InMemoryStateStore, LatitudeCapture, type LatitudeCaptureConfig, LocalMcpBridge, LocalUploadStore, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PonchoConfig, type ProviderConfig, type RemoteMcpServerConfig, type RuntimeRenderContext, S3UploadStore, STORAGE_SCHEMA_VERSION, type SkillContextEntry, type SkillMetadata, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type SubagentManager, type SubagentResult, type SubagentSummary, type TelemetryConfig, TelemetryEmitter, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type UploadStore, type UploadsConfig, VercelBlobUploadStore, buildAgentDirectoryName, buildSkillContextWindow, createConversationStore, createDefaultTools, createMemoryStore, createMemoryTools, createModelProvider, createSkillTools, createStateStore, createSubagentTools, createUploadStore, createWriteTool, deriveUploadKey, ensureAgentIdentity, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getPonchoStoreRoot, jsonSchemaToZod, loadPonchoConfig, loadSkillContext, loadSkillInstructions, loadSkillMetadata, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, readSkillResource, renderAgentPrompt, resolveAgentIdentity, resolveMemoryConfig, resolveSkillDirs, resolveStateConfig, slugifyStorageComponent };
|
package/dist/index.js
CHANGED
|
@@ -466,7 +466,7 @@ var createWriteTool = (workingDir) => defineTool({
|
|
|
466
466
|
|
|
467
467
|
// src/harness.ts
|
|
468
468
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
469
|
-
import { getTextContent } from "@poncho-ai/sdk";
|
|
469
|
+
import { getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
470
470
|
|
|
471
471
|
// src/upload-store.ts
|
|
472
472
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -2447,6 +2447,138 @@ var extractRunnableFunction = (value) => {
|
|
|
2447
2447
|
return void 0;
|
|
2448
2448
|
};
|
|
2449
2449
|
|
|
2450
|
+
// src/subagent-tools.ts
|
|
2451
|
+
import { defineTool as defineTool4, getTextContent } from "@poncho-ai/sdk";
|
|
2452
|
+
var LAST_MESSAGES_TO_RETURN = 10;
|
|
2453
|
+
var summarizeResult = (r) => {
|
|
2454
|
+
const summary = {
|
|
2455
|
+
subagentId: r.subagentId,
|
|
2456
|
+
status: r.status
|
|
2457
|
+
};
|
|
2458
|
+
if (r.result) {
|
|
2459
|
+
summary.result = {
|
|
2460
|
+
status: r.result.status,
|
|
2461
|
+
response: r.result.response,
|
|
2462
|
+
steps: r.result.steps,
|
|
2463
|
+
duration: r.result.duration
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
if (r.error) {
|
|
2467
|
+
summary.error = r.error;
|
|
2468
|
+
}
|
|
2469
|
+
if (r.latestMessages && r.latestMessages.length > 0) {
|
|
2470
|
+
summary.latestMessages = r.latestMessages.slice(-LAST_MESSAGES_TO_RETURN).map((m) => ({
|
|
2471
|
+
role: m.role,
|
|
2472
|
+
content: getTextContent(m).slice(0, 2e3)
|
|
2473
|
+
}));
|
|
2474
|
+
}
|
|
2475
|
+
return summary;
|
|
2476
|
+
};
|
|
2477
|
+
var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
2478
|
+
defineTool4({
|
|
2479
|
+
name: "spawn_subagent",
|
|
2480
|
+
description: "Spawn a subagent to work on a task and wait for it to finish. The subagent is a full copy of yourself running in its own conversation context with access to the same tools (except memory writes). This call blocks until the subagent completes and returns its result.\n\nGuidelines:\n- Use subagents to parallelize work: call spawn_subagent multiple times in one response for independent sub-tasks -- they run concurrently.\n- Prefer doing work yourself for simple or quick tasks. Spawn subagents for substantial, self-contained work.\n- The subagent has no memory of your conversation -- write thorough, self-contained instructions in the task.",
|
|
2481
|
+
inputSchema: {
|
|
2482
|
+
type: "object",
|
|
2483
|
+
properties: {
|
|
2484
|
+
task: {
|
|
2485
|
+
type: "string",
|
|
2486
|
+
description: "Thorough, self-contained instructions for the subagent. Include all relevant context, goals, and constraints -- the subagent starts with zero prior conversation history."
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
required: ["task"],
|
|
2490
|
+
additionalProperties: false
|
|
2491
|
+
},
|
|
2492
|
+
handler: async (input) => {
|
|
2493
|
+
const task = typeof input.task === "string" ? input.task : "";
|
|
2494
|
+
if (!task.trim()) {
|
|
2495
|
+
return { error: "task is required" };
|
|
2496
|
+
}
|
|
2497
|
+
const conversationId = getConversationId();
|
|
2498
|
+
if (!conversationId) {
|
|
2499
|
+
return { error: "no active conversation to spawn subagent from" };
|
|
2500
|
+
}
|
|
2501
|
+
const result = await manager.spawn({
|
|
2502
|
+
task: task.trim(),
|
|
2503
|
+
parentConversationId: conversationId,
|
|
2504
|
+
ownerId: getOwnerId()
|
|
2505
|
+
});
|
|
2506
|
+
return summarizeResult(result);
|
|
2507
|
+
}
|
|
2508
|
+
}),
|
|
2509
|
+
defineTool4({
|
|
2510
|
+
name: "message_subagent",
|
|
2511
|
+
description: "Send a follow-up message to a completed or stopped subagent and wait for it to finish. This restarts the subagent with the new message and blocks until it completes. Only works when the subagent is not currently running.",
|
|
2512
|
+
inputSchema: {
|
|
2513
|
+
type: "object",
|
|
2514
|
+
properties: {
|
|
2515
|
+
subagent_id: {
|
|
2516
|
+
type: "string",
|
|
2517
|
+
description: "The subagent ID (from spawn_subagent result or list_subagents)."
|
|
2518
|
+
},
|
|
2519
|
+
message: {
|
|
2520
|
+
type: "string",
|
|
2521
|
+
description: "The follow-up instructions or message to send."
|
|
2522
|
+
}
|
|
2523
|
+
},
|
|
2524
|
+
required: ["subagent_id", "message"],
|
|
2525
|
+
additionalProperties: false
|
|
2526
|
+
},
|
|
2527
|
+
handler: async (input) => {
|
|
2528
|
+
const subagentId = typeof input.subagent_id === "string" ? input.subagent_id : "";
|
|
2529
|
+
const message = typeof input.message === "string" ? input.message : "";
|
|
2530
|
+
if (!subagentId || !message.trim()) {
|
|
2531
|
+
return { error: "subagent_id and message are required" };
|
|
2532
|
+
}
|
|
2533
|
+
const result = await manager.sendMessage(subagentId, message.trim());
|
|
2534
|
+
return summarizeResult(result);
|
|
2535
|
+
}
|
|
2536
|
+
}),
|
|
2537
|
+
defineTool4({
|
|
2538
|
+
name: "stop_subagent",
|
|
2539
|
+
description: "Stop a running subagent. The subagent's conversation is preserved but it will stop processing. Use this to cancel work that is no longer needed.",
|
|
2540
|
+
inputSchema: {
|
|
2541
|
+
type: "object",
|
|
2542
|
+
properties: {
|
|
2543
|
+
subagent_id: {
|
|
2544
|
+
type: "string",
|
|
2545
|
+
description: "The subagent ID (from spawn_subagent result or list_subagents)."
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
required: ["subagent_id"],
|
|
2549
|
+
additionalProperties: false
|
|
2550
|
+
},
|
|
2551
|
+
handler: async (input) => {
|
|
2552
|
+
const subagentId = typeof input.subagent_id === "string" ? input.subagent_id : "";
|
|
2553
|
+
if (!subagentId) {
|
|
2554
|
+
return { error: "subagent_id is required" };
|
|
2555
|
+
}
|
|
2556
|
+
await manager.stop(subagentId);
|
|
2557
|
+
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
2558
|
+
}
|
|
2559
|
+
}),
|
|
2560
|
+
defineTool4({
|
|
2561
|
+
name: "list_subagents",
|
|
2562
|
+
description: "List all subagents that have been spawned in this conversation. Returns each subagent's ID, original task, current status, and message count. Use this to look up subagent IDs before calling message_subagent or stop_subagent.",
|
|
2563
|
+
inputSchema: {
|
|
2564
|
+
type: "object",
|
|
2565
|
+
properties: {},
|
|
2566
|
+
additionalProperties: false
|
|
2567
|
+
},
|
|
2568
|
+
handler: async () => {
|
|
2569
|
+
const conversationId = getConversationId();
|
|
2570
|
+
if (!conversationId) {
|
|
2571
|
+
return { error: "no active conversation" };
|
|
2572
|
+
}
|
|
2573
|
+
const subagents = await manager.list(conversationId);
|
|
2574
|
+
if (subagents.length === 0) {
|
|
2575
|
+
return { message: "No subagents have been spawned in this conversation." };
|
|
2576
|
+
}
|
|
2577
|
+
return { subagents };
|
|
2578
|
+
}
|
|
2579
|
+
})
|
|
2580
|
+
];
|
|
2581
|
+
|
|
2450
2582
|
// src/harness.ts
|
|
2451
2583
|
import { LatitudeTelemetry } from "@latitude-data/telemetry";
|
|
2452
2584
|
import { diag, DiagLogLevel } from "@opentelemetry/api";
|
|
@@ -2928,6 +3060,7 @@ var AgentHarness = class {
|
|
|
2928
3060
|
_browserMod;
|
|
2929
3061
|
parsedAgent;
|
|
2930
3062
|
mcpBridge;
|
|
3063
|
+
subagentManager;
|
|
2931
3064
|
resolveToolAccess(toolName) {
|
|
2932
3065
|
const tools = this.loadedConfig?.tools;
|
|
2933
3066
|
if (!tools) return true;
|
|
@@ -2964,6 +3097,19 @@ var AgentHarness = class {
|
|
|
2964
3097
|
this.dispatcher.register(tool);
|
|
2965
3098
|
}
|
|
2966
3099
|
}
|
|
3100
|
+
unregisterTools(names) {
|
|
3101
|
+
this.dispatcher.unregisterMany(names);
|
|
3102
|
+
}
|
|
3103
|
+
setSubagentManager(manager) {
|
|
3104
|
+
this.subagentManager = manager;
|
|
3105
|
+
this.dispatcher.registerMany(
|
|
3106
|
+
createSubagentTools(
|
|
3107
|
+
manager,
|
|
3108
|
+
() => this._currentRunConversationId,
|
|
3109
|
+
() => this._currentRunOwnerId ?? "anonymous"
|
|
3110
|
+
)
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
2967
3113
|
registerConfiguredBuiltInTools(config) {
|
|
2968
3114
|
for (const tool of createDefaultTools(this.workingDir)) {
|
|
2969
3115
|
if (this.isToolEnabled(tool.name)) {
|
|
@@ -3418,6 +3564,8 @@ var AgentHarness = class {
|
|
|
3418
3564
|
}
|
|
3419
3565
|
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
3420
3566
|
_currentRunConversationId;
|
|
3567
|
+
/** Owner ID of the currently executing run (used by subagent tools). */
|
|
3568
|
+
_currentRunOwnerId;
|
|
3421
3569
|
get browserSession() {
|
|
3422
3570
|
return this._browserSession;
|
|
3423
3571
|
}
|
|
@@ -3515,6 +3663,10 @@ var AgentHarness = class {
|
|
|
3515
3663
|
}
|
|
3516
3664
|
await this.refreshSkillsIfChanged();
|
|
3517
3665
|
this._currentRunConversationId = input.conversationId;
|
|
3666
|
+
const ownerParam = input.parameters?.__ownerId;
|
|
3667
|
+
if (typeof ownerParam === "string") {
|
|
3668
|
+
this._currentRunOwnerId = ownerParam;
|
|
3669
|
+
}
|
|
3518
3670
|
const agent = this.parsedAgent;
|
|
3519
3671
|
const runId = `run_${randomUUID3()}`;
|
|
3520
3672
|
const start = now();
|
|
@@ -3538,9 +3690,29 @@ var AgentHarness = class {
|
|
|
3538
3690
|
const developmentContext = this.environment === "development" ? `
|
|
3539
3691
|
|
|
3540
3692
|
${DEVELOPMENT_MODE_CONTEXT}` : "";
|
|
3693
|
+
const browserContext = this._browserSession ? `
|
|
3694
|
+
|
|
3695
|
+
## Browser Tools
|
|
3696
|
+
|
|
3697
|
+
The user has a live browser viewport displayed alongside the conversation. They can see everything the browser shows in real time and interact with it directly (click, type, scroll, paste).
|
|
3698
|
+
|
|
3699
|
+
### Authentication
|
|
3700
|
+
When a website requires authentication or credentials, do NOT ask the user to send them in the chat. Instead, navigate to the login page and let the user enter their credentials directly in the browser viewport. Wait for them to confirm they have logged in before continuing.
|
|
3701
|
+
|
|
3702
|
+
### Session persistence
|
|
3703
|
+
Browser sessions (cookies, localStorage, login state) are automatically saved and restored across conversations. If the user logged into a website in a previous conversation, that session is likely still active. Try navigating directly to the authenticated page before asking the user to log in again.
|
|
3704
|
+
|
|
3705
|
+
### Reading page content
|
|
3706
|
+
- Use \`browser_content\` to read the visible text on a page. This is fast and token-efficient.
|
|
3707
|
+
- Use \`browser_snapshot\` to get the accessibility tree with interactive element refs for clicking and typing.
|
|
3708
|
+
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
3709
|
+
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
3710
|
+
|
|
3711
|
+
### Tabs and resources
|
|
3712
|
+
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.` : "";
|
|
3541
3713
|
const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
|
|
3542
3714
|
|
|
3543
|
-
${this.skillContextWindow}` : `${systemPrompt}${developmentContext}`;
|
|
3715
|
+
${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
|
|
3544
3716
|
const mainMemory = this.memoryStore ? await this.memoryStore.getMainMemory() : void 0;
|
|
3545
3717
|
const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
|
|
3546
3718
|
...[truncated]` : mainMemory?.content;
|
|
@@ -3695,7 +3867,7 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
3695
3867
|
if (rich && rich.length > 0) {
|
|
3696
3868
|
return [{ role: "tool", content: rich }];
|
|
3697
3869
|
}
|
|
3698
|
-
const textContent = typeof msg.content === "string" ? msg.content :
|
|
3870
|
+
const textContent = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
3699
3871
|
try {
|
|
3700
3872
|
const parsed = JSON.parse(textContent);
|
|
3701
3873
|
if (!Array.isArray(parsed)) {
|
|
@@ -3745,7 +3917,7 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
3745
3917
|
}
|
|
3746
3918
|
}
|
|
3747
3919
|
if (msg.role === "assistant") {
|
|
3748
|
-
const assistantText = typeof msg.content === "string" ? msg.content :
|
|
3920
|
+
const assistantText = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
3749
3921
|
try {
|
|
3750
3922
|
const parsed = JSON.parse(assistantText);
|
|
3751
3923
|
if (typeof parsed === "object" && parsed !== null) {
|
|
@@ -3784,7 +3956,7 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
3784
3956
|
if (msg.role === "system") {
|
|
3785
3957
|
return [{
|
|
3786
3958
|
role: "system",
|
|
3787
|
-
content: typeof msg.content === "string" ? msg.content :
|
|
3959
|
+
content: typeof msg.content === "string" ? msg.content : getTextContent2(msg)
|
|
3788
3960
|
}];
|
|
3789
3961
|
}
|
|
3790
3962
|
if (msg.role === "user") {
|
|
@@ -4054,7 +4226,8 @@ ${textContent}` };
|
|
|
4054
4226
|
step,
|
|
4055
4227
|
workingDir: this.workingDir,
|
|
4056
4228
|
parameters: input.parameters ?? {},
|
|
4057
|
-
abortSignal: input.abortSignal
|
|
4229
|
+
abortSignal: input.abortSignal,
|
|
4230
|
+
conversationId: input.conversationId
|
|
4058
4231
|
};
|
|
4059
4232
|
const toolResultsForModel = [];
|
|
4060
4233
|
const richToolResults = [];
|
|
@@ -4159,11 +4332,14 @@ ${textContent}` };
|
|
|
4159
4332
|
});
|
|
4160
4333
|
} else {
|
|
4161
4334
|
span?.end({ result: { value: result2.output ?? null, isError: false } });
|
|
4335
|
+
const serialized = JSON.stringify(result2.output ?? null);
|
|
4336
|
+
const outputTokenEstimate = Math.ceil(serialized.length / 4);
|
|
4162
4337
|
yield pushEvent({
|
|
4163
4338
|
type: "tool:completed",
|
|
4164
4339
|
tool: result2.tool,
|
|
4165
4340
|
output: result2.output,
|
|
4166
|
-
duration: now() - batchStart
|
|
4341
|
+
duration: now() - batchStart,
|
|
4342
|
+
outputTokenEstimate
|
|
4167
4343
|
});
|
|
4168
4344
|
const { mediaItems, strippedOutput } = extractMediaFromToolOutput(result2.output);
|
|
4169
4345
|
toolResultsForModel.push({
|
|
@@ -4521,6 +4697,19 @@ var InMemoryConversationStore = class {
|
|
|
4521
4697
|
this.purgeExpired();
|
|
4522
4698
|
return Array.from(this.conversations.values()).filter((conversation) => conversation.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
4523
4699
|
}
|
|
4700
|
+
async listSummaries(ownerId = DEFAULT_OWNER) {
|
|
4701
|
+
this.purgeExpired();
|
|
4702
|
+
return Array.from(this.conversations.values()).filter((c) => c.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
|
|
4703
|
+
conversationId: c.conversationId,
|
|
4704
|
+
title: c.title,
|
|
4705
|
+
updatedAt: c.updatedAt,
|
|
4706
|
+
createdAt: c.createdAt,
|
|
4707
|
+
ownerId: c.ownerId,
|
|
4708
|
+
parentConversationId: c.parentConversationId,
|
|
4709
|
+
messageCount: c.messages.length,
|
|
4710
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0
|
|
4711
|
+
}));
|
|
4712
|
+
}
|
|
4524
4713
|
async get(conversationId) {
|
|
4525
4714
|
this.purgeExpired();
|
|
4526
4715
|
return this.conversations.get(conversationId);
|
|
@@ -4622,8 +4811,12 @@ var FileConversationStore = class {
|
|
|
4622
4811
|
conversationId: conversation.conversationId,
|
|
4623
4812
|
title: conversation.title,
|
|
4624
4813
|
updatedAt: conversation.updatedAt,
|
|
4814
|
+
createdAt: conversation.createdAt,
|
|
4625
4815
|
ownerId: conversation.ownerId,
|
|
4626
|
-
fileName: entry.name
|
|
4816
|
+
fileName: entry.name,
|
|
4817
|
+
parentConversationId: conversation.parentConversationId,
|
|
4818
|
+
messageCount: conversation.messages.length,
|
|
4819
|
+
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0
|
|
4627
4820
|
});
|
|
4628
4821
|
}
|
|
4629
4822
|
} catch {
|
|
@@ -4646,6 +4839,16 @@ var FileConversationStore = class {
|
|
|
4646
4839
|
for (const conversation of parsed.conversations ?? []) {
|
|
4647
4840
|
this.conversations.set(conversation.conversationId, conversation);
|
|
4648
4841
|
}
|
|
4842
|
+
let needsRebuild = false;
|
|
4843
|
+
for (const entry of this.conversations.values()) {
|
|
4844
|
+
if (entry.messageCount === void 0) {
|
|
4845
|
+
needsRebuild = true;
|
|
4846
|
+
break;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
if (needsRebuild) {
|
|
4850
|
+
await this.rebuildIndexFromFiles();
|
|
4851
|
+
}
|
|
4649
4852
|
} catch {
|
|
4650
4853
|
await this.rebuildIndexFromFiles();
|
|
4651
4854
|
}
|
|
@@ -4661,8 +4864,12 @@ var FileConversationStore = class {
|
|
|
4661
4864
|
conversationId: conversation.conversationId,
|
|
4662
4865
|
title: conversation.title,
|
|
4663
4866
|
updatedAt: conversation.updatedAt,
|
|
4867
|
+
createdAt: conversation.createdAt,
|
|
4664
4868
|
ownerId: conversation.ownerId,
|
|
4665
|
-
fileName
|
|
4869
|
+
fileName,
|
|
4870
|
+
parentConversationId: conversation.parentConversationId,
|
|
4871
|
+
messageCount: conversation.messages.length,
|
|
4872
|
+
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0
|
|
4666
4873
|
});
|
|
4667
4874
|
await this.writeIndex();
|
|
4668
4875
|
});
|
|
@@ -4680,6 +4887,19 @@ var FileConversationStore = class {
|
|
|
4680
4887
|
}
|
|
4681
4888
|
return conversations;
|
|
4682
4889
|
}
|
|
4890
|
+
async listSummaries(ownerId = DEFAULT_OWNER) {
|
|
4891
|
+
await this.ensureLoaded();
|
|
4892
|
+
return Array.from(this.conversations.values()).filter((c) => c.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
|
|
4893
|
+
conversationId: c.conversationId,
|
|
4894
|
+
title: c.title,
|
|
4895
|
+
updatedAt: c.updatedAt,
|
|
4896
|
+
createdAt: c.createdAt,
|
|
4897
|
+
ownerId: c.ownerId,
|
|
4898
|
+
parentConversationId: c.parentConversationId,
|
|
4899
|
+
messageCount: c.messageCount,
|
|
4900
|
+
hasPendingApprovals: c.hasPendingApprovals
|
|
4901
|
+
}));
|
|
4902
|
+
}
|
|
4683
4903
|
async get(conversationId) {
|
|
4684
4904
|
await this.ensureLoaded();
|
|
4685
4905
|
const summary = this.conversations.get(conversationId);
|
|
@@ -4914,6 +5134,30 @@ var KeyValueConversationStoreBase = class {
|
|
|
4914
5134
|
}
|
|
4915
5135
|
return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
4916
5136
|
}
|
|
5137
|
+
async listSummaries(ownerId = DEFAULT_OWNER) {
|
|
5138
|
+
const kv = await this.client();
|
|
5139
|
+
if (!kv) {
|
|
5140
|
+
return await this.memoryFallback.listSummaries(ownerId);
|
|
5141
|
+
}
|
|
5142
|
+
const ids = await this.getOwnerConversationIds(ownerId);
|
|
5143
|
+
const summaries = [];
|
|
5144
|
+
for (const id of ids) {
|
|
5145
|
+
const meta = await this.getConversationMeta(id);
|
|
5146
|
+
if (meta && meta.ownerId === ownerId) {
|
|
5147
|
+
summaries.push({
|
|
5148
|
+
conversationId: meta.conversationId,
|
|
5149
|
+
title: meta.title,
|
|
5150
|
+
updatedAt: meta.updatedAt,
|
|
5151
|
+
createdAt: meta.createdAt,
|
|
5152
|
+
ownerId: meta.ownerId,
|
|
5153
|
+
parentConversationId: meta.parentConversationId,
|
|
5154
|
+
messageCount: meta.messageCount,
|
|
5155
|
+
hasPendingApprovals: meta.hasPendingApprovals
|
|
5156
|
+
});
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
5160
|
+
}
|
|
4917
5161
|
async get(conversationId) {
|
|
4918
5162
|
const kv = await this.client();
|
|
4919
5163
|
if (!kv) {
|
|
@@ -4963,7 +5207,11 @@ var KeyValueConversationStoreBase = class {
|
|
|
4963
5207
|
conversationId: nextConversation.conversationId,
|
|
4964
5208
|
title: nextConversation.title,
|
|
4965
5209
|
updatedAt: nextConversation.updatedAt,
|
|
4966
|
-
|
|
5210
|
+
createdAt: nextConversation.createdAt,
|
|
5211
|
+
ownerId: nextConversation.ownerId,
|
|
5212
|
+
parentConversationId: nextConversation.parentConversationId,
|
|
5213
|
+
messageCount: nextConversation.messages.length,
|
|
5214
|
+
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0
|
|
4967
5215
|
}),
|
|
4968
5216
|
this.ttl
|
|
4969
5217
|
);
|
|
@@ -5429,7 +5677,7 @@ var TelemetryEmitter = class {
|
|
|
5429
5677
|
};
|
|
5430
5678
|
|
|
5431
5679
|
// src/index.ts
|
|
5432
|
-
import { defineTool as
|
|
5680
|
+
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
5433
5681
|
export {
|
|
5434
5682
|
AgentHarness,
|
|
5435
5683
|
InMemoryConversationStore,
|
|
@@ -5452,9 +5700,10 @@ export {
|
|
|
5452
5700
|
createModelProvider,
|
|
5453
5701
|
createSkillTools,
|
|
5454
5702
|
createStateStore,
|
|
5703
|
+
createSubagentTools,
|
|
5455
5704
|
createUploadStore,
|
|
5456
5705
|
createWriteTool,
|
|
5457
|
-
|
|
5706
|
+
defineTool5 as defineTool,
|
|
5458
5707
|
deriveUploadKey,
|
|
5459
5708
|
ensureAgentIdentity,
|
|
5460
5709
|
generateAgentId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"redis": "^5.10.0",
|
|
32
32
|
"yaml": "^2.4.0",
|
|
33
33
|
"zod": "^3.22.0",
|
|
34
|
-
"@poncho-ai/sdk": "1.
|
|
34
|
+
"@poncho-ai/sdk": "1.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/mustache": "^4.2.6",
|
package/src/harness.ts
CHANGED
|
@@ -29,6 +29,8 @@ import { addPromptCacheBreakpoints } from "./prompt-cache.js";
|
|
|
29
29
|
import { jsonSchemaToZod } from "./schema-converter.js";
|
|
30
30
|
import type { SkillMetadata } from "./skill-context.js";
|
|
31
31
|
import { createSkillTools, normalizeScriptPolicyPath } from "./skill-tools.js";
|
|
32
|
+
import { createSubagentTools } from "./subagent-tools.js";
|
|
33
|
+
import type { SubagentManager } from "./subagent-manager.js";
|
|
32
34
|
import { LatitudeTelemetry } from "@latitude-data/telemetry";
|
|
33
35
|
import { diag, DiagLogLevel } from "@opentelemetry/api";
|
|
34
36
|
import {
|
|
@@ -502,6 +504,7 @@ export class AgentHarness {
|
|
|
502
504
|
|
|
503
505
|
private parsedAgent?: ParsedAgent;
|
|
504
506
|
private mcpBridge?: LocalMcpBridge;
|
|
507
|
+
private subagentManager?: SubagentManager;
|
|
505
508
|
|
|
506
509
|
private resolveToolAccess(toolName: string): ToolAccess {
|
|
507
510
|
const tools = this.loadedConfig?.tools;
|
|
@@ -547,6 +550,21 @@ export class AgentHarness {
|
|
|
547
550
|
}
|
|
548
551
|
}
|
|
549
552
|
|
|
553
|
+
unregisterTools(names: string[]): void {
|
|
554
|
+
this.dispatcher.unregisterMany(names);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
setSubagentManager(manager: SubagentManager): void {
|
|
558
|
+
this.subagentManager = manager;
|
|
559
|
+
this.dispatcher.registerMany(
|
|
560
|
+
createSubagentTools(
|
|
561
|
+
manager,
|
|
562
|
+
() => this._currentRunConversationId,
|
|
563
|
+
() => this._currentRunOwnerId ?? "anonymous",
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
550
568
|
private registerConfiguredBuiltInTools(config: PonchoConfig | undefined): void {
|
|
551
569
|
for (const tool of createDefaultTools(this.workingDir)) {
|
|
552
570
|
if (this.isToolEnabled(tool.name)) {
|
|
@@ -1060,6 +1078,8 @@ export class AgentHarness {
|
|
|
1060
1078
|
|
|
1061
1079
|
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
1062
1080
|
private _currentRunConversationId?: string;
|
|
1081
|
+
/** Owner ID of the currently executing run (used by subagent tools). */
|
|
1082
|
+
private _currentRunOwnerId?: string;
|
|
1063
1083
|
|
|
1064
1084
|
get browserSession(): unknown {
|
|
1065
1085
|
return this._browserSession;
|
|
@@ -1180,8 +1200,12 @@ export class AgentHarness {
|
|
|
1180
1200
|
}
|
|
1181
1201
|
await this.refreshSkillsIfChanged();
|
|
1182
1202
|
|
|
1183
|
-
// Track which conversation this run belongs to so browser tools resolve
|
|
1203
|
+
// Track which conversation/owner this run belongs to so browser & subagent tools resolve correctly
|
|
1184
1204
|
this._currentRunConversationId = input.conversationId;
|
|
1205
|
+
const ownerParam = input.parameters?.__ownerId;
|
|
1206
|
+
if (typeof ownerParam === "string") {
|
|
1207
|
+
this._currentRunOwnerId = ownerParam;
|
|
1208
|
+
}
|
|
1185
1209
|
|
|
1186
1210
|
const agent = this.parsedAgent as ParsedAgent;
|
|
1187
1211
|
const runId = `run_${randomUUID()}`;
|
|
@@ -1210,9 +1234,29 @@ export class AgentHarness {
|
|
|
1210
1234
|
});
|
|
1211
1235
|
const developmentContext =
|
|
1212
1236
|
this.environment === "development" ? `\n\n${DEVELOPMENT_MODE_CONTEXT}` : "";
|
|
1237
|
+
const browserContext = this._browserSession
|
|
1238
|
+
? `\n\n## Browser Tools
|
|
1239
|
+
|
|
1240
|
+
The user has a live browser viewport displayed alongside the conversation. They can see everything the browser shows in real time and interact with it directly (click, type, scroll, paste).
|
|
1241
|
+
|
|
1242
|
+
### Authentication
|
|
1243
|
+
When a website requires authentication or credentials, do NOT ask the user to send them in the chat. Instead, navigate to the login page and let the user enter their credentials directly in the browser viewport. Wait for them to confirm they have logged in before continuing.
|
|
1244
|
+
|
|
1245
|
+
### Session persistence
|
|
1246
|
+
Browser sessions (cookies, localStorage, login state) are automatically saved and restored across conversations. If the user logged into a website in a previous conversation, that session is likely still active. Try navigating directly to the authenticated page before asking the user to log in again.
|
|
1247
|
+
|
|
1248
|
+
### Reading page content
|
|
1249
|
+
- Use \`browser_content\` to read the visible text on a page. This is fast and token-efficient.
|
|
1250
|
+
- Use \`browser_snapshot\` to get the accessibility tree with interactive element refs for clicking and typing.
|
|
1251
|
+
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
1252
|
+
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
1253
|
+
|
|
1254
|
+
### Tabs and resources
|
|
1255
|
+
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.`
|
|
1256
|
+
: "";
|
|
1213
1257
|
const promptWithSkills = this.skillContextWindow
|
|
1214
|
-
? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}`
|
|
1215
|
-
: `${systemPrompt}${developmentContext}`;
|
|
1258
|
+
? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}${browserContext}`
|
|
1259
|
+
: `${systemPrompt}${developmentContext}${browserContext}`;
|
|
1216
1260
|
const mainMemory = this.memoryStore
|
|
1217
1261
|
? await this.memoryStore.getMainMemory()
|
|
1218
1262
|
: undefined;
|
|
@@ -1839,6 +1883,7 @@ ${boundedMainMemory.trim()}`
|
|
|
1839
1883
|
workingDir: this.workingDir,
|
|
1840
1884
|
parameters: input.parameters ?? {},
|
|
1841
1885
|
abortSignal: input.abortSignal,
|
|
1886
|
+
conversationId: input.conversationId,
|
|
1842
1887
|
};
|
|
1843
1888
|
|
|
1844
1889
|
const toolResultsForModel: Array<{
|
|
@@ -1975,11 +2020,14 @@ ${boundedMainMemory.trim()}`
|
|
|
1975
2020
|
});
|
|
1976
2021
|
} else {
|
|
1977
2022
|
span?.end({ result: { value: result.output ?? null, isError: false } });
|
|
2023
|
+
const serialized = JSON.stringify(result.output ?? null);
|
|
2024
|
+
const outputTokenEstimate = Math.ceil(serialized.length / 4);
|
|
1978
2025
|
yield pushEvent({
|
|
1979
2026
|
type: "tool:completed",
|
|
1980
2027
|
tool: result.tool,
|
|
1981
2028
|
output: result.output,
|
|
1982
2029
|
duration: now() - batchStart,
|
|
2030
|
+
outputTokenEstimate,
|
|
1983
2031
|
});
|
|
1984
2032
|
|
|
1985
2033
|
const { mediaItems, strippedOutput } = extractMediaFromToolOutput(result.output);
|
package/src/index.ts
CHANGED
|
@@ -14,5 +14,7 @@ export * from "./state.js";
|
|
|
14
14
|
export * from "./upload-store.js";
|
|
15
15
|
export * from "./telemetry.js";
|
|
16
16
|
export * from "./tool-dispatcher.js";
|
|
17
|
+
export * from "./subagent-manager.js";
|
|
18
|
+
export * from "./subagent-tools.js";
|
|
17
19
|
export { defineTool } from "@poncho-ai/sdk";
|
|
18
20
|
export type { ToolDefinition } from "@poncho-ai/sdk";
|
package/src/latitude-capture.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Latitude telemetry integration for Vercel AI SDK
|
|
3
|
-
*
|
|
4
|
-
* TODO: Implement proper Vercel AI SDK telemetry integration using:
|
|
5
|
-
* - LatitudeTelemetry.capture() wrapper around streamText()
|
|
6
|
-
* - experimental_telemetry: { isEnabled: true } in streamText() options
|
|
7
|
-
*
|
|
8
|
-
* This requires @latitude-data/telemetry package which has official
|
|
9
|
-
* Vercel AI SDK support.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
export interface LatitudeCaptureConfig {
|
|
13
2
|
apiKeyEnv?: string;
|
|
14
3
|
projectIdEnv?: string;
|
|
@@ -17,8 +6,9 @@ export interface LatitudeCaptureConfig {
|
|
|
17
6
|
}
|
|
18
7
|
|
|
19
8
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
9
|
+
* Reads and validates Latitude telemetry configuration from environment
|
|
10
|
+
* variables. The actual telemetry capture is handled by LatitudeTelemetry
|
|
11
|
+
* from @latitude-data/telemetry in harness.ts (via runWithTelemetry).
|
|
22
12
|
*/
|
|
23
13
|
export class LatitudeCapture {
|
|
24
14
|
private readonly apiKey?: string;
|
package/src/state.ts
CHANGED
|
@@ -38,12 +38,22 @@ export interface Conversation {
|
|
|
38
38
|
}>;
|
|
39
39
|
ownerId: string;
|
|
40
40
|
tenantId: string | null;
|
|
41
|
+
contextTokens?: number;
|
|
42
|
+
contextWindow?: number;
|
|
43
|
+
parentConversationId?: string;
|
|
44
|
+
subagentMeta?: {
|
|
45
|
+
task: string;
|
|
46
|
+
status: "running" | "completed" | "error" | "stopped";
|
|
47
|
+
result?: import("@poncho-ai/sdk").RunResult;
|
|
48
|
+
error?: import("@poncho-ai/sdk").AgentFailure;
|
|
49
|
+
};
|
|
41
50
|
createdAt: number;
|
|
42
51
|
updatedAt: number;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export interface ConversationStore {
|
|
46
55
|
list(ownerId?: string): Promise<Conversation[]>;
|
|
56
|
+
listSummaries(ownerId?: string): Promise<ConversationSummary[]>;
|
|
47
57
|
get(conversationId: string): Promise<Conversation | undefined>;
|
|
48
58
|
create(ownerId?: string, title?: string): Promise<Conversation>;
|
|
49
59
|
update(conversation: Conversation): Promise<void>;
|
|
@@ -220,6 +230,23 @@ export class InMemoryConversationStore implements ConversationStore {
|
|
|
220
230
|
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
async listSummaries(ownerId = DEFAULT_OWNER): Promise<ConversationSummary[]> {
|
|
234
|
+
this.purgeExpired();
|
|
235
|
+
return Array.from(this.conversations.values())
|
|
236
|
+
.filter((c) => c.ownerId === ownerId)
|
|
237
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
238
|
+
.map((c) => ({
|
|
239
|
+
conversationId: c.conversationId,
|
|
240
|
+
title: c.title,
|
|
241
|
+
updatedAt: c.updatedAt,
|
|
242
|
+
createdAt: c.createdAt,
|
|
243
|
+
ownerId: c.ownerId,
|
|
244
|
+
parentConversationId: c.parentConversationId,
|
|
245
|
+
messageCount: c.messages.length,
|
|
246
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
223
250
|
async get(conversationId: string): Promise<Conversation | undefined> {
|
|
224
251
|
this.purgeExpired();
|
|
225
252
|
return this.conversations.get(conversationId);
|
|
@@ -266,14 +293,29 @@ export class InMemoryConversationStore implements ConversationStore {
|
|
|
266
293
|
}
|
|
267
294
|
}
|
|
268
295
|
|
|
296
|
+
export type ConversationSummary = {
|
|
297
|
+
conversationId: string;
|
|
298
|
+
title: string;
|
|
299
|
+
updatedAt: number;
|
|
300
|
+
createdAt?: number;
|
|
301
|
+
ownerId: string;
|
|
302
|
+
parentConversationId?: string;
|
|
303
|
+
messageCount?: number;
|
|
304
|
+
hasPendingApprovals?: boolean;
|
|
305
|
+
};
|
|
306
|
+
|
|
269
307
|
type ConversationStoreFile = {
|
|
270
308
|
schemaVersion: string;
|
|
271
309
|
conversations: Array<{
|
|
272
310
|
conversationId: string;
|
|
273
311
|
title: string;
|
|
274
312
|
updatedAt: number;
|
|
313
|
+
createdAt?: number;
|
|
275
314
|
ownerId: string;
|
|
276
315
|
fileName: string;
|
|
316
|
+
parentConversationId?: string;
|
|
317
|
+
messageCount?: number;
|
|
318
|
+
hasPendingApprovals?: boolean;
|
|
277
319
|
}>;
|
|
278
320
|
};
|
|
279
321
|
|
|
@@ -342,8 +384,12 @@ class FileConversationStore implements ConversationStore {
|
|
|
342
384
|
conversationId: conversation.conversationId,
|
|
343
385
|
title: conversation.title,
|
|
344
386
|
updatedAt: conversation.updatedAt,
|
|
387
|
+
createdAt: conversation.createdAt,
|
|
345
388
|
ownerId: conversation.ownerId,
|
|
346
389
|
fileName: entry.name,
|
|
390
|
+
parentConversationId: conversation.parentConversationId,
|
|
391
|
+
messageCount: conversation.messages.length,
|
|
392
|
+
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0,
|
|
347
393
|
});
|
|
348
394
|
}
|
|
349
395
|
} catch {
|
|
@@ -369,6 +415,17 @@ class FileConversationStore implements ConversationStore {
|
|
|
369
415
|
for (const conversation of parsed.conversations ?? []) {
|
|
370
416
|
this.conversations.set(conversation.conversationId, conversation);
|
|
371
417
|
}
|
|
418
|
+
// Rebuild if any entry is from an older index format (missing messageCount)
|
|
419
|
+
let needsRebuild = false;
|
|
420
|
+
for (const entry of this.conversations.values()) {
|
|
421
|
+
if (entry.messageCount === undefined) {
|
|
422
|
+
needsRebuild = true;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (needsRebuild) {
|
|
427
|
+
await this.rebuildIndexFromFiles();
|
|
428
|
+
}
|
|
372
429
|
} catch {
|
|
373
430
|
await this.rebuildIndexFromFiles();
|
|
374
431
|
}
|
|
@@ -385,8 +442,12 @@ class FileConversationStore implements ConversationStore {
|
|
|
385
442
|
conversationId: conversation.conversationId,
|
|
386
443
|
title: conversation.title,
|
|
387
444
|
updatedAt: conversation.updatedAt,
|
|
445
|
+
createdAt: conversation.createdAt,
|
|
388
446
|
ownerId: conversation.ownerId,
|
|
389
447
|
fileName,
|
|
448
|
+
parentConversationId: conversation.parentConversationId,
|
|
449
|
+
messageCount: conversation.messages.length,
|
|
450
|
+
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0,
|
|
390
451
|
});
|
|
391
452
|
await this.writeIndex();
|
|
392
453
|
});
|
|
@@ -408,6 +469,23 @@ class FileConversationStore implements ConversationStore {
|
|
|
408
469
|
return conversations;
|
|
409
470
|
}
|
|
410
471
|
|
|
472
|
+
async listSummaries(ownerId = DEFAULT_OWNER): Promise<ConversationSummary[]> {
|
|
473
|
+
await this.ensureLoaded();
|
|
474
|
+
return Array.from(this.conversations.values())
|
|
475
|
+
.filter((c) => c.ownerId === ownerId)
|
|
476
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
477
|
+
.map((c) => ({
|
|
478
|
+
conversationId: c.conversationId,
|
|
479
|
+
title: c.title,
|
|
480
|
+
updatedAt: c.updatedAt,
|
|
481
|
+
createdAt: c.createdAt,
|
|
482
|
+
ownerId: c.ownerId,
|
|
483
|
+
parentConversationId: c.parentConversationId,
|
|
484
|
+
messageCount: c.messageCount,
|
|
485
|
+
hasPendingApprovals: c.hasPendingApprovals,
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
|
|
411
489
|
async get(conversationId: string): Promise<Conversation | undefined> {
|
|
412
490
|
await this.ensureLoaded();
|
|
413
491
|
const summary = this.conversations.get(conversationId);
|
|
@@ -573,7 +651,11 @@ type ConversationMeta = {
|
|
|
573
651
|
conversationId: string;
|
|
574
652
|
title: string;
|
|
575
653
|
updatedAt: number;
|
|
654
|
+
createdAt?: number;
|
|
576
655
|
ownerId: string;
|
|
656
|
+
parentConversationId?: string;
|
|
657
|
+
messageCount?: number;
|
|
658
|
+
hasPendingApprovals?: boolean;
|
|
577
659
|
};
|
|
578
660
|
|
|
579
661
|
abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
@@ -690,6 +772,31 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
|
690
772
|
return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
691
773
|
}
|
|
692
774
|
|
|
775
|
+
async listSummaries(ownerId = DEFAULT_OWNER): Promise<ConversationSummary[]> {
|
|
776
|
+
const kv = await this.client();
|
|
777
|
+
if (!kv) {
|
|
778
|
+
return await this.memoryFallback.listSummaries(ownerId);
|
|
779
|
+
}
|
|
780
|
+
const ids = await this.getOwnerConversationIds(ownerId);
|
|
781
|
+
const summaries: ConversationSummary[] = [];
|
|
782
|
+
for (const id of ids) {
|
|
783
|
+
const meta = await this.getConversationMeta(id);
|
|
784
|
+
if (meta && meta.ownerId === ownerId) {
|
|
785
|
+
summaries.push({
|
|
786
|
+
conversationId: meta.conversationId,
|
|
787
|
+
title: meta.title,
|
|
788
|
+
updatedAt: meta.updatedAt,
|
|
789
|
+
createdAt: meta.createdAt,
|
|
790
|
+
ownerId: meta.ownerId,
|
|
791
|
+
parentConversationId: meta.parentConversationId,
|
|
792
|
+
messageCount: meta.messageCount,
|
|
793
|
+
hasPendingApprovals: meta.hasPendingApprovals,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
798
|
+
}
|
|
799
|
+
|
|
693
800
|
async get(conversationId: string): Promise<Conversation | undefined> {
|
|
694
801
|
const kv = await this.client();
|
|
695
802
|
if (!kv) {
|
|
@@ -741,7 +848,11 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
|
741
848
|
conversationId: nextConversation.conversationId,
|
|
742
849
|
title: nextConversation.title,
|
|
743
850
|
updatedAt: nextConversation.updatedAt,
|
|
851
|
+
createdAt: nextConversation.createdAt,
|
|
744
852
|
ownerId: nextConversation.ownerId,
|
|
853
|
+
parentConversationId: nextConversation.parentConversationId,
|
|
854
|
+
messageCount: nextConversation.messages.length,
|
|
855
|
+
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0,
|
|
745
856
|
} satisfies ConversationMeta),
|
|
746
857
|
this.ttl,
|
|
747
858
|
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentFailure, Message, RunResult } from "@poncho-ai/sdk";
|
|
2
|
+
|
|
3
|
+
export interface SubagentResult {
|
|
4
|
+
subagentId: string;
|
|
5
|
+
status: "completed" | "error" | "stopped";
|
|
6
|
+
latestMessages?: Message[];
|
|
7
|
+
result?: RunResult;
|
|
8
|
+
error?: AgentFailure;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SubagentSummary {
|
|
12
|
+
subagentId: string;
|
|
13
|
+
task: string;
|
|
14
|
+
status: string;
|
|
15
|
+
messageCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SubagentManager {
|
|
19
|
+
spawn(opts: {
|
|
20
|
+
task: string;
|
|
21
|
+
parentConversationId: string;
|
|
22
|
+
ownerId: string;
|
|
23
|
+
}): Promise<SubagentResult>;
|
|
24
|
+
|
|
25
|
+
sendMessage(subagentId: string, message: string): Promise<SubagentResult>;
|
|
26
|
+
|
|
27
|
+
stop(subagentId: string): Promise<void>;
|
|
28
|
+
|
|
29
|
+
list(parentConversationId: string): Promise<SubagentSummary[]>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { defineTool, type Message, type ToolDefinition, getTextContent } from "@poncho-ai/sdk";
|
|
2
|
+
import type { SubagentManager, SubagentResult } from "./subagent-manager.js";
|
|
3
|
+
|
|
4
|
+
const LAST_MESSAGES_TO_RETURN = 10;
|
|
5
|
+
|
|
6
|
+
const summarizeResult = (r: SubagentResult): Record<string, unknown> => {
|
|
7
|
+
const summary: Record<string, unknown> = {
|
|
8
|
+
subagentId: r.subagentId,
|
|
9
|
+
status: r.status,
|
|
10
|
+
};
|
|
11
|
+
if (r.result) {
|
|
12
|
+
summary.result = {
|
|
13
|
+
status: r.result.status,
|
|
14
|
+
response: r.result.response,
|
|
15
|
+
steps: r.result.steps,
|
|
16
|
+
duration: r.result.duration,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (r.error) {
|
|
20
|
+
summary.error = r.error;
|
|
21
|
+
}
|
|
22
|
+
if (r.latestMessages && r.latestMessages.length > 0) {
|
|
23
|
+
summary.latestMessages = r.latestMessages
|
|
24
|
+
.slice(-LAST_MESSAGES_TO_RETURN)
|
|
25
|
+
.map((m: Message) => ({
|
|
26
|
+
role: m.role,
|
|
27
|
+
content: getTextContent(m).slice(0, 2000),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
return summary;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const createSubagentTools = (
|
|
34
|
+
manager: SubagentManager,
|
|
35
|
+
getConversationId: () => string | undefined,
|
|
36
|
+
getOwnerId: () => string,
|
|
37
|
+
): ToolDefinition[] => [
|
|
38
|
+
defineTool({
|
|
39
|
+
name: "spawn_subagent",
|
|
40
|
+
description:
|
|
41
|
+
"Spawn a subagent to work on a task and wait for it to finish. The subagent is a full copy of " +
|
|
42
|
+
"yourself running in its own conversation context with access to the same tools (except memory writes). " +
|
|
43
|
+
"This call blocks until the subagent completes and returns its result.\n\n" +
|
|
44
|
+
"Guidelines:\n" +
|
|
45
|
+
"- Use subagents to parallelize work: call spawn_subagent multiple times in one response for independent sub-tasks -- they run concurrently.\n" +
|
|
46
|
+
"- Prefer doing work yourself for simple or quick tasks. Spawn subagents for substantial, self-contained work.\n" +
|
|
47
|
+
"- The subagent has no memory of your conversation -- write thorough, self-contained instructions in the task.",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
task: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description:
|
|
54
|
+
"Thorough, self-contained instructions for the subagent. Include all relevant context, " +
|
|
55
|
+
"goals, and constraints -- the subagent starts with zero prior conversation history.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ["task"],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
handler: async (input) => {
|
|
62
|
+
const task = typeof input.task === "string" ? input.task : "";
|
|
63
|
+
if (!task.trim()) {
|
|
64
|
+
return { error: "task is required" };
|
|
65
|
+
}
|
|
66
|
+
const conversationId = getConversationId();
|
|
67
|
+
if (!conversationId) {
|
|
68
|
+
return { error: "no active conversation to spawn subagent from" };
|
|
69
|
+
}
|
|
70
|
+
const result = await manager.spawn({
|
|
71
|
+
task: task.trim(),
|
|
72
|
+
parentConversationId: conversationId,
|
|
73
|
+
ownerId: getOwnerId(),
|
|
74
|
+
});
|
|
75
|
+
return summarizeResult(result);
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
defineTool({
|
|
80
|
+
name: "message_subagent",
|
|
81
|
+
description:
|
|
82
|
+
"Send a follow-up message to a completed or stopped subagent and wait for it to finish. " +
|
|
83
|
+
"This restarts the subagent with the new message and blocks until it completes. " +
|
|
84
|
+
"Only works when the subagent is not currently running.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
subagent_id: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "The subagent ID (from spawn_subagent result or list_subagents).",
|
|
91
|
+
},
|
|
92
|
+
message: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "The follow-up instructions or message to send.",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["subagent_id", "message"],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
handler: async (input) => {
|
|
101
|
+
const subagentId = typeof input.subagent_id === "string" ? input.subagent_id : "";
|
|
102
|
+
const message = typeof input.message === "string" ? input.message : "";
|
|
103
|
+
if (!subagentId || !message.trim()) {
|
|
104
|
+
return { error: "subagent_id and message are required" };
|
|
105
|
+
}
|
|
106
|
+
const result = await manager.sendMessage(subagentId, message.trim());
|
|
107
|
+
return summarizeResult(result);
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
|
|
111
|
+
defineTool({
|
|
112
|
+
name: "stop_subagent",
|
|
113
|
+
description:
|
|
114
|
+
"Stop a running subagent. The subagent's conversation is preserved but it will stop processing. " +
|
|
115
|
+
"Use this to cancel work that is no longer needed.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
subagent_id: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "The subagent ID (from spawn_subagent result or list_subagents).",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
required: ["subagent_id"],
|
|
125
|
+
additionalProperties: false,
|
|
126
|
+
},
|
|
127
|
+
handler: async (input) => {
|
|
128
|
+
const subagentId = typeof input.subagent_id === "string" ? input.subagent_id : "";
|
|
129
|
+
if (!subagentId) {
|
|
130
|
+
return { error: "subagent_id is required" };
|
|
131
|
+
}
|
|
132
|
+
await manager.stop(subagentId);
|
|
133
|
+
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
|
|
137
|
+
defineTool({
|
|
138
|
+
name: "list_subagents",
|
|
139
|
+
description:
|
|
140
|
+
"List all subagents that have been spawned in this conversation. Returns each subagent's ID, " +
|
|
141
|
+
"original task, current status, and message count. Use this to look up subagent IDs before " +
|
|
142
|
+
"calling message_subagent or stop_subagent.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {},
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
},
|
|
148
|
+
handler: async () => {
|
|
149
|
+
const conversationId = getConversationId();
|
|
150
|
+
if (!conversationId) {
|
|
151
|
+
return { error: "no active conversation" };
|
|
152
|
+
}
|
|
153
|
+
const subagents = await manager.list(conversationId);
|
|
154
|
+
if (subagents.length === 0) {
|
|
155
|
+
return { message: "No subagents have been spawned in this conversation." };
|
|
156
|
+
}
|
|
157
|
+
return { subagents };
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
];
|
package/.turbo/turbo-lint.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @poncho-ai/harness@0.14.2 test /Users/cesar/Dev/latitude/poncho-ai/packages/harness
|
|
3
|
-
> vitest
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[7m[1m[36m RUN [39m[22m[27m [36mv1.6.1[39m [90m/Users/cesar/Dev/latitude/poncho-ai/packages/harness[39m
|
|
7
|
-
|
|
8
|
-
[event] step:completed {"type":"step:completed","step":1,"duration":1}
|
|
9
|
-
[32m✓[39m test/telemetry.test.ts [2m ([22m[2m3 tests[22m[2m)[22m[90m 3[2mms[22m[39m
|
|
10
|
-
[event] step:started {"type":"step:started","step":2}
|
|
11
|
-
[32m✓[39m test/schema-converter.test.ts [2m ([22m[2m27 tests[22m[2m)[22m[90m 16[2mms[22m[39m
|
|
12
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mdiscovers and calls tools over streamable HTTP[22m[39m
|
|
13
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
14
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
15
|
-
|
|
16
|
-
[32m✓[39m test/agent-parser.test.ts [2m ([22m[2m10 tests[22m[2m)[22m[90m 17[2mms[22m[39m
|
|
17
|
-
[32m✓[39m test/memory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 16[2mms[22m[39m
|
|
18
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mselects discovered tools by requested patterns[22m[39m
|
|
19
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
20
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
21
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
22
|
-
|
|
23
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
24
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":0,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
25
|
-
|
|
26
|
-
[90mstderr[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
27
|
-
[poncho][mcp] {"event":"auth.token_missing","server":"remote","tokenEnv":"MISSING_TOKEN_ENV"}
|
|
28
|
-
|
|
29
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mreturns actionable errors for 403 permission failures[22m[39m
|
|
30
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
31
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
32
|
-
|
|
33
|
-
[32m✓[39m test/mcp.test.ts [2m ([22m[2m6 tests[22m[2m)[22m[90m 97[2mms[22m[39m
|
|
34
|
-
[32m✓[39m test/state.test.ts [2m ([22m[2m5 tests[22m[2m)[22m[90m 217[2mms[22m[39m
|
|
35
|
-
[32m✓[39m test/agent-identity.test.ts [2m ([22m[2m2 tests[22m[2m)[22m[90m 15[2mms[22m[39m
|
|
36
|
-
[32m✓[39m test/model-factory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 3[2mms[22m[39m
|
|
37
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters default filesystem tools[22m[39m
|
|
38
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
39
|
-
|
|
40
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdisables write_file by default in production environment[22m[39m
|
|
41
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
42
|
-
|
|
43
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows disabling built-in tools via poncho.config.js[22m[39m
|
|
44
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
45
|
-
|
|
46
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
47
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
48
|
-
|
|
49
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
50
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
51
|
-
|
|
52
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not auto-register exported tool objects from skill scripts[22m[39m
|
|
53
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
54
|
-
|
|
55
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
56
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
57
|
-
|
|
58
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
59
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
60
|
-
|
|
61
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
62
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:beta","requestedPatterns":[]}
|
|
63
|
-
|
|
64
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
65
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
66
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:obsolete","requestedPatterns":[]}
|
|
67
|
-
|
|
68
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
69
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
70
|
-
|
|
71
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not refresh skills outside development mode[22m[39m
|
|
72
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
73
|
-
|
|
74
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
75
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
76
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:alpha","requestedPatterns":[]}
|
|
77
|
-
|
|
78
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
79
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
80
|
-
|
|
81
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mlists skill scripts through list_skill_scripts[22m[39m
|
|
82
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
83
|
-
|
|
84
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns JavaScript/TypeScript skill scripts through run_skill_script[22m[39m
|
|
85
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
86
|
-
|
|
87
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns AGENT-scope scripts from root scripts directory[22m[39m
|
|
88
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
89
|
-
|
|
90
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mblocks path traversal in run_skill_script[22m[39m
|
|
91
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
92
|
-
|
|
93
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrequires allowed-tools entries for non-standard script directories[22m[39m
|
|
94
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
95
|
-
|
|
96
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters MCP tools dynamically for stacked active skills and supports deactivation[22m[39m
|
|
97
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
98
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
99
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
100
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-a","requestedPatterns":["remote/a"],"registeredCount":1,"activeSkills":["skill-a"]}
|
|
101
|
-
|
|
102
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters MCP tools dynamically for stacked active skills and supports deactivation[22m[39m
|
|
103
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":2,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
104
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-b","requestedPatterns":["remote/a","remote/b"],"registeredCount":2,"activeSkills":["skill-a","skill-b"]}
|
|
105
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
106
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"deactivate:skill-a","requestedPatterns":["remote/b"],"registeredCount":1,"activeSkills":["skill-b"]}
|
|
107
|
-
|
|
108
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports flat tool access config format[22m[39m
|
|
109
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
110
|
-
|
|
111
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mflat tool access takes priority over legacy defaults[22m[39m
|
|
112
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
113
|
-
|
|
114
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mbyEnvironment overrides flat tool access[22m[39m
|
|
115
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
116
|
-
|
|
117
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisterTools skips tools disabled via config[22m[39m
|
|
118
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
119
|
-
|
|
120
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mapproval access level registers the tool but marks it for approval[22m[39m
|
|
121
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
122
|
-
|
|
123
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mtools without approval config do not require approval[22m[39m
|
|
124
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
125
|
-
|
|
126
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows in-flight MCP calls to finish after skill deactivation[22m[39m
|
|
127
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
128
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
129
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
130
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-slow","requestedPatterns":["remote/slow"],"registeredCount":1,"activeSkills":["skill-slow"]}
|
|
131
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"deactivate:skill-slow","requestedPatterns":[]}
|
|
132
|
-
|
|
133
|
-
[32m✓[39m test/harness.test.ts [2m ([22m[2m25 tests[22m[2m)[22m[33m 365[2mms[22m[39m
|
|
134
|
-
|
|
135
|
-
[2m Test Files [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
136
|
-
[2m Tests [22m [1m[32m86 passed[39m[22m[90m (86)[39m
|
|
137
|
-
[2m Start at [22m 13:30:36
|
|
138
|
-
[2m Duration [22m 2.19s[2m (transform 1.11s, setup 0ms, collect 2.81s, tests 749ms, environment 5ms, prepare 1.23s)[22m
|
|
139
|
-
|