@poncho-ai/harness 0.25.0 → 0.26.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 +6 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +497 -286
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/harness.ts +18 -2
- package/src/kv-store.ts +206 -0
- package/src/memory.ts +26 -291
- package/src/todo-tools.ts +363 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -135
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { defineTool, type ToolDefinition } from "@poncho-ai/sdk";
|
|
4
|
+
import type { StateConfig } from "./state.js";
|
|
5
|
+
import {
|
|
6
|
+
ensureAgentIdentity,
|
|
7
|
+
getAgentStoreDirectory,
|
|
8
|
+
slugifyStorageComponent,
|
|
9
|
+
STORAGE_SCHEMA_VERSION,
|
|
10
|
+
} from "./agent-identity.js";
|
|
11
|
+
import { createRawKVStore, type RawKVStore } from "./kv-store.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Data model
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export type TodoStatus = "pending" | "in_progress" | "completed";
|
|
18
|
+
export type TodoPriority = "high" | "medium" | "low";
|
|
19
|
+
|
|
20
|
+
export interface TodoItem {
|
|
21
|
+
id: string;
|
|
22
|
+
content: string;
|
|
23
|
+
status: TodoStatus;
|
|
24
|
+
priority: TodoPriority;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
updatedAt: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TodoStore {
|
|
30
|
+
get(conversationId: string): Promise<TodoItem[]>;
|
|
31
|
+
set(conversationId: string, todos: TodoItem[]): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const VALID_STATUSES: TodoStatus[] = ["pending", "in_progress", "completed"];
|
|
39
|
+
const VALID_PRIORITIES: TodoPriority[] = ["high", "medium", "low"];
|
|
40
|
+
const TODOS_DIRECTORY = "todos";
|
|
41
|
+
|
|
42
|
+
const writeJsonAtomic = async (filePath: string, payload: unknown): Promise<void> => {
|
|
43
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
44
|
+
const tmpPath = `${filePath}.tmp`;
|
|
45
|
+
await writeFile(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
46
|
+
await rename(tmpPath, filePath);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const parseTodoList = (raw: unknown): TodoItem[] => {
|
|
50
|
+
if (!Array.isArray(raw)) return [];
|
|
51
|
+
return raw.filter(
|
|
52
|
+
(item): item is TodoItem =>
|
|
53
|
+
typeof item === "object" &&
|
|
54
|
+
item !== null &&
|
|
55
|
+
typeof (item as Record<string, unknown>).id === "string" &&
|
|
56
|
+
typeof (item as Record<string, unknown>).content === "string",
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const generateId = (): string =>
|
|
61
|
+
(globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`).slice(0, 8);
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// InMemoryTodoStore
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
class InMemoryTodoStore implements TodoStore {
|
|
68
|
+
private readonly store = new Map<string, TodoItem[]>();
|
|
69
|
+
|
|
70
|
+
async get(conversationId: string): Promise<TodoItem[]> {
|
|
71
|
+
return this.store.get(conversationId) ?? [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async set(conversationId: string, todos: TodoItem[]): Promise<void> {
|
|
75
|
+
this.store.set(conversationId, todos);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// FileTodoStore — one JSON file per conversation
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
class FileTodoStore implements TodoStore {
|
|
84
|
+
private readonly workingDir: string;
|
|
85
|
+
private todosDir = "";
|
|
86
|
+
|
|
87
|
+
constructor(workingDir: string) {
|
|
88
|
+
this.workingDir = workingDir;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async ensureTodosDir(): Promise<string> {
|
|
92
|
+
if (this.todosDir) return this.todosDir;
|
|
93
|
+
const identity = await ensureAgentIdentity(this.workingDir);
|
|
94
|
+
this.todosDir = resolve(getAgentStoreDirectory(identity), TODOS_DIRECTORY);
|
|
95
|
+
await mkdir(this.todosDir, { recursive: true });
|
|
96
|
+
return this.todosDir;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async filePath(conversationId: string): Promise<string> {
|
|
100
|
+
const dir = await this.ensureTodosDir();
|
|
101
|
+
return resolve(dir, `${slugifyStorageComponent(conversationId)}.json`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async get(conversationId: string): Promise<TodoItem[]> {
|
|
105
|
+
try {
|
|
106
|
+
const fp = await this.filePath(conversationId);
|
|
107
|
+
const raw = await readFile(fp, "utf8");
|
|
108
|
+
return parseTodoList(JSON.parse(raw));
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async set(conversationId: string, todos: TodoItem[]): Promise<void> {
|
|
115
|
+
const fp = await this.filePath(conversationId);
|
|
116
|
+
await writeJsonAtomic(fp, todos);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// KVBackedTodoStore — wraps any RawKVStore (Upstash, Redis, DynamoDB)
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
class KVBackedTodoStore implements TodoStore {
|
|
125
|
+
private readonly kv: RawKVStore;
|
|
126
|
+
private readonly baseKey: string;
|
|
127
|
+
private readonly ttl?: number;
|
|
128
|
+
private readonly memoryFallback = new InMemoryTodoStore();
|
|
129
|
+
|
|
130
|
+
constructor(kv: RawKVStore, baseKey: string, ttl?: number) {
|
|
131
|
+
this.kv = kv;
|
|
132
|
+
this.baseKey = baseKey;
|
|
133
|
+
this.ttl = ttl;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private keyFor(conversationId: string): string {
|
|
137
|
+
return `${this.baseKey}:${slugifyStorageComponent(conversationId)}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async get(conversationId: string): Promise<TodoItem[]> {
|
|
141
|
+
try {
|
|
142
|
+
const raw = await this.kv.get(this.keyFor(conversationId));
|
|
143
|
+
if (!raw) return [];
|
|
144
|
+
return parseTodoList(JSON.parse(raw));
|
|
145
|
+
} catch {
|
|
146
|
+
return this.memoryFallback.get(conversationId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async set(conversationId: string, todos: TodoItem[]): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const serialized = JSON.stringify(todos);
|
|
153
|
+
const key = this.keyFor(conversationId);
|
|
154
|
+
if (typeof this.ttl === "number") {
|
|
155
|
+
await this.kv.setWithTtl(key, serialized, Math.max(1, this.ttl));
|
|
156
|
+
} else {
|
|
157
|
+
await this.kv.set(key, serialized);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
await this.memoryFallback.set(conversationId, todos);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Factory
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
export const createTodoStore = (
|
|
170
|
+
agentId: string,
|
|
171
|
+
config?: StateConfig,
|
|
172
|
+
options?: { workingDir?: string },
|
|
173
|
+
): TodoStore => {
|
|
174
|
+
const provider = config?.provider ?? "local";
|
|
175
|
+
const ttl = config?.ttl;
|
|
176
|
+
const workingDir = options?.workingDir ?? process.cwd();
|
|
177
|
+
|
|
178
|
+
if (provider === "local") {
|
|
179
|
+
return new FileTodoStore(workingDir);
|
|
180
|
+
}
|
|
181
|
+
if (provider === "memory") {
|
|
182
|
+
return new InMemoryTodoStore();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const kv = createRawKVStore(config);
|
|
186
|
+
if (kv) {
|
|
187
|
+
const baseKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:todos`;
|
|
188
|
+
return new KVBackedTodoStore(kv, baseKey, ttl);
|
|
189
|
+
}
|
|
190
|
+
return new InMemoryTodoStore();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Tool definitions
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
export const createTodoTools = (store: TodoStore): ToolDefinition[] => {
|
|
198
|
+
const resolveKey = (context: { conversationId?: string; runId: string }): string =>
|
|
199
|
+
context.conversationId || context.runId;
|
|
200
|
+
|
|
201
|
+
return [
|
|
202
|
+
defineTool({
|
|
203
|
+
name: "todo_list",
|
|
204
|
+
description:
|
|
205
|
+
"List all todo items for the current conversation. " +
|
|
206
|
+
"Use this to check progress and plan next steps.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
status: {
|
|
211
|
+
type: "string",
|
|
212
|
+
enum: VALID_STATUSES,
|
|
213
|
+
description: "Filter by status (omit to list all)",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
additionalProperties: false,
|
|
217
|
+
},
|
|
218
|
+
handler: async (input, context) => {
|
|
219
|
+
const key = resolveKey(context);
|
|
220
|
+
let todos = await store.get(key);
|
|
221
|
+
const status = typeof input.status === "string" ? input.status : undefined;
|
|
222
|
+
if (status && VALID_STATUSES.includes(status as TodoStatus)) {
|
|
223
|
+
todos = todos.filter((t) => t.status === status);
|
|
224
|
+
}
|
|
225
|
+
return { todos, count: todos.length };
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
|
|
229
|
+
defineTool({
|
|
230
|
+
name: "todo_add",
|
|
231
|
+
description:
|
|
232
|
+
"Add a new todo item for the current conversation. " +
|
|
233
|
+
"Use proactively for complex multi-step tasks (3+ steps).",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
content: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Description of the task",
|
|
240
|
+
},
|
|
241
|
+
status: {
|
|
242
|
+
type: "string",
|
|
243
|
+
enum: VALID_STATUSES,
|
|
244
|
+
description: "Initial status (default: pending)",
|
|
245
|
+
},
|
|
246
|
+
priority: {
|
|
247
|
+
type: "string",
|
|
248
|
+
enum: VALID_PRIORITIES,
|
|
249
|
+
description: "Priority level (default: medium)",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
required: ["content"],
|
|
253
|
+
additionalProperties: false,
|
|
254
|
+
},
|
|
255
|
+
handler: async (input, context) => {
|
|
256
|
+
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
257
|
+
if (!content) throw new Error("content is required");
|
|
258
|
+
const status: TodoStatus =
|
|
259
|
+
typeof input.status === "string" && VALID_STATUSES.includes(input.status as TodoStatus)
|
|
260
|
+
? (input.status as TodoStatus)
|
|
261
|
+
: "pending";
|
|
262
|
+
const priority: TodoPriority =
|
|
263
|
+
typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority as TodoPriority)
|
|
264
|
+
? (input.priority as TodoPriority)
|
|
265
|
+
: "medium";
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
const todo: TodoItem = {
|
|
268
|
+
id: generateId(),
|
|
269
|
+
content,
|
|
270
|
+
status,
|
|
271
|
+
priority,
|
|
272
|
+
createdAt: now,
|
|
273
|
+
updatedAt: now,
|
|
274
|
+
};
|
|
275
|
+
const key = resolveKey(context);
|
|
276
|
+
const todos = await store.get(key);
|
|
277
|
+
todos.push(todo);
|
|
278
|
+
await store.set(key, todos);
|
|
279
|
+
return { todo, todos };
|
|
280
|
+
},
|
|
281
|
+
}),
|
|
282
|
+
|
|
283
|
+
defineTool({
|
|
284
|
+
name: "todo_update",
|
|
285
|
+
description:
|
|
286
|
+
"Update an existing todo item's status, content, or priority. " +
|
|
287
|
+
"Mark tasks in_progress when starting and completed when done.",
|
|
288
|
+
inputSchema: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
id: {
|
|
292
|
+
type: "string",
|
|
293
|
+
description: "ID of the todo to update",
|
|
294
|
+
},
|
|
295
|
+
status: {
|
|
296
|
+
type: "string",
|
|
297
|
+
enum: VALID_STATUSES,
|
|
298
|
+
description: "New status",
|
|
299
|
+
},
|
|
300
|
+
content: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "New content/description",
|
|
303
|
+
},
|
|
304
|
+
priority: {
|
|
305
|
+
type: "string",
|
|
306
|
+
enum: VALID_PRIORITIES,
|
|
307
|
+
description: "New priority level",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
required: ["id"],
|
|
311
|
+
additionalProperties: false,
|
|
312
|
+
},
|
|
313
|
+
handler: async (input, context) => {
|
|
314
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
315
|
+
if (!id) throw new Error("id is required");
|
|
316
|
+
const key = resolveKey(context);
|
|
317
|
+
const todos = await store.get(key);
|
|
318
|
+
const todo = todos.find((t) => t.id === id);
|
|
319
|
+
if (!todo) throw new Error(`Todo with id "${id}" not found`);
|
|
320
|
+
|
|
321
|
+
if (typeof input.status === "string" && VALID_STATUSES.includes(input.status as TodoStatus)) {
|
|
322
|
+
todo.status = input.status as TodoStatus;
|
|
323
|
+
}
|
|
324
|
+
if (typeof input.content === "string" && input.content.trim()) {
|
|
325
|
+
todo.content = input.content.trim();
|
|
326
|
+
}
|
|
327
|
+
if (typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority as TodoPriority)) {
|
|
328
|
+
todo.priority = input.priority as TodoPriority;
|
|
329
|
+
}
|
|
330
|
+
todo.updatedAt = Date.now();
|
|
331
|
+
await store.set(key, todos);
|
|
332
|
+
return { todo, todos };
|
|
333
|
+
},
|
|
334
|
+
}),
|
|
335
|
+
|
|
336
|
+
defineTool({
|
|
337
|
+
name: "todo_remove",
|
|
338
|
+
description: "Remove a todo item by ID.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
id: {
|
|
343
|
+
type: "string",
|
|
344
|
+
description: "ID of the todo to remove",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
required: ["id"],
|
|
348
|
+
additionalProperties: false,
|
|
349
|
+
},
|
|
350
|
+
handler: async (input, context) => {
|
|
351
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
352
|
+
if (!id) throw new Error("id is required");
|
|
353
|
+
const key = resolveKey(context);
|
|
354
|
+
const todos = await store.get(key);
|
|
355
|
+
const index = todos.findIndex((t) => t.id === id);
|
|
356
|
+
if (index === -1) throw new Error(`Todo with id "${id}" not found`);
|
|
357
|
+
const [removed] = todos.splice(index, 1);
|
|
358
|
+
await store.set(key, todos);
|
|
359
|
+
return { removed, todos };
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
];
|
|
363
|
+
};
|
package/.turbo/turbo-lint.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @poncho-ai/harness@0.16.1 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
|
-
[32m✓[39m test/telemetry.test.ts [2m ([22m[2m3 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
9
|
-
[event] step:completed {"type":"step:completed","step":1,"duration":1}
|
|
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 19[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
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mselects discovered tools by requested patterns[22m[39m
|
|
17
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
18
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
19
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
20
|
-
|
|
21
|
-
[32m✓[39m test/agent-parser.test.ts [2m ([22m[2m10 tests[22m[2m)[22m[90m 24[2mms[22m[39m
|
|
22
|
-
[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
|
|
23
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":0,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
24
|
-
|
|
25
|
-
[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
|
|
26
|
-
[poncho][mcp] {"event":"auth.token_missing","server":"remote","tokenEnv":"MISSING_TOKEN_ENV"}
|
|
27
|
-
|
|
28
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mreturns actionable errors for 403 permission failures[22m[39m
|
|
29
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
30
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
31
|
-
|
|
32
|
-
[32m✓[39m test/mcp.test.ts [2m ([22m[2m6 tests[22m[2m)[22m[90m 81[2mms[22m[39m
|
|
33
|
-
[32m✓[39m test/memory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 56[2mms[22m[39m
|
|
34
|
-
[32m✓[39m test/state.test.ts [2m ([22m[2m5 tests[22m[2m)[22m[90m 237[2mms[22m[39m
|
|
35
|
-
[32m✓[39m test/model-factory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
36
|
-
[32m✓[39m test/agent-identity.test.ts [2m ([22m[2m2 tests[22m[2m)[22m[90m 43[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
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:beta","requestedPatterns":[]}
|
|
61
|
-
|
|
62
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
63
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
64
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:obsolete","requestedPatterns":[]}
|
|
65
|
-
|
|
66
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
67
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
68
|
-
|
|
69
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not refresh skills outside development mode[22m[39m
|
|
70
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
71
|
-
|
|
72
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
73
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
74
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:alpha","requestedPatterns":[]}
|
|
75
|
-
|
|
76
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
77
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
78
|
-
|
|
79
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mlists skill scripts through list_skill_scripts[22m[39m
|
|
80
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
81
|
-
|
|
82
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns JavaScript/TypeScript skill scripts through run_skill_script[22m[39m
|
|
83
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
84
|
-
|
|
85
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns AGENT-scope scripts from root scripts directory[22m[39m
|
|
86
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
87
|
-
|
|
88
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mblocks path traversal in run_skill_script[22m[39m
|
|
89
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
90
|
-
|
|
91
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrequires allowed-tools entries for non-standard script directories[22m[39m
|
|
92
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
93
|
-
|
|
94
|
-
[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
|
|
95
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
96
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
97
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
98
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-a","requestedPatterns":["remote/a"],"registeredCount":1,"activeSkills":["skill-a"]}
|
|
99
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":2,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
100
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-b","requestedPatterns":["remote/a","remote/b"],"registeredCount":2,"activeSkills":["skill-a","skill-b"]}
|
|
101
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
102
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"deactivate:skill-a","requestedPatterns":["remote/b"],"registeredCount":1,"activeSkills":["skill-b"]}
|
|
103
|
-
|
|
104
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports flat tool access config format[22m[39m
|
|
105
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
106
|
-
|
|
107
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mflat tool access takes priority over legacy defaults[22m[39m
|
|
108
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
109
|
-
|
|
110
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mbyEnvironment overrides flat tool access[22m[39m
|
|
111
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
112
|
-
|
|
113
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisterTools skips tools disabled via config[22m[39m
|
|
114
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
115
|
-
|
|
116
|
-
[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
|
|
117
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
118
|
-
|
|
119
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mtools without approval config do not require approval[22m[39m
|
|
120
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
121
|
-
|
|
122
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows in-flight MCP calls to finish after skill deactivation[22m[39m
|
|
123
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
124
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
125
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
126
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-slow","requestedPatterns":["remote/slow"],"registeredCount":1,"activeSkills":["skill-slow"]}
|
|
127
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"deactivate:skill-slow","requestedPatterns":[]}
|
|
128
|
-
|
|
129
|
-
[32m✓[39m test/harness.test.ts [2m ([22m[2m25 tests[22m[2m)[22m[90m 291[2mms[22m[39m
|
|
130
|
-
|
|
131
|
-
[2m Test Files [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
132
|
-
[2m Tests [22m [1m[32m86 passed[39m[22m[90m (86)[39m
|
|
133
|
-
[2m Start at [22m 17:47:43
|
|
134
|
-
[2m Duration [22m 1.88s[2m (transform 684ms, setup 1ms, collect 2.34s, tests 755ms, environment 2ms, prepare 1.27s)[22m
|
|
135
|
-
|