@poncho-ai/harness 0.37.2 → 0.39.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 +216 -0
- package/dist/index.d.ts +397 -34
- package/dist/index.js +2228 -130
- package/package.json +2 -2
- package/src/harness.ts +48 -72
- package/src/index.ts +1 -0
- package/src/local-tools.ts +4 -3
- package/src/mcp.ts +9 -6
- package/src/orchestrator/continuation.ts +13 -0
- package/src/orchestrator/history.ts +69 -0
- package/src/orchestrator/index.ts +54 -0
- package/src/orchestrator/orchestrator.ts +1535 -0
- package/src/orchestrator/subagents.ts +27 -0
- package/src/orchestrator/turn.ts +367 -0
- package/src/reminder-store.ts +183 -16
- package/src/reminder-tools.ts +102 -6
- package/src/state.ts +127 -3
- package/src/storage/engine.ts +29 -10
- package/src/storage/memory-engine.ts +74 -10
- package/src/storage/postgres-engine.ts +1 -0
- package/src/storage/schema.ts +21 -0
- package/src/storage/sql-dialect.ts +294 -23
- package/src/storage/sqlite-engine.ts +1 -0
- package/src/storage/store-adapters.ts +17 -11
- package/src/telemetry.ts +10 -7
- package/src/upload-store.ts +7 -4
- package/src/vfs/bash-manager.ts +16 -2
- package/src/vfs/edit-file-tool.ts +9 -8
- package/src/vfs/read-file-tool.ts +14 -15
- package/src/vfs/write-file-tool.ts +6 -5
- package/test/orchestrator.test.ts +176 -0
- package/test/reminder-store.test.ts +193 -4
- package/test/state.test.ts +21 -0
- package/test/storage-engine.test.ts +80 -0
package/dist/index.js
CHANGED
|
@@ -2300,7 +2300,7 @@ var ponchoDocsTool = defineTool({
|
|
|
2300
2300
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
2301
2301
|
import { readFile as readFile8 } from "fs/promises";
|
|
2302
2302
|
import { resolve as resolve11 } from "path";
|
|
2303
|
-
import { defineTool as defineTool12, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
2303
|
+
import { defineTool as defineTool12, getTextContent as getTextContent2, createLogger as createLogger5, formatError as fmtErr, url as urlColor } from "@poncho-ai/sdk";
|
|
2304
2304
|
|
|
2305
2305
|
// src/upload-store.ts
|
|
2306
2306
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -2308,6 +2308,8 @@ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3, rm as
|
|
|
2308
2308
|
import { createRequire } from "module";
|
|
2309
2309
|
import { homedir as homedir2 } from "os";
|
|
2310
2310
|
import { resolve as resolve5 } from "path";
|
|
2311
|
+
import { createLogger } from "@poncho-ai/sdk";
|
|
2312
|
+
var uploadLog = createLogger("upload");
|
|
2311
2313
|
var tryImport = async (mod, workingDir) => {
|
|
2312
2314
|
try {
|
|
2313
2315
|
return await import(
|
|
@@ -2345,7 +2347,7 @@ var CachedUploadStore = class {
|
|
|
2345
2347
|
this.cache.set(key, { data, ts: now2 });
|
|
2346
2348
|
this.evict();
|
|
2347
2349
|
this.inner.put(key, data, mediaType).catch((err) => {
|
|
2348
|
-
|
|
2350
|
+
uploadLog.error(`background upload failed: ${err instanceof Error ? err.message : err}`);
|
|
2349
2351
|
});
|
|
2350
2352
|
return ref;
|
|
2351
2353
|
}
|
|
@@ -2559,7 +2561,7 @@ var S3UploadStore = class {
|
|
|
2559
2561
|
}
|
|
2560
2562
|
};
|
|
2561
2563
|
var warn = (msg) => {
|
|
2562
|
-
|
|
2564
|
+
uploadLog.warn(msg);
|
|
2563
2565
|
};
|
|
2564
2566
|
var createUploadStore = async (config, workingDir) => {
|
|
2565
2567
|
const provider = config?.provider ?? "local";
|
|
@@ -2573,7 +2575,7 @@ var createUploadStore = async (config, workingDir) => {
|
|
|
2573
2575
|
const store = new VercelBlobUploadStore(workingDir, config?.access ?? "public");
|
|
2574
2576
|
try {
|
|
2575
2577
|
await store.loadSdk();
|
|
2576
|
-
|
|
2578
|
+
uploadLog.item("using vercel-blob store");
|
|
2577
2579
|
return new CachedUploadStore(store);
|
|
2578
2580
|
} catch {
|
|
2579
2581
|
warn(
|
|
@@ -2599,7 +2601,7 @@ var createUploadStore = async (config, workingDir) => {
|
|
|
2599
2601
|
);
|
|
2600
2602
|
try {
|
|
2601
2603
|
await store.ensureClient();
|
|
2602
|
-
|
|
2604
|
+
uploadLog.item(`using s3 store (bucket: ${bucket})`);
|
|
2603
2605
|
return new CachedUploadStore(store);
|
|
2604
2606
|
} catch {
|
|
2605
2607
|
warn(
|
|
@@ -2663,16 +2665,41 @@ var InMemoryEngine = class {
|
|
|
2663
2665
|
get: async (conversationId) => {
|
|
2664
2666
|
return this.convs.get(conversationId);
|
|
2665
2667
|
},
|
|
2666
|
-
|
|
2668
|
+
// In-memory storage has no separate archive blob, so both variants
|
|
2669
|
+
// return the same conversation object.
|
|
2670
|
+
getWithArchive: async (conversationId) => {
|
|
2671
|
+
return this.convs.get(conversationId);
|
|
2672
|
+
},
|
|
2673
|
+
getStatusSnapshot: async (conversationId) => {
|
|
2674
|
+
const c = this.convs.get(conversationId);
|
|
2675
|
+
if (!c) return void 0;
|
|
2676
|
+
return {
|
|
2677
|
+
conversationId: c.conversationId,
|
|
2678
|
+
updatedAt: c.updatedAt,
|
|
2679
|
+
messageCount: c.messages.length,
|
|
2680
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
2681
|
+
hasContinuationMessages: Array.isArray(c._continuationMessages) && c._continuationMessages.length > 0,
|
|
2682
|
+
parentConversationId: c.parentConversationId ?? null,
|
|
2683
|
+
ownerId: c.ownerId,
|
|
2684
|
+
tenantId: c.tenantId,
|
|
2685
|
+
runStatus: c.runStatus ?? null
|
|
2686
|
+
};
|
|
2687
|
+
},
|
|
2688
|
+
create: async (ownerId, title, tenantId, init) => {
|
|
2667
2689
|
const now2 = Date.now();
|
|
2668
2690
|
const conv = {
|
|
2669
2691
|
conversationId: randomUUID3(),
|
|
2670
2692
|
title: normalizeTitle(title),
|
|
2671
|
-
messages: [],
|
|
2693
|
+
messages: init?.messages ?? [],
|
|
2672
2694
|
ownerId: ownerId ?? DEFAULT_OWNER,
|
|
2673
2695
|
tenantId: tenantId === void 0 ? null : tenantId,
|
|
2674
2696
|
createdAt: now2,
|
|
2675
|
-
updatedAt: now2
|
|
2697
|
+
updatedAt: now2,
|
|
2698
|
+
...init?.parentConversationId !== void 0 ? { parentConversationId: init.parentConversationId } : {},
|
|
2699
|
+
...init?.parentMessageId !== void 0 ? { parentMessageId: init.parentMessageId } : {},
|
|
2700
|
+
...init?.threadMeta !== void 0 ? { threadMeta: init.threadMeta } : {},
|
|
2701
|
+
...init?.subagentMeta !== void 0 ? { subagentMeta: init.subagentMeta } : {},
|
|
2702
|
+
...init?.channelMeta !== void 0 ? { channelMeta: init.channelMeta } : {}
|
|
2676
2703
|
};
|
|
2677
2704
|
this.convs.set(conv.conversationId, conv);
|
|
2678
2705
|
return conv;
|
|
@@ -2720,6 +2747,16 @@ var InMemoryEngine = class {
|
|
|
2720
2747
|
if (!conv) return void 0;
|
|
2721
2748
|
conv.runningCallbackSince = void 0;
|
|
2722
2749
|
return conv;
|
|
2750
|
+
},
|
|
2751
|
+
listThreads: async (parentConversationId) => {
|
|
2752
|
+
const results = [];
|
|
2753
|
+
for (const c of this.convs.values()) {
|
|
2754
|
+
if (c.parentConversationId === parentConversationId && typeof c.parentMessageId === "string") {
|
|
2755
|
+
results.push(this.toSummary(c));
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
results.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
2759
|
+
return results;
|
|
2723
2760
|
}
|
|
2724
2761
|
};
|
|
2725
2762
|
// -----------------------------------------------------------------------
|
|
@@ -2776,11 +2813,21 @@ var InMemoryEngine = class {
|
|
|
2776
2813
|
createdAt: Date.now(),
|
|
2777
2814
|
conversationId: input.conversationId,
|
|
2778
2815
|
ownerId: input.ownerId,
|
|
2779
|
-
tenantId: input.tenantId
|
|
2816
|
+
tenantId: input.tenantId,
|
|
2817
|
+
recurrence: input.recurrence ?? null,
|
|
2818
|
+
occurrenceCount: 0
|
|
2780
2819
|
};
|
|
2781
2820
|
this.reminderData.set(r.id, r);
|
|
2782
2821
|
return r;
|
|
2783
2822
|
},
|
|
2823
|
+
update: async (id, fields) => {
|
|
2824
|
+
const r = this.reminderData.get(id);
|
|
2825
|
+
if (!r) throw new Error(`Reminder ${id} not found`);
|
|
2826
|
+
if (fields.scheduledAt !== void 0) r.scheduledAt = fields.scheduledAt;
|
|
2827
|
+
if (fields.occurrenceCount !== void 0) r.occurrenceCount = fields.occurrenceCount;
|
|
2828
|
+
if (fields.status !== void 0) r.status = fields.status;
|
|
2829
|
+
return r;
|
|
2830
|
+
},
|
|
2784
2831
|
cancel: async (id) => {
|
|
2785
2832
|
const r = this.reminderData.get(id);
|
|
2786
2833
|
if (!r) throw new Error(`Reminder ${id} not found`);
|
|
@@ -3013,6 +3060,7 @@ var InMemoryEngine = class {
|
|
|
3013
3060
|
messageCount: c.messages.length,
|
|
3014
3061
|
hasPendingApprovals: (c.pendingApprovals?.length ?? 0) > 0,
|
|
3015
3062
|
parentConversationId: c.parentConversationId,
|
|
3063
|
+
parentMessageId: c.parentMessageId,
|
|
3016
3064
|
channelMeta: c.channelMeta
|
|
3017
3065
|
};
|
|
3018
3066
|
}
|
|
@@ -3057,6 +3105,7 @@ import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
|
3057
3105
|
|
|
3058
3106
|
// src/storage/sql-dialect.ts
|
|
3059
3107
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3108
|
+
import { createLogger as createLogger2 } from "@poncho-ai/sdk";
|
|
3060
3109
|
|
|
3061
3110
|
// src/storage/schema.ts
|
|
3062
3111
|
var migrations = [
|
|
@@ -3207,10 +3256,32 @@ var migrations = [
|
|
|
3207
3256
|
]
|
|
3208
3257
|
];
|
|
3209
3258
|
}
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
version: 5,
|
|
3262
|
+
name: "add_reminder_recurrence",
|
|
3263
|
+
up: (d) => {
|
|
3264
|
+
const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
|
|
3265
|
+
return [
|
|
3266
|
+
`ALTER TABLE reminders ADD COLUMN recurrence ${jsonType}`,
|
|
3267
|
+
`ALTER TABLE reminders ADD COLUMN occurrence_count INTEGER NOT NULL DEFAULT 0`
|
|
3268
|
+
];
|
|
3269
|
+
}
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
version: 6,
|
|
3273
|
+
name: "add_thread_anchor",
|
|
3274
|
+
up: () => [
|
|
3275
|
+
`ALTER TABLE conversations ADD COLUMN parent_message_id TEXT`,
|
|
3276
|
+
`CREATE INDEX IF NOT EXISTS idx_conversations_thread_anchor
|
|
3277
|
+
ON conversations (parent_conversation_id, parent_message_id)
|
|
3278
|
+
WHERE parent_message_id IS NOT NULL`
|
|
3279
|
+
]
|
|
3210
3280
|
}
|
|
3211
3281
|
];
|
|
3212
3282
|
|
|
3213
3283
|
// src/storage/sql-dialect.ts
|
|
3284
|
+
var egressLog = createLogger2("egress");
|
|
3214
3285
|
var sqliteDialect = {
|
|
3215
3286
|
tag: "sqlite",
|
|
3216
3287
|
param: () => "?",
|
|
@@ -3241,9 +3312,68 @@ var rewrite = (sql, dialect) => {
|
|
|
3241
3312
|
}
|
|
3242
3313
|
return sql;
|
|
3243
3314
|
};
|
|
3315
|
+
var ConversationEgressMeter = class {
|
|
3316
|
+
read = {};
|
|
3317
|
+
write = {};
|
|
3318
|
+
lastLogAt = Date.now();
|
|
3319
|
+
logIntervalMs;
|
|
3320
|
+
enabled;
|
|
3321
|
+
constructor(logIntervalMs = 6e4) {
|
|
3322
|
+
this.logIntervalMs = logIntervalMs;
|
|
3323
|
+
this.enabled = process.env.PONCHO_LOG_EGRESS === "1";
|
|
3324
|
+
}
|
|
3325
|
+
trackRead(method, bytes) {
|
|
3326
|
+
const b = this.read[method] ??= { calls: 0, bytes: 0 };
|
|
3327
|
+
b.calls += 1;
|
|
3328
|
+
b.bytes += bytes;
|
|
3329
|
+
this.maybeLog();
|
|
3330
|
+
}
|
|
3331
|
+
trackWrite(method, bytes) {
|
|
3332
|
+
const b = this.write[method] ??= { calls: 0, bytes: 0 };
|
|
3333
|
+
b.calls += 1;
|
|
3334
|
+
b.bytes += bytes;
|
|
3335
|
+
this.maybeLog();
|
|
3336
|
+
}
|
|
3337
|
+
maybeLog() {
|
|
3338
|
+
if (!this.enabled) return;
|
|
3339
|
+
const now2 = Date.now();
|
|
3340
|
+
if (now2 - this.lastLogAt < this.logIntervalMs) return;
|
|
3341
|
+
this.lastLogAt = now2;
|
|
3342
|
+
this.flush();
|
|
3343
|
+
}
|
|
3344
|
+
flush() {
|
|
3345
|
+
if (!this.enabled) return;
|
|
3346
|
+
const fmt = (buckets) => Object.entries(buckets).filter(([, b]) => b.calls > 0).map(([m, b]) => `${m}=${b.calls}calls/${fmtBytes(b.bytes)}`).join(", ");
|
|
3347
|
+
const r = fmt(this.read);
|
|
3348
|
+
const w = fmt(this.write);
|
|
3349
|
+
if (r || w) {
|
|
3350
|
+
egressLog.debug(`read: ${r || "(none)"} | write: ${w || "(none)"}`);
|
|
3351
|
+
}
|
|
3352
|
+
for (const b of Object.values(this.read)) {
|
|
3353
|
+
b.calls = 0;
|
|
3354
|
+
b.bytes = 0;
|
|
3355
|
+
}
|
|
3356
|
+
for (const b of Object.values(this.write)) {
|
|
3357
|
+
b.calls = 0;
|
|
3358
|
+
b.bytes = 0;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
var fmtBytes = (n) => {
|
|
3363
|
+
if (n < 1024) return `${n}B`;
|
|
3364
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
3365
|
+
return `${(n / (1024 * 1024)).toFixed(2)}MB`;
|
|
3366
|
+
};
|
|
3367
|
+
var colBytes = (v) => {
|
|
3368
|
+
if (v == null) return 0;
|
|
3369
|
+
if (typeof v === "string") return v.length;
|
|
3370
|
+
if (Buffer.isBuffer(v)) return v.byteLength;
|
|
3371
|
+
return JSON.stringify(v).length;
|
|
3372
|
+
};
|
|
3244
3373
|
var SqlStorageEngine = class {
|
|
3245
3374
|
dialect;
|
|
3246
3375
|
agentId;
|
|
3376
|
+
egressMeter = new ConversationEgressMeter();
|
|
3247
3377
|
constructor(dialect, agentId) {
|
|
3248
3378
|
this.dialect = dialect;
|
|
3249
3379
|
this.agentId = agentId;
|
|
@@ -3255,6 +3385,10 @@ var SqlStorageEngine = class {
|
|
|
3255
3385
|
await this.onBeforeInit();
|
|
3256
3386
|
await this.runMigrations();
|
|
3257
3387
|
}
|
|
3388
|
+
/** Flush egress stats before shutdown. Subclasses should call super. */
|
|
3389
|
+
flushEgressStats() {
|
|
3390
|
+
this.egressMeter.flush();
|
|
3391
|
+
}
|
|
3258
3392
|
/** Hook for subclass-specific setup (e.g. WAL mode). */
|
|
3259
3393
|
async onBeforeInit() {
|
|
3260
3394
|
}
|
|
@@ -3305,7 +3439,7 @@ var SqlStorageEngine = class {
|
|
|
3305
3439
|
const filterTenant = tenantId !== void 0;
|
|
3306
3440
|
const params = [this.agentId];
|
|
3307
3441
|
let sql = `SELECT id, title, updated_at, created_at, owner_id, tenant_id,
|
|
3308
|
-
message_count, parent_conversation_id,
|
|
3442
|
+
message_count, parent_conversation_id, parent_message_id,
|
|
3309
3443
|
has_pending_approvals, channel_meta
|
|
3310
3444
|
FROM conversations WHERE agent_id = $1`;
|
|
3311
3445
|
if (filterTenant) {
|
|
@@ -3321,11 +3455,62 @@ var SqlStorageEngine = class {
|
|
|
3321
3455
|
return rows.map((r) => this.rowToSummary(r));
|
|
3322
3456
|
},
|
|
3323
3457
|
get: async (conversationId) => {
|
|
3458
|
+
const row = await this.executor.get(
|
|
3459
|
+
rewrite("SELECT data, harness_messages, continuation_messages FROM conversations WHERE id = $1 AND agent_id = $2", this.dialect),
|
|
3460
|
+
[conversationId, this.agentId]
|
|
3461
|
+
);
|
|
3462
|
+
if (!row) return void 0;
|
|
3463
|
+
this.egressMeter.trackRead(
|
|
3464
|
+
"get",
|
|
3465
|
+
colBytes(row.data) + colBytes(row.harness_messages) + colBytes(row.continuation_messages)
|
|
3466
|
+
);
|
|
3467
|
+
const conv = this.parseConversation(row.data);
|
|
3468
|
+
if (row.harness_messages) {
|
|
3469
|
+
conv._harnessMessages = typeof row.harness_messages === "string" ? JSON.parse(row.harness_messages) : row.harness_messages;
|
|
3470
|
+
}
|
|
3471
|
+
if (row.continuation_messages) {
|
|
3472
|
+
conv._continuationMessages = typeof row.continuation_messages === "string" ? JSON.parse(row.continuation_messages) : row.continuation_messages;
|
|
3473
|
+
}
|
|
3474
|
+
return conv;
|
|
3475
|
+
},
|
|
3476
|
+
getStatusSnapshot: async (conversationId) => {
|
|
3477
|
+
const runStatusExpr = this.dialect.tag === "sqlite" ? "json_extract(data, '$.runStatus')" : "data->>'runStatus'";
|
|
3478
|
+
const row = await this.executor.get(
|
|
3479
|
+
rewrite(
|
|
3480
|
+
`SELECT updated_at, message_count, has_pending_approvals,
|
|
3481
|
+
parent_conversation_id, owner_id, tenant_id,
|
|
3482
|
+
(continuation_messages IS NOT NULL) AS has_continuation_messages,
|
|
3483
|
+
${runStatusExpr} AS run_status
|
|
3484
|
+
FROM conversations WHERE id = $1 AND agent_id = $2`,
|
|
3485
|
+
this.dialect
|
|
3486
|
+
),
|
|
3487
|
+
[conversationId, this.agentId]
|
|
3488
|
+
);
|
|
3489
|
+
if (!row) return void 0;
|
|
3490
|
+
this.egressMeter.trackRead("getStatusSnapshot", 200);
|
|
3491
|
+
const runStatus = row.run_status === "running" || row.run_status === "idle" ? row.run_status : null;
|
|
3492
|
+
return {
|
|
3493
|
+
conversationId,
|
|
3494
|
+
updatedAt: new Date(row.updated_at).getTime(),
|
|
3495
|
+
messageCount: row.message_count ?? 0,
|
|
3496
|
+
hasPendingApprovals: !!row.has_pending_approvals,
|
|
3497
|
+
hasContinuationMessages: !!row.has_continuation_messages,
|
|
3498
|
+
parentConversationId: row.parent_conversation_id,
|
|
3499
|
+
ownerId: row.owner_id,
|
|
3500
|
+
tenantId: row.tenant_id === "__default__" ? null : row.tenant_id,
|
|
3501
|
+
runStatus
|
|
3502
|
+
};
|
|
3503
|
+
},
|
|
3504
|
+
getWithArchive: async (conversationId) => {
|
|
3324
3505
|
const row = await this.executor.get(
|
|
3325
3506
|
rewrite("SELECT data, tool_result_archive, harness_messages, continuation_messages FROM conversations WHERE id = $1 AND agent_id = $2", this.dialect),
|
|
3326
3507
|
[conversationId, this.agentId]
|
|
3327
3508
|
);
|
|
3328
3509
|
if (!row) return void 0;
|
|
3510
|
+
this.egressMeter.trackRead(
|
|
3511
|
+
"getWithArchive",
|
|
3512
|
+
colBytes(row.data) + colBytes(row.tool_result_archive) + colBytes(row.harness_messages) + colBytes(row.continuation_messages)
|
|
3513
|
+
);
|
|
3329
3514
|
const conv = this.parseConversation(row.data);
|
|
3330
3515
|
if (row.tool_result_archive) {
|
|
3331
3516
|
conv._toolResultArchive = typeof row.tool_result_archive === "string" ? JSON.parse(row.tool_result_archive) : row.tool_result_archive;
|
|
@@ -3338,24 +3523,30 @@ var SqlStorageEngine = class {
|
|
|
3338
3523
|
}
|
|
3339
3524
|
return conv;
|
|
3340
3525
|
},
|
|
3341
|
-
create: async (ownerId, title, tenantId) => {
|
|
3526
|
+
create: async (ownerId, title, tenantId, init) => {
|
|
3342
3527
|
const id = randomUUID4();
|
|
3343
3528
|
const now2 = Date.now();
|
|
3344
3529
|
const conv = {
|
|
3345
3530
|
conversationId: id,
|
|
3346
3531
|
title: normalizeTitle2(title),
|
|
3347
|
-
messages: [],
|
|
3532
|
+
messages: init?.messages ?? [],
|
|
3348
3533
|
ownerId: ownerId ?? DEFAULT_OWNER2,
|
|
3349
3534
|
tenantId: tenantId === void 0 ? null : tenantId,
|
|
3350
3535
|
createdAt: now2,
|
|
3351
|
-
updatedAt: now2
|
|
3536
|
+
updatedAt: now2,
|
|
3537
|
+
...init?.parentConversationId !== void 0 ? { parentConversationId: init.parentConversationId } : {},
|
|
3538
|
+
...init?.parentMessageId !== void 0 ? { parentMessageId: init.parentMessageId } : {},
|
|
3539
|
+
...init?.subagentMeta !== void 0 ? { subagentMeta: init.subagentMeta } : {},
|
|
3540
|
+
...init?.threadMeta !== void 0 ? { threadMeta: init.threadMeta } : {},
|
|
3541
|
+
...init?.channelMeta !== void 0 ? { channelMeta: init.channelMeta } : {}
|
|
3352
3542
|
};
|
|
3353
3543
|
const data = JSON.stringify(conv);
|
|
3544
|
+
const channelMetaJson = conv.channelMeta ? JSON.stringify(conv.channelMeta) : null;
|
|
3354
3545
|
await this.executor.run(
|
|
3355
3546
|
rewrite(
|
|
3356
3547
|
`INSERT INTO conversations (id, agent_id, tenant_id, owner_id, title, data, message_count, created_at, updated_at,
|
|
3357
|
-
parent_conversation_id, has_pending_approvals, channel_meta)
|
|
3358
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
|
3548
|
+
parent_conversation_id, parent_message_id, has_pending_approvals, channel_meta)
|
|
3549
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
|
3359
3550
|
this.dialect
|
|
3360
3551
|
),
|
|
3361
3552
|
[
|
|
@@ -3365,12 +3556,13 @@ var SqlStorageEngine = class {
|
|
|
3365
3556
|
conv.ownerId,
|
|
3366
3557
|
conv.title,
|
|
3367
3558
|
data,
|
|
3368
|
-
|
|
3559
|
+
conv.messages.length,
|
|
3369
3560
|
new Date(now2).toISOString(),
|
|
3370
3561
|
new Date(now2).toISOString(),
|
|
3371
|
-
null,
|
|
3562
|
+
conv.parentConversationId ?? null,
|
|
3563
|
+
conv.parentMessageId ?? null,
|
|
3372
3564
|
0,
|
|
3373
|
-
|
|
3565
|
+
channelMetaJson
|
|
3374
3566
|
]
|
|
3375
3567
|
);
|
|
3376
3568
|
return conv;
|
|
@@ -3389,25 +3581,36 @@ var SqlStorageEngine = class {
|
|
|
3389
3581
|
const archiveJson = archive ? JSON.stringify(archive) : null;
|
|
3390
3582
|
const harnessJson = harnessMessages ? JSON.stringify(harnessMessages) : null;
|
|
3391
3583
|
const continuationJson = continuationMessages ? JSON.stringify(continuationMessages) : null;
|
|
3584
|
+
this.egressMeter.trackWrite(
|
|
3585
|
+
"update",
|
|
3586
|
+
data.length + (archiveJson?.length ?? 0) + (harnessJson?.length ?? 0) + (continuationJson?.length ?? 0)
|
|
3587
|
+
);
|
|
3392
3588
|
const msgCount = conversation.messages?.length ?? 0;
|
|
3393
3589
|
const tid = normalizeTenant2(conversation.tenantId);
|
|
3394
3590
|
const now2 = new Date(conversation.updatedAt).toISOString();
|
|
3395
3591
|
const created = new Date(conversation.createdAt).toISOString();
|
|
3396
3592
|
const parentConvId = conversation.parentConversationId ?? null;
|
|
3593
|
+
const parentMsgId = conversation.parentMessageId ?? null;
|
|
3397
3594
|
const hasPendingApprovals = (conversation.pendingApprovals?.length ?? 0) > 0 ? 1 : 0;
|
|
3398
3595
|
const channelMetaJson = conversation.channelMeta ? JSON.stringify(conversation.channelMeta) : null;
|
|
3399
3596
|
await this.executor.run(
|
|
3400
3597
|
rewrite(
|
|
3401
3598
|
`INSERT INTO conversations (id, agent_id, tenant_id, owner_id, title, data, message_count, created_at, updated_at,
|
|
3402
3599
|
tool_result_archive, harness_messages, continuation_messages,
|
|
3403
|
-
parent_conversation_id, has_pending_approvals, channel_meta)
|
|
3404
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
|
3600
|
+
parent_conversation_id, parent_message_id, has_pending_approvals, channel_meta)
|
|
3601
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
|
3405
3602
|
${this.dialect.upsert(["id"])}
|
|
3406
3603
|
data = excluded.data, title = excluded.title, message_count = excluded.message_count,
|
|
3407
3604
|
updated_at = excluded.updated_at, tenant_id = excluded.tenant_id, owner_id = excluded.owner_id,
|
|
3408
|
-
tool_result_archive
|
|
3605
|
+
-- tool_result_archive is append-only from the caller's side; if the
|
|
3606
|
+
-- in-memory conversation was loaded via the light get() variant it
|
|
3607
|
+
-- won't have _toolResultArchive attached, so we must preserve the
|
|
3608
|
+
-- existing column instead of clobbering it with NULL.
|
|
3609
|
+
tool_result_archive = COALESCE(excluded.tool_result_archive, conversations.tool_result_archive),
|
|
3610
|
+
harness_messages = excluded.harness_messages,
|
|
3409
3611
|
continuation_messages = excluded.continuation_messages,
|
|
3410
3612
|
parent_conversation_id = excluded.parent_conversation_id,
|
|
3613
|
+
parent_message_id = excluded.parent_message_id,
|
|
3411
3614
|
has_pending_approvals = excluded.has_pending_approvals,
|
|
3412
3615
|
channel_meta = excluded.channel_meta`,
|
|
3413
3616
|
this.dialect
|
|
@@ -3426,6 +3629,7 @@ var SqlStorageEngine = class {
|
|
|
3426
3629
|
harnessJson,
|
|
3427
3630
|
continuationJson,
|
|
3428
3631
|
parentConvId,
|
|
3632
|
+
parentMsgId,
|
|
3429
3633
|
hasPendingApprovals,
|
|
3430
3634
|
channelMetaJson
|
|
3431
3635
|
]
|
|
@@ -3456,7 +3660,7 @@ var SqlStorageEngine = class {
|
|
|
3456
3660
|
const pattern = `%${query}%`;
|
|
3457
3661
|
const params = [this.agentId, pattern, pattern];
|
|
3458
3662
|
let sql = `SELECT id, title, updated_at, created_at, owner_id, tenant_id,
|
|
3459
|
-
message_count, parent_conversation_id,
|
|
3663
|
+
message_count, parent_conversation_id, parent_message_id,
|
|
3460
3664
|
has_pending_approvals, channel_meta
|
|
3461
3665
|
FROM conversations
|
|
3462
3666
|
WHERE agent_id = $1 AND (title LIKE $2 OR data LIKE $3)`;
|
|
@@ -3480,6 +3684,21 @@ var SqlStorageEngine = class {
|
|
|
3480
3684
|
conv.runningCallbackSince = void 0;
|
|
3481
3685
|
await this.conversations.update(conv);
|
|
3482
3686
|
return conv;
|
|
3687
|
+
},
|
|
3688
|
+
listThreads: async (parentConversationId) => {
|
|
3689
|
+
const sql = `SELECT id, title, updated_at, created_at, owner_id, tenant_id,
|
|
3690
|
+
message_count, parent_conversation_id, parent_message_id,
|
|
3691
|
+
has_pending_approvals, channel_meta
|
|
3692
|
+
FROM conversations
|
|
3693
|
+
WHERE agent_id = $1
|
|
3694
|
+
AND parent_conversation_id = $2
|
|
3695
|
+
AND parent_message_id IS NOT NULL
|
|
3696
|
+
ORDER BY updated_at DESC`;
|
|
3697
|
+
const rows = await this.executor.all(rewrite(sql, this.dialect), [
|
|
3698
|
+
this.agentId,
|
|
3699
|
+
parentConversationId
|
|
3700
|
+
]);
|
|
3701
|
+
return rows.map((r) => this.rowToSummary(r));
|
|
3483
3702
|
}
|
|
3484
3703
|
};
|
|
3485
3704
|
// -----------------------------------------------------------------------
|
|
@@ -3567,10 +3786,11 @@ var SqlStorageEngine = class {
|
|
|
3567
3786
|
const id = randomUUID4();
|
|
3568
3787
|
const now2 = Date.now();
|
|
3569
3788
|
const tid = normalizeTenant2(input.tenantId);
|
|
3789
|
+
const recurrenceJson = input.recurrence ? JSON.stringify(input.recurrence) : null;
|
|
3570
3790
|
await this.executor.run(
|
|
3571
3791
|
rewrite(
|
|
3572
|
-
`INSERT INTO reminders (id, agent_id, tenant_id, owner_id, conversation_id, task, status, scheduled_at, timezone, created_at)
|
|
3573
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
3792
|
+
`INSERT INTO reminders (id, agent_id, tenant_id, owner_id, conversation_id, task, status, scheduled_at, timezone, created_at, recurrence, occurrence_count)
|
|
3793
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
|
3574
3794
|
this.dialect
|
|
3575
3795
|
),
|
|
3576
3796
|
[
|
|
@@ -3583,7 +3803,9 @@ var SqlStorageEngine = class {
|
|
|
3583
3803
|
"pending",
|
|
3584
3804
|
input.scheduledAt,
|
|
3585
3805
|
input.timezone ?? null,
|
|
3586
|
-
new Date(now2).toISOString()
|
|
3806
|
+
new Date(now2).toISOString(),
|
|
3807
|
+
recurrenceJson,
|
|
3808
|
+
0
|
|
3587
3809
|
]
|
|
3588
3810
|
);
|
|
3589
3811
|
return {
|
|
@@ -3595,9 +3817,52 @@ var SqlStorageEngine = class {
|
|
|
3595
3817
|
createdAt: now2,
|
|
3596
3818
|
conversationId: input.conversationId,
|
|
3597
3819
|
ownerId: input.ownerId,
|
|
3598
|
-
tenantId: input.tenantId
|
|
3820
|
+
tenantId: input.tenantId,
|
|
3821
|
+
recurrence: input.recurrence ?? null,
|
|
3822
|
+
occurrenceCount: 0
|
|
3599
3823
|
};
|
|
3600
3824
|
},
|
|
3825
|
+
update: async (id, fields) => {
|
|
3826
|
+
const setClauses = [];
|
|
3827
|
+
const params = [];
|
|
3828
|
+
let idx = 1;
|
|
3829
|
+
if (fields.scheduledAt !== void 0) {
|
|
3830
|
+
setClauses.push(`scheduled_at = $${idx++}`);
|
|
3831
|
+
params.push(fields.scheduledAt);
|
|
3832
|
+
}
|
|
3833
|
+
if (fields.occurrenceCount !== void 0) {
|
|
3834
|
+
setClauses.push(`occurrence_count = $${idx++}`);
|
|
3835
|
+
params.push(fields.occurrenceCount);
|
|
3836
|
+
}
|
|
3837
|
+
if (fields.status !== void 0) {
|
|
3838
|
+
setClauses.push(`status = $${idx++}`);
|
|
3839
|
+
params.push(fields.status);
|
|
3840
|
+
}
|
|
3841
|
+
if (setClauses.length === 0) {
|
|
3842
|
+
const row2 = await this.executor.get(
|
|
3843
|
+
rewrite("SELECT * FROM reminders WHERE id = $1 AND agent_id = $2", this.dialect),
|
|
3844
|
+
[id, this.agentId]
|
|
3845
|
+
);
|
|
3846
|
+
if (!row2) throw new Error(`Reminder ${id} not found`);
|
|
3847
|
+
return this.rowToReminder(row2);
|
|
3848
|
+
}
|
|
3849
|
+
params.push(id, this.agentId);
|
|
3850
|
+
const idIdx = idx++;
|
|
3851
|
+
const agentIdx = idx++;
|
|
3852
|
+
await this.executor.run(
|
|
3853
|
+
rewrite(
|
|
3854
|
+
`UPDATE reminders SET ${setClauses.join(", ")} WHERE id = $${idIdx} AND agent_id = $${agentIdx}`,
|
|
3855
|
+
this.dialect
|
|
3856
|
+
),
|
|
3857
|
+
params
|
|
3858
|
+
);
|
|
3859
|
+
const row = await this.executor.get(
|
|
3860
|
+
rewrite("SELECT * FROM reminders WHERE id = $1 AND agent_id = $2", this.dialect),
|
|
3861
|
+
[id, this.agentId]
|
|
3862
|
+
);
|
|
3863
|
+
if (!row) throw new Error(`Reminder ${id} not found`);
|
|
3864
|
+
return this.rowToReminder(row);
|
|
3865
|
+
},
|
|
3601
3866
|
cancel: async (id) => {
|
|
3602
3867
|
await this.executor.run(
|
|
3603
3868
|
rewrite(
|
|
@@ -3899,11 +4164,20 @@ var SqlStorageEngine = class {
|
|
|
3899
4164
|
messageCount: row.message_count,
|
|
3900
4165
|
hasPendingApprovals: !!row.has_pending_approvals,
|
|
3901
4166
|
parentConversationId: row.parent_conversation_id || void 0,
|
|
4167
|
+
parentMessageId: row.parent_message_id || void 0,
|
|
3902
4168
|
channelMeta: rawChannelMeta ? typeof rawChannelMeta === "string" ? JSON.parse(rawChannelMeta) : rawChannelMeta : void 0
|
|
3903
4169
|
};
|
|
3904
4170
|
}
|
|
3905
4171
|
rowToReminder(row) {
|
|
3906
4172
|
const tid = row.tenant_id;
|
|
4173
|
+
let recurrence = null;
|
|
4174
|
+
if (row.recurrence) {
|
|
4175
|
+
try {
|
|
4176
|
+
recurrence = typeof row.recurrence === "string" ? JSON.parse(row.recurrence) : row.recurrence;
|
|
4177
|
+
} catch {
|
|
4178
|
+
recurrence = null;
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
3907
4181
|
return {
|
|
3908
4182
|
id: row.id,
|
|
3909
4183
|
task: row.task,
|
|
@@ -3913,7 +4187,9 @@ var SqlStorageEngine = class {
|
|
|
3913
4187
|
createdAt: new Date(row.created_at).getTime(),
|
|
3914
4188
|
conversationId: row.conversation_id,
|
|
3915
4189
|
ownerId: row.owner_id ?? void 0,
|
|
3916
|
-
tenantId: tid === DEFAULT_TENANT2 ? null : tid
|
|
4190
|
+
tenantId: tid === DEFAULT_TENANT2 ? null : tid,
|
|
4191
|
+
recurrence,
|
|
4192
|
+
occurrenceCount: row.occurrence_count ?? 0
|
|
3917
4193
|
};
|
|
3918
4194
|
}
|
|
3919
4195
|
toUint8Array(value) {
|
|
@@ -4019,6 +4295,7 @@ var SqliteEngine = class extends SqlStorageEngine {
|
|
|
4019
4295
|
};
|
|
4020
4296
|
}
|
|
4021
4297
|
async close() {
|
|
4298
|
+
this.flushEgressStats();
|
|
4022
4299
|
this.db?.close();
|
|
4023
4300
|
}
|
|
4024
4301
|
};
|
|
@@ -4075,6 +4352,7 @@ var PostgresEngine = class extends SqlStorageEngine {
|
|
|
4075
4352
|
this.patchVfs();
|
|
4076
4353
|
}
|
|
4077
4354
|
async close() {
|
|
4355
|
+
this.flushEgressStats();
|
|
4078
4356
|
await this.sql?.end();
|
|
4079
4357
|
}
|
|
4080
4358
|
/** Refresh the path cache for a tenant (call before bash.exec()). */
|
|
@@ -4170,12 +4448,15 @@ function createConversationStoreFromEngine(engine) {
|
|
|
4170
4448
|
}),
|
|
4171
4449
|
listSummaries: (ownerId, tenantId) => engine.conversations.list(ownerId, tenantId),
|
|
4172
4450
|
get: (conversationId) => engine.conversations.get(conversationId),
|
|
4173
|
-
|
|
4451
|
+
getWithArchive: (conversationId) => engine.conversations.getWithArchive(conversationId),
|
|
4452
|
+
getStatusSnapshot: (conversationId) => engine.conversations.getStatusSnapshot(conversationId),
|
|
4453
|
+
create: (ownerId, title, tenantId, init) => engine.conversations.create(ownerId, title, tenantId, init),
|
|
4174
4454
|
update: (conversation) => engine.conversations.update(conversation),
|
|
4175
4455
|
rename: (conversationId, title) => engine.conversations.rename(conversationId, title),
|
|
4176
4456
|
delete: (conversationId) => engine.conversations.delete(conversationId),
|
|
4177
4457
|
appendSubagentResult: (conversationId, result) => engine.conversations.appendSubagentResult(conversationId, result),
|
|
4178
|
-
clearCallbackLock: (conversationId) => engine.conversations.clearCallbackLock(conversationId)
|
|
4458
|
+
clearCallbackLock: (conversationId) => engine.conversations.clearCallbackLock(conversationId),
|
|
4459
|
+
listThreads: (parentConversationId) => engine.conversations.listThreads(parentConversationId)
|
|
4179
4460
|
};
|
|
4180
4461
|
}
|
|
4181
4462
|
function createMemoryStoreFromEngine(engine, tenantId) {
|
|
@@ -4194,6 +4475,7 @@ function createReminderStoreFromEngine(engine) {
|
|
|
4194
4475
|
return {
|
|
4195
4476
|
list: () => engine.reminders.list(),
|
|
4196
4477
|
create: (input) => engine.reminders.create(input),
|
|
4478
|
+
update: (id, fields) => engine.reminders.update(id, fields),
|
|
4197
4479
|
cancel: (id) => engine.reminders.cancel(id),
|
|
4198
4480
|
delete: (id) => engine.reminders.delete(id)
|
|
4199
4481
|
};
|
|
@@ -4591,13 +4873,23 @@ var BashEnvironmentManager = class {
|
|
|
4591
4873
|
this.bashOptions = toBashOptions(bashConfig, network);
|
|
4592
4874
|
}
|
|
4593
4875
|
environments = /* @__PURE__ */ new Map();
|
|
4876
|
+
filesystems = /* @__PURE__ */ new Map();
|
|
4594
4877
|
workingDir;
|
|
4595
4878
|
bashOptions;
|
|
4879
|
+
/** Return the combined IFileSystem (VFS + optional /project mount) for a tenant. */
|
|
4880
|
+
getFs(tenantId) {
|
|
4881
|
+
let fs = this.filesystems.get(tenantId);
|
|
4882
|
+
if (!fs) {
|
|
4883
|
+
const adapter = new PonchoFsAdapter(this.engine, tenantId, this.limits);
|
|
4884
|
+
fs = createBashFs(adapter, this.workingDir);
|
|
4885
|
+
this.filesystems.set(tenantId, fs);
|
|
4886
|
+
}
|
|
4887
|
+
return fs;
|
|
4888
|
+
}
|
|
4596
4889
|
getOrCreate(tenantId) {
|
|
4597
4890
|
let bash = this.environments.get(tenantId);
|
|
4598
4891
|
if (!bash) {
|
|
4599
|
-
const
|
|
4600
|
-
const fs = createBashFs(adapter, this.workingDir);
|
|
4892
|
+
const fs = this.getFs(tenantId);
|
|
4601
4893
|
bash = new Bash({
|
|
4602
4894
|
fs,
|
|
4603
4895
|
cwd: "/",
|
|
@@ -4621,9 +4913,11 @@ var BashEnvironmentManager = class {
|
|
|
4621
4913
|
}
|
|
4622
4914
|
destroy(tenantId) {
|
|
4623
4915
|
this.environments.delete(tenantId);
|
|
4916
|
+
this.filesystems.delete(tenantId);
|
|
4624
4917
|
}
|
|
4625
4918
|
destroyAll() {
|
|
4626
4919
|
this.environments.clear();
|
|
4920
|
+
this.filesystems.clear();
|
|
4627
4921
|
}
|
|
4628
4922
|
};
|
|
4629
4923
|
|
|
@@ -4701,7 +4995,7 @@ var mimeFromPath = (path) => {
|
|
|
4701
4995
|
return MIME_MAP2[path.slice(dot).toLowerCase()];
|
|
4702
4996
|
};
|
|
4703
4997
|
var isTextMime = (mime) => mime.startsWith("text/") || mime === "application/json" || mime === "application/xml" || mime === "application/sql" || mime === "application/javascript" || mime === "application/x-sh";
|
|
4704
|
-
var createReadFileTool = (
|
|
4998
|
+
var createReadFileTool = (getFs) => defineTool3({
|
|
4705
4999
|
name: "read_file",
|
|
4706
5000
|
description: "Read a file from the virtual filesystem. Returns text content for text-based files, or sends images and PDFs directly to the model for visual analysis.",
|
|
4707
5001
|
inputSchema: {
|
|
@@ -4721,23 +5015,24 @@ var createReadFileTool = (engine) => defineTool3({
|
|
|
4721
5015
|
throw new Error("path is required");
|
|
4722
5016
|
}
|
|
4723
5017
|
const tenantId = context.tenantId ?? "__default__";
|
|
4724
|
-
const
|
|
4725
|
-
if (!
|
|
5018
|
+
const fs = getFs(tenantId);
|
|
5019
|
+
if (!await fs.exists(filePath)) {
|
|
4726
5020
|
throw new Error(`File not found: ${filePath}`);
|
|
4727
5021
|
}
|
|
4728
|
-
|
|
5022
|
+
const stat3 = await fs.stat(filePath);
|
|
5023
|
+
if (stat3.isDirectory) {
|
|
4729
5024
|
throw new Error(`${filePath} is a directory, not a file`);
|
|
4730
5025
|
}
|
|
4731
|
-
const mediaType =
|
|
5026
|
+
const mediaType = mimeFromPath(filePath) ?? "application/octet-stream";
|
|
4732
5027
|
const filename = filePath.split("/").pop() ?? filePath;
|
|
4733
5028
|
if (isTextMime(mediaType)) {
|
|
4734
|
-
const
|
|
4735
|
-
const text = Buffer.from(buf).toString("utf8");
|
|
5029
|
+
const text = await fs.readFile(filePath);
|
|
4736
5030
|
return { filename, mediaType, content: text };
|
|
4737
5031
|
}
|
|
5032
|
+
const buf = await fs.readFileBuffer(filePath);
|
|
4738
5033
|
return {
|
|
4739
5034
|
type: "file",
|
|
4740
|
-
data:
|
|
5035
|
+
data: Buffer.from(buf).toString("base64"),
|
|
4741
5036
|
mediaType,
|
|
4742
5037
|
filename
|
|
4743
5038
|
};
|
|
@@ -4746,7 +5041,7 @@ var createReadFileTool = (engine) => defineTool3({
|
|
|
4746
5041
|
|
|
4747
5042
|
// src/vfs/edit-file-tool.ts
|
|
4748
5043
|
import { defineTool as defineTool4 } from "@poncho-ai/sdk";
|
|
4749
|
-
var createEditFileTool = (
|
|
5044
|
+
var createEditFileTool = (getFs) => defineTool4({
|
|
4750
5045
|
name: "edit_file",
|
|
4751
5046
|
description: "Edit a file by replacing an exact string match with new content. The old_str must match exactly one location in the file. Use an empty new_str to delete matched content. Use read_file first to see current content before editing.",
|
|
4752
5047
|
inputSchema: {
|
|
@@ -4775,11 +5070,11 @@ var createEditFileTool = (engine) => defineTool4({
|
|
|
4775
5070
|
if (!filePath) throw new Error("path is required");
|
|
4776
5071
|
if (!oldStr) throw new Error("old_str must not be empty");
|
|
4777
5072
|
const tenantId = context.tenantId ?? "__default__";
|
|
4778
|
-
const
|
|
4779
|
-
if (!
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
const content =
|
|
5073
|
+
const fs = getFs(tenantId);
|
|
5074
|
+
if (!await fs.exists(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
5075
|
+
const stat3 = await fs.stat(filePath);
|
|
5076
|
+
if (stat3.isDirectory) throw new Error(`${filePath} is a directory`);
|
|
5077
|
+
const content = await fs.readFile(filePath);
|
|
4783
5078
|
const first = content.indexOf(oldStr);
|
|
4784
5079
|
if (first === -1) {
|
|
4785
5080
|
throw new Error(
|
|
@@ -4793,14 +5088,14 @@ var createEditFileTool = (engine) => defineTool4({
|
|
|
4793
5088
|
);
|
|
4794
5089
|
}
|
|
4795
5090
|
const updated = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
4796
|
-
await
|
|
5091
|
+
await fs.writeFile(filePath, updated);
|
|
4797
5092
|
return { ok: true, path: filePath };
|
|
4798
5093
|
}
|
|
4799
5094
|
});
|
|
4800
5095
|
|
|
4801
5096
|
// src/vfs/write-file-tool.ts
|
|
4802
5097
|
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
4803
|
-
var createWriteFileTool = (
|
|
5098
|
+
var createWriteFileTool = (getFs) => defineTool5({
|
|
4804
5099
|
name: "write_file",
|
|
4805
5100
|
description: "Create a new file or overwrite an existing file in the virtual filesystem. Parent directories are created automatically. Prefer edit_file for targeted changes to existing files.",
|
|
4806
5101
|
inputSchema: {
|
|
@@ -4823,11 +5118,12 @@ var createWriteFileTool = (engine) => defineTool5({
|
|
|
4823
5118
|
const content = typeof input.content === "string" ? input.content : "";
|
|
4824
5119
|
if (!filePath) throw new Error("path is required");
|
|
4825
5120
|
const tenantId = context.tenantId ?? "__default__";
|
|
5121
|
+
const fs = getFs(tenantId);
|
|
4826
5122
|
const dir = filePath.slice(0, filePath.lastIndexOf("/"));
|
|
4827
5123
|
if (dir) {
|
|
4828
|
-
await
|
|
5124
|
+
await fs.mkdir(dir, { recursive: true });
|
|
4829
5125
|
}
|
|
4830
|
-
await
|
|
5126
|
+
await fs.writeFile(filePath, content);
|
|
4831
5127
|
return { ok: true, path: filePath };
|
|
4832
5128
|
}
|
|
4833
5129
|
});
|
|
@@ -5370,10 +5666,11 @@ async function resolveEnv(secretsStore, tenantId, envName) {
|
|
|
5370
5666
|
// src/reminder-tools.ts
|
|
5371
5667
|
import { defineTool as defineTool8 } from "@poncho-ai/sdk";
|
|
5372
5668
|
var VALID_STATUSES2 = ["pending", "cancelled"];
|
|
5669
|
+
var VALID_RECURRENCE_TYPES = ["daily", "weekly", "monthly", "cron"];
|
|
5373
5670
|
var createReminderTools = (store) => [
|
|
5374
5671
|
defineTool8({
|
|
5375
5672
|
name: "set_reminder",
|
|
5376
|
-
description: "Set a
|
|
5673
|
+
description: "Set a reminder that will fire at the specified date and time. Use this when the user asks to be reminded about something. The datetime must be an ISO 8601 string in the future. When the reminder fires, the task message will be delivered to the user. Supports optional recurrence for recurring reminders (daily, weekly, monthly, or cron).",
|
|
5377
5674
|
inputSchema: {
|
|
5378
5675
|
type: "object",
|
|
5379
5676
|
properties: {
|
|
@@ -5383,11 +5680,45 @@ var createReminderTools = (store) => [
|
|
|
5383
5680
|
},
|
|
5384
5681
|
datetime: {
|
|
5385
5682
|
type: "string",
|
|
5386
|
-
description: "ISO 8601 datetime for when the reminder should fire (e.g. '2026-03-23T09:00:00Z')"
|
|
5683
|
+
description: "ISO 8601 datetime for when the reminder should first fire (e.g. '2026-03-23T09:00:00Z')"
|
|
5387
5684
|
},
|
|
5388
5685
|
timezone: {
|
|
5389
5686
|
type: "string",
|
|
5390
5687
|
description: "IANA timezone for interpreting the datetime if it lacks an offset (e.g. 'America/New_York'). Defaults to UTC."
|
|
5688
|
+
},
|
|
5689
|
+
recurrence: {
|
|
5690
|
+
type: "object",
|
|
5691
|
+
description: "Optional. Set this to make the reminder repeat. Omit for a one-time reminder.",
|
|
5692
|
+
properties: {
|
|
5693
|
+
type: {
|
|
5694
|
+
type: "string",
|
|
5695
|
+
enum: VALID_RECURRENCE_TYPES,
|
|
5696
|
+
description: "How often to repeat: 'daily', 'weekly', 'monthly', or 'cron'."
|
|
5697
|
+
},
|
|
5698
|
+
interval: {
|
|
5699
|
+
type: "number",
|
|
5700
|
+
description: "Repeat every N units (e.g. 2 = every 2 days/weeks/months). Defaults to 1."
|
|
5701
|
+
},
|
|
5702
|
+
daysOfWeek: {
|
|
5703
|
+
type: "array",
|
|
5704
|
+
items: { type: "number" },
|
|
5705
|
+
description: "For weekly: which days to fire (0=Sunday, 1=Monday, ..., 6=Saturday)."
|
|
5706
|
+
},
|
|
5707
|
+
expression: {
|
|
5708
|
+
type: "string",
|
|
5709
|
+
description: "For type 'cron': a 5-field cron expression (e.g. '0 9 * * 1-5' for weekdays at 9am)."
|
|
5710
|
+
},
|
|
5711
|
+
endsAt: {
|
|
5712
|
+
type: "string",
|
|
5713
|
+
description: "ISO 8601 datetime after which the recurrence should stop."
|
|
5714
|
+
},
|
|
5715
|
+
maxOccurrences: {
|
|
5716
|
+
type: "number",
|
|
5717
|
+
description: "Maximum number of times the reminder should fire before stopping."
|
|
5718
|
+
}
|
|
5719
|
+
},
|
|
5720
|
+
required: ["type"],
|
|
5721
|
+
additionalProperties: false
|
|
5391
5722
|
}
|
|
5392
5723
|
},
|
|
5393
5724
|
required: ["task", "datetime"],
|
|
@@ -5434,13 +5765,56 @@ var createReminderTools = (store) => [
|
|
|
5434
5765
|
if (scheduledAt <= Date.now()) {
|
|
5435
5766
|
throw new Error("Reminder datetime must be in the future");
|
|
5436
5767
|
}
|
|
5768
|
+
let recurrence = null;
|
|
5769
|
+
if (input.recurrence && typeof input.recurrence === "object") {
|
|
5770
|
+
const rec = input.recurrence;
|
|
5771
|
+
const recType = rec.type;
|
|
5772
|
+
if (!VALID_RECURRENCE_TYPES.includes(recType)) {
|
|
5773
|
+
throw new Error(`Invalid recurrence type: "${recType}". Must be one of: ${VALID_RECURRENCE_TYPES.join(", ")}`);
|
|
5774
|
+
}
|
|
5775
|
+
recurrence = { type: recType };
|
|
5776
|
+
if (rec.interval !== void 0) {
|
|
5777
|
+
const interval = Number(rec.interval);
|
|
5778
|
+
if (!Number.isInteger(interval) || interval < 1) {
|
|
5779
|
+
throw new Error("recurrence.interval must be a positive integer");
|
|
5780
|
+
}
|
|
5781
|
+
recurrence.interval = interval;
|
|
5782
|
+
}
|
|
5783
|
+
if (rec.daysOfWeek !== void 0) {
|
|
5784
|
+
if (!Array.isArray(rec.daysOfWeek)) throw new Error("recurrence.daysOfWeek must be an array");
|
|
5785
|
+
const days = rec.daysOfWeek.map(Number);
|
|
5786
|
+
if (days.some((d) => !Number.isInteger(d) || d < 0 || d > 6)) {
|
|
5787
|
+
throw new Error("recurrence.daysOfWeek values must be integers 0-6");
|
|
5788
|
+
}
|
|
5789
|
+
recurrence.daysOfWeek = days;
|
|
5790
|
+
}
|
|
5791
|
+
if (rec.expression !== void 0) {
|
|
5792
|
+
if (typeof rec.expression !== "string") throw new Error("recurrence.expression must be a string");
|
|
5793
|
+
recurrence.expression = rec.expression;
|
|
5794
|
+
}
|
|
5795
|
+
if (rec.endsAt !== void 0) {
|
|
5796
|
+
const endsAtDate = new Date(rec.endsAt);
|
|
5797
|
+
if (isNaN(endsAtDate.getTime())) {
|
|
5798
|
+
throw new Error(`Invalid recurrence.endsAt: "${rec.endsAt}"`);
|
|
5799
|
+
}
|
|
5800
|
+
recurrence.endsAt = endsAtDate.getTime();
|
|
5801
|
+
}
|
|
5802
|
+
if (rec.maxOccurrences !== void 0) {
|
|
5803
|
+
const max = Number(rec.maxOccurrences);
|
|
5804
|
+
if (!Number.isInteger(max) || max < 1) {
|
|
5805
|
+
throw new Error("recurrence.maxOccurrences must be a positive integer");
|
|
5806
|
+
}
|
|
5807
|
+
recurrence.maxOccurrences = max;
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5437
5810
|
const conversationId = context.conversationId || context.runId;
|
|
5438
5811
|
const reminder = await store.create({
|
|
5439
5812
|
task,
|
|
5440
5813
|
scheduledAt,
|
|
5441
5814
|
timezone,
|
|
5442
5815
|
conversationId,
|
|
5443
|
-
tenantId: context.tenantId
|
|
5816
|
+
tenantId: context.tenantId,
|
|
5817
|
+
recurrence
|
|
5444
5818
|
});
|
|
5445
5819
|
return {
|
|
5446
5820
|
ok: true,
|
|
@@ -5449,14 +5823,16 @@ var createReminderTools = (store) => [
|
|
|
5449
5823
|
task: reminder.task,
|
|
5450
5824
|
scheduledAt: new Date(reminder.scheduledAt).toISOString(),
|
|
5451
5825
|
timezone: reminder.timezone ?? "UTC",
|
|
5452
|
-
status: reminder.status
|
|
5826
|
+
status: reminder.status,
|
|
5827
|
+
recurrence: reminder.recurrence ?? void 0,
|
|
5828
|
+
occurrenceCount: reminder.occurrenceCount ?? 0
|
|
5453
5829
|
}
|
|
5454
5830
|
};
|
|
5455
5831
|
}
|
|
5456
5832
|
}),
|
|
5457
5833
|
defineTool8({
|
|
5458
5834
|
name: "list_reminders",
|
|
5459
|
-
description: "List reminders for this agent. Returns all reminders by default; use the status filter to show only pending or cancelled ones. Fired reminders are automatically deleted after delivery.",
|
|
5835
|
+
description: "List reminders for this agent. Returns all reminders by default; use the status filter to show only pending or cancelled ones. Fired one-time reminders are automatically deleted after delivery. Recurring reminders stay active and show their recurrence config and fire count.",
|
|
5460
5836
|
inputSchema: {
|
|
5461
5837
|
type: "object",
|
|
5462
5838
|
properties: {
|
|
@@ -5484,7 +5860,9 @@ var createReminderTools = (store) => [
|
|
|
5484
5860
|
scheduledAt: new Date(r.scheduledAt).toISOString(),
|
|
5485
5861
|
timezone: r.timezone ?? "UTC",
|
|
5486
5862
|
status: r.status,
|
|
5487
|
-
createdAt: new Date(r.createdAt).toISOString()
|
|
5863
|
+
createdAt: new Date(r.createdAt).toISOString(),
|
|
5864
|
+
recurrence: r.recurrence ?? void 0,
|
|
5865
|
+
occurrenceCount: r.occurrenceCount ?? 0
|
|
5488
5866
|
})),
|
|
5489
5867
|
count: reminders.length
|
|
5490
5868
|
};
|
|
@@ -5492,7 +5870,7 @@ var createReminderTools = (store) => [
|
|
|
5492
5870
|
}),
|
|
5493
5871
|
defineTool8({
|
|
5494
5872
|
name: "cancel_reminder",
|
|
5495
|
-
description: "Cancel a pending reminder by its ID.",
|
|
5873
|
+
description: "Cancel a pending reminder by its ID. This works for both one-time and recurring reminders \u2014 cancelling a recurring reminder stops all future occurrences.",
|
|
5496
5874
|
inputSchema: {
|
|
5497
5875
|
type: "object",
|
|
5498
5876
|
properties: {
|
|
@@ -5529,6 +5907,8 @@ var createReminderTools = (store) => [
|
|
|
5529
5907
|
];
|
|
5530
5908
|
|
|
5531
5909
|
// src/mcp.ts
|
|
5910
|
+
import { createLogger as createLogger3 } from "@poncho-ai/sdk";
|
|
5911
|
+
var mcpLog = createLogger3("mcp");
|
|
5532
5912
|
var McpHttpError = class extends Error {
|
|
5533
5913
|
status;
|
|
5534
5914
|
constructor(status, message) {
|
|
@@ -5776,12 +6156,10 @@ var LocalMcpBridge = class {
|
|
|
5776
6156
|
return server.name ?? server.url;
|
|
5777
6157
|
}
|
|
5778
6158
|
log(level, event, payload) {
|
|
5779
|
-
const
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
}
|
|
5784
|
-
console.info(`[poncho][mcp] ${line}`);
|
|
6159
|
+
const summary = Object.entries(payload).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ");
|
|
6160
|
+
const msg = summary ? `${event} ${summary}` : event;
|
|
6161
|
+
if (level === "warn") mcpLog.warn(msg);
|
|
6162
|
+
else mcpLog.debug(msg);
|
|
5785
6163
|
}
|
|
5786
6164
|
/** Set of servers where discovery was deferred (no default token, has env resolver). */
|
|
5787
6165
|
deferredDiscoveryServers = /* @__PURE__ */ new Set();
|
|
@@ -7450,6 +7828,9 @@ import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace
|
|
|
7450
7828
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
7451
7829
|
|
|
7452
7830
|
// src/telemetry.ts
|
|
7831
|
+
import { createLogger as createLogger4 } from "@poncho-ai/sdk";
|
|
7832
|
+
var eventLog = createLogger4("event");
|
|
7833
|
+
var telemetryLog = createLogger4("telemetry");
|
|
7453
7834
|
var MAX_FIELD_LENGTH = 200;
|
|
7454
7835
|
var OMIT_FROM_LOG = /* @__PURE__ */ new Set(["continuationMessages", "_harnessMessages", "messages", "compactedHistory"]);
|
|
7455
7836
|
function sanitizeEventForLog(event) {
|
|
@@ -7490,11 +7871,10 @@ var TelemetryEmitter = class {
|
|
|
7490
7871
|
if (otlp) {
|
|
7491
7872
|
await this.sendOtlp(event, otlp);
|
|
7492
7873
|
}
|
|
7493
|
-
if (event.type === "model:chunk") {
|
|
7874
|
+
if (event.type === "model:chunk" || event.type === "browser:frame") {
|
|
7494
7875
|
return;
|
|
7495
7876
|
}
|
|
7496
|
-
|
|
7497
|
-
`);
|
|
7877
|
+
eventLog.debug(`${event.type} ${sanitizeEventForLog(event)}`);
|
|
7498
7878
|
}
|
|
7499
7879
|
async sendOtlp(event, otlp) {
|
|
7500
7880
|
try {
|
|
@@ -7526,8 +7906,8 @@ var TelemetryEmitter = class {
|
|
|
7526
7906
|
})
|
|
7527
7907
|
});
|
|
7528
7908
|
} catch (err) {
|
|
7529
|
-
|
|
7530
|
-
`
|
|
7909
|
+
telemetryLog.warn(
|
|
7910
|
+
`OTLP log delivery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7531
7911
|
);
|
|
7532
7912
|
}
|
|
7533
7913
|
}
|
|
@@ -7621,6 +8001,11 @@ var ToolDispatcher = class {
|
|
|
7621
8001
|
};
|
|
7622
8002
|
|
|
7623
8003
|
// src/harness.ts
|
|
8004
|
+
var harnessLog = createLogger5("harness");
|
|
8005
|
+
var telemetryLog2 = createLogger5("telemetry");
|
|
8006
|
+
var costLog = createLogger5("cost");
|
|
8007
|
+
var mcpLog2 = createLogger5("mcp");
|
|
8008
|
+
var modelLog = createLogger5("model");
|
|
7624
8009
|
function formatOtlpError(err) {
|
|
7625
8010
|
if (!(err instanceof Error)) return String(err);
|
|
7626
8011
|
const parts = [];
|
|
@@ -8228,8 +8613,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
8228
8613
|
const toolResultId = typeof input.toolResultId === "string" ? input.toolResultId : "";
|
|
8229
8614
|
const record = archive[toolResultId];
|
|
8230
8615
|
if (!record) {
|
|
8231
|
-
|
|
8232
|
-
`
|
|
8616
|
+
costLog.debug(
|
|
8617
|
+
`archived tool result miss: id="${toolResultId}" conv=${conversationId.slice(0, 8)}`
|
|
8233
8618
|
);
|
|
8234
8619
|
return {
|
|
8235
8620
|
error: `No archived tool result found for id "${toolResultId}" in this conversation.`
|
|
@@ -8239,8 +8624,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
8239
8624
|
const limit = Math.min(Math.max(Number(input.limit) || 6e3, 1), 2e4);
|
|
8240
8625
|
const end = Math.min(record.payload.length, offset + limit);
|
|
8241
8626
|
const chunk = record.payload.slice(offset, end);
|
|
8242
|
-
|
|
8243
|
-
`
|
|
8627
|
+
costLog.debug(
|
|
8628
|
+
`archived tool result hit: id="${toolResultId}" conv=${conversationId.slice(0, 8)} offset=${offset} returned=${chunk.length} total=${record.payload.length}`
|
|
8244
8629
|
);
|
|
8245
8630
|
return {
|
|
8246
8631
|
toolResultId: record.toolResultId,
|
|
@@ -8612,9 +8997,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8612
8997
|
this.dispatcher.unregisterMany(this.registeredMcpToolNames);
|
|
8613
8998
|
this.registeredMcpToolNames.clear();
|
|
8614
8999
|
if (requestedPatterns.length === 0) {
|
|
8615
|
-
|
|
8616
|
-
`[poncho][mcp] ${JSON.stringify({ event: "tools.cleared", reason, requestedPatterns })}`
|
|
8617
|
-
);
|
|
9000
|
+
mcpLog2.debug(`tools cleared (reason=${reason})`);
|
|
8618
9001
|
return;
|
|
8619
9002
|
}
|
|
8620
9003
|
const tools = await this.mcpBridge.loadTools(requestedPatterns);
|
|
@@ -8622,14 +9005,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
8622
9005
|
for (const tool of tools) {
|
|
8623
9006
|
this.registeredMcpToolNames.add(tool.name);
|
|
8624
9007
|
}
|
|
8625
|
-
|
|
8626
|
-
`
|
|
8627
|
-
event: "tools.refreshed",
|
|
8628
|
-
reason,
|
|
8629
|
-
requestedPatterns,
|
|
8630
|
-
registeredCount: tools.length,
|
|
8631
|
-
activeSkills: this.listActiveSkills()
|
|
8632
|
-
})}`
|
|
9008
|
+
mcpLog2.debug(
|
|
9009
|
+
`tools refreshed (reason=${reason}, registered=${tools.length}, patterns=${requestedPatterns.length})`
|
|
8633
9010
|
);
|
|
8634
9011
|
}
|
|
8635
9012
|
buildSkillFingerprint(skills) {
|
|
@@ -8692,9 +9069,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8692
9069
|
this.agentFileFingerprint = rawContent;
|
|
8693
9070
|
return true;
|
|
8694
9071
|
} catch (error) {
|
|
8695
|
-
|
|
8696
|
-
`[poncho][agent] Failed to refresh AGENT.md in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
8697
|
-
);
|
|
9072
|
+
createLogger5("agent").warn(`failed to refresh AGENT.md in dev: ${fmtErr(error)}`);
|
|
8698
9073
|
return false;
|
|
8699
9074
|
}
|
|
8700
9075
|
}
|
|
@@ -8742,9 +9117,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8742
9117
|
await this.refreshMcpTools("skills:changed");
|
|
8743
9118
|
return true;
|
|
8744
9119
|
} catch (error) {
|
|
8745
|
-
|
|
8746
|
-
`[poncho][skills] Failed to refresh skills in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
8747
|
-
);
|
|
9120
|
+
createLogger5("skills").warn(`failed to refresh skills in dev: ${fmtErr(error)}`);
|
|
8748
9121
|
return false;
|
|
8749
9122
|
}
|
|
8750
9123
|
}
|
|
@@ -8794,9 +9167,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
8794
9167
|
config?.network
|
|
8795
9168
|
);
|
|
8796
9169
|
this.registerIfMissing(createBashTool(this.bashManager));
|
|
8797
|
-
this.
|
|
8798
|
-
this.registerIfMissing(
|
|
8799
|
-
this.registerIfMissing(
|
|
9170
|
+
const getFs = (tenantId) => this.bashManager.getFs(tenantId);
|
|
9171
|
+
this.registerIfMissing(createReadFileTool(getFs));
|
|
9172
|
+
this.registerIfMissing(createEditFileTool(getFs));
|
|
9173
|
+
this.registerIfMissing(createWriteFileTool(getFs));
|
|
8800
9174
|
if (config?.isolate) {
|
|
8801
9175
|
const { createRunCodeTool, buildRunCodeDescription, bundleLibraries } = await import("./isolate-TCWTUVG4.js");
|
|
8802
9176
|
let libraryPreamble = null;
|
|
@@ -8838,9 +9212,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8838
9212
|
}
|
|
8839
9213
|
if (config?.browser) {
|
|
8840
9214
|
await this.initBrowserTools(config).catch((e) => {
|
|
8841
|
-
|
|
8842
|
-
`[poncho][browser] Failed to load browser tools: ${e instanceof Error ? e.message : String(e)}`
|
|
8843
|
-
);
|
|
9215
|
+
createLogger5("browser").warn(`failed to load browser tools: ${fmtErr(e)}`);
|
|
8844
9216
|
});
|
|
8845
9217
|
}
|
|
8846
9218
|
const stateConfig = resolveStateConfig(config);
|
|
@@ -8871,7 +9243,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8871
9243
|
provider2.register();
|
|
8872
9244
|
this.otlpTracerProvider = provider2;
|
|
8873
9245
|
this.hasOtlpExporter = true;
|
|
8874
|
-
|
|
9246
|
+
telemetryLog2.item(`OTLP trace exporter active \u2192 ${urlColor(otlpConfig.url)}`);
|
|
8875
9247
|
}
|
|
8876
9248
|
}
|
|
8877
9249
|
async buildBrowserStoragePersistence(config, sessionId) {
|
|
@@ -8986,13 +9358,13 @@ var AgentHarness = class _AgentHarness {
|
|
|
8986
9358
|
await this.mcpBridge?.stopLocalServers();
|
|
8987
9359
|
if (this.otlpSpanProcessor) {
|
|
8988
9360
|
await this.otlpSpanProcessor.shutdown().catch((err) => {
|
|
8989
|
-
|
|
9361
|
+
telemetryLog2.warn(`OTLP span processor shutdown error: ${formatOtlpError(err)}`);
|
|
8990
9362
|
});
|
|
8991
9363
|
this.otlpSpanProcessor = void 0;
|
|
8992
9364
|
}
|
|
8993
9365
|
if (this.otlpTracerProvider) {
|
|
8994
9366
|
await this.otlpTracerProvider.shutdown().catch((err) => {
|
|
8995
|
-
|
|
9367
|
+
telemetryLog2.warn(`OTLP tracer provider shutdown error: ${formatOtlpError(err)}`);
|
|
8996
9368
|
});
|
|
8997
9369
|
this.otlpTracerProvider = void 0;
|
|
8998
9370
|
}
|
|
@@ -9044,7 +9416,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9044
9416
|
await this.otlpSpanProcessor?.forceFlush();
|
|
9045
9417
|
} catch (err) {
|
|
9046
9418
|
const detail = formatOtlpError(err);
|
|
9047
|
-
|
|
9419
|
+
telemetryLog2.warn(`OTLP span flush failed: ${detail}`);
|
|
9048
9420
|
}
|
|
9049
9421
|
}
|
|
9050
9422
|
} else {
|
|
@@ -9090,19 +9462,15 @@ var AgentHarness = class _AgentHarness {
|
|
|
9090
9462
|
this.seedToolResultArchive(conversationId, input.parameters);
|
|
9091
9463
|
const truncationSummary = this.truncateHistoricalToolResults(messages, conversationId);
|
|
9092
9464
|
if (truncationSummary.changed) {
|
|
9093
|
-
|
|
9094
|
-
`
|
|
9465
|
+
costLog.debug(
|
|
9466
|
+
`truncated ${truncationSummary.truncatedCount} historical tool result(s) (archived=${truncationSummary.archivedCount}, omitted=${truncationSummary.omittedChars} chars) conv=${conversationId.slice(0, 8)}`
|
|
9095
9467
|
);
|
|
9096
9468
|
}
|
|
9097
9469
|
const hasFullToolResults = hasUntruncatedToolResults(messages);
|
|
9098
9470
|
if (hasFullToolResults) {
|
|
9099
|
-
|
|
9100
|
-
`[poncho][cost] Prompt cache breakpoint will be placed before untruncated tool results for run "${runId}" (stable prefix only).`
|
|
9101
|
-
);
|
|
9471
|
+
costLog.debug(`cache breakpoint before untruncated tool results (run=${runId.slice(0, 12)})`);
|
|
9102
9472
|
} else {
|
|
9103
|
-
|
|
9104
|
-
`[poncho][cost] Prompt cache breakpoint will be placed at history tail for run "${runId}" (no untruncated tool results).`
|
|
9105
|
-
);
|
|
9473
|
+
costLog.debug(`cache breakpoint at history tail (run=${runId.slice(0, 12)})`);
|
|
9106
9474
|
}
|
|
9107
9475
|
const inputMessageCount = messages.length;
|
|
9108
9476
|
const events = [];
|
|
@@ -9590,7 +9958,7 @@ ${textContent}` };
|
|
|
9590
9958
|
};
|
|
9591
9959
|
const modelName = agent.frontmatter.model?.name ?? "claude-opus-4-5";
|
|
9592
9960
|
if (step === 1) {
|
|
9593
|
-
|
|
9961
|
+
modelLog.item(`${modelName} (provider=${agent.frontmatter.model?.provider ?? "anthropic"})`);
|
|
9594
9962
|
}
|
|
9595
9963
|
const modelInstance = this.modelProvider(modelName);
|
|
9596
9964
|
const compactionConfig = resolveCompactionConfig(agent.frontmatter.compaction);
|
|
@@ -9688,8 +10056,8 @@ ${textContent}` };
|
|
|
9688
10056
|
message: `Model "${modelName}" did not respond within the ${Math.floor(timeoutMs / 1e3)}s run timeout.`
|
|
9689
10057
|
}
|
|
9690
10058
|
});
|
|
9691
|
-
|
|
9692
|
-
`
|
|
10059
|
+
harnessLog.error(
|
|
10060
|
+
`stream timeout: model=${modelName} step=${step} elapsed=${now() - start}ms`
|
|
9693
10061
|
);
|
|
9694
10062
|
return;
|
|
9695
10063
|
}
|
|
@@ -9722,8 +10090,8 @@ ${textContent}` };
|
|
|
9722
10090
|
break;
|
|
9723
10091
|
}
|
|
9724
10092
|
const isFirstChunk = chunkCount === 0;
|
|
9725
|
-
|
|
9726
|
-
`
|
|
10093
|
+
harnessLog.error(
|
|
10094
|
+
`stream timeout waiting for ${isFirstChunk ? "first" : "next"} chunk: model=${modelName} step=${step} chunks=${chunkCount} elapsed=${now() - start}ms`
|
|
9727
10095
|
);
|
|
9728
10096
|
if (isFirstChunk) {
|
|
9729
10097
|
throw new FirstChunkTimeoutError(modelName, FIRST_CHUNK_TIMEOUT_MS);
|
|
@@ -9778,7 +10146,7 @@ ${textContent}` };
|
|
|
9778
10146
|
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
9779
10147
|
contextWindow
|
|
9780
10148
|
};
|
|
9781
|
-
|
|
10149
|
+
harnessLog.info(`soft deadline fired mid-stream at step ${step} (${(now() - start).toFixed(0)}ms); checkpointing with ${fullText.length} chars of partial text`);
|
|
9782
10150
|
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
9783
10151
|
return;
|
|
9784
10152
|
}
|
|
@@ -9824,9 +10192,7 @@ ${textContent}` };
|
|
|
9824
10192
|
message: `Model "${modelName}" returned an error. This may indicate the model is not supported by the current provider SDK version, or the API returned an error response.`
|
|
9825
10193
|
}
|
|
9826
10194
|
});
|
|
9827
|
-
|
|
9828
|
-
`[poncho][harness] Model error: finishReason="error", model="${modelName}", step=${step}`
|
|
9829
|
-
);
|
|
10195
|
+
harnessLog.error(`model error: finishReason="error" model=${modelName} step=${step}`);
|
|
9830
10196
|
return;
|
|
9831
10197
|
}
|
|
9832
10198
|
if (finishReason === "content-filter") {
|
|
@@ -9862,8 +10228,8 @@ ${textContent}` };
|
|
|
9862
10228
|
cacheWrite: stepCacheWriteTokens
|
|
9863
10229
|
}
|
|
9864
10230
|
});
|
|
9865
|
-
|
|
9866
|
-
`
|
|
10231
|
+
costLog.debug(
|
|
10232
|
+
`step=${step} in=${stepInputTokens} out=${usage.outputTokens ?? 0} cached=${stepCachedTokens} cw=${stepCacheWriteTokens} totals(in=${totalInputTokens} out=${totalOutputTokens} cached=${totalCachedTokens} cw=${totalCacheWriteTokens})`
|
|
9867
10233
|
);
|
|
9868
10234
|
const toolCalls = toolCallsResult.map((tc) => ({
|
|
9869
10235
|
id: tc.toolCallId,
|
|
@@ -9882,14 +10248,10 @@ ${textContent}` };
|
|
|
9882
10248
|
message: `Model "${modelName}" returned no content (finish reason: ${finishReason}). The model may not be supported by the current provider SDK version.`
|
|
9883
10249
|
}
|
|
9884
10250
|
});
|
|
9885
|
-
|
|
9886
|
-
`[poncho][harness] Empty response: finishReason="${finishReason}", model="${modelName}", step=${step}`
|
|
9887
|
-
);
|
|
10251
|
+
harnessLog.error(`empty response: finishReason="${finishReason}" model=${modelName} step=${step}`);
|
|
9888
10252
|
return;
|
|
9889
10253
|
}
|
|
9890
|
-
|
|
9891
|
-
`[poncho][harness] Model "${modelName}" returned an empty response with finishReason="stop" on step ${step}.`
|
|
9892
|
-
);
|
|
10254
|
+
harnessLog.warn(`model "${modelName}" returned empty response with finishReason="stop" on step ${step}`);
|
|
9893
10255
|
}
|
|
9894
10256
|
if (fullText.length > 0) {
|
|
9895
10257
|
messages.push({
|
|
@@ -10255,8 +10617,8 @@ ${this.skillFingerprint}`;
|
|
|
10255
10617
|
if (isRetryableModelError(error) && transientStepRetryCount < MAX_TRANSIENT_STEP_RETRIES) {
|
|
10256
10618
|
transientStepRetryCount += 1;
|
|
10257
10619
|
const statusCode = getErrorStatusCode(error);
|
|
10258
|
-
|
|
10259
|
-
`
|
|
10620
|
+
harnessLog.warn(
|
|
10621
|
+
`retrying step ${step} after transient model error (attempt ${transientStepRetryCount}/${MAX_TRANSIENT_STEP_RETRIES})${typeof statusCode === "number" ? ` status=${statusCode}` : ""}: ${fmtErr(error)}`
|
|
10260
10622
|
);
|
|
10261
10623
|
step -= 1;
|
|
10262
10624
|
continue;
|
|
@@ -10267,7 +10629,7 @@ ${this.skillFingerprint}`;
|
|
|
10267
10629
|
runId,
|
|
10268
10630
|
error: runError
|
|
10269
10631
|
});
|
|
10270
|
-
|
|
10632
|
+
harnessLog.error(`step ${step} error: ${fmtErr(error)}`);
|
|
10271
10633
|
return;
|
|
10272
10634
|
}
|
|
10273
10635
|
}
|
|
@@ -10431,12 +10793,22 @@ var InMemoryReminderStore = class {
|
|
|
10431
10793
|
createdAt: Date.now(),
|
|
10432
10794
|
conversationId: input.conversationId,
|
|
10433
10795
|
ownerId: input.ownerId,
|
|
10434
|
-
tenantId: input.tenantId
|
|
10796
|
+
tenantId: input.tenantId,
|
|
10797
|
+
recurrence: input.recurrence ?? null,
|
|
10798
|
+
occurrenceCount: 0
|
|
10435
10799
|
};
|
|
10436
10800
|
this.reminders = pruneStale(this.reminders);
|
|
10437
10801
|
this.reminders.push(reminder);
|
|
10438
10802
|
return reminder;
|
|
10439
10803
|
}
|
|
10804
|
+
async update(id, fields) {
|
|
10805
|
+
const reminder = this.reminders.find((r) => r.id === id);
|
|
10806
|
+
if (!reminder) throw new Error(`Reminder "${id}" not found`);
|
|
10807
|
+
if (fields.scheduledAt !== void 0) reminder.scheduledAt = fields.scheduledAt;
|
|
10808
|
+
if (fields.occurrenceCount !== void 0) reminder.occurrenceCount = fields.occurrenceCount;
|
|
10809
|
+
if (fields.status !== void 0) reminder.status = fields.status;
|
|
10810
|
+
return reminder;
|
|
10811
|
+
}
|
|
10440
10812
|
async cancel(id) {
|
|
10441
10813
|
const reminder = this.reminders.find((r) => r.id === id);
|
|
10442
10814
|
if (!reminder) throw new Error(`Reminder "${id}" not found`);
|
|
@@ -10450,6 +10822,101 @@ var InMemoryReminderStore = class {
|
|
|
10450
10822
|
this.reminders = this.reminders.filter((r) => r.id !== id);
|
|
10451
10823
|
}
|
|
10452
10824
|
};
|
|
10825
|
+
var computeNextOccurrence = (reminder) => {
|
|
10826
|
+
const rec = reminder.recurrence;
|
|
10827
|
+
if (!rec) return null;
|
|
10828
|
+
const fired = (reminder.occurrenceCount ?? 0) + 1;
|
|
10829
|
+
if (rec.maxOccurrences && fired >= rec.maxOccurrences) return null;
|
|
10830
|
+
const interval = rec.interval ?? 1;
|
|
10831
|
+
const prev = reminder.scheduledAt;
|
|
10832
|
+
let next;
|
|
10833
|
+
switch (rec.type) {
|
|
10834
|
+
case "daily": {
|
|
10835
|
+
next = prev + interval * 24 * 60 * 60 * 1e3;
|
|
10836
|
+
break;
|
|
10837
|
+
}
|
|
10838
|
+
case "weekly": {
|
|
10839
|
+
if (rec.daysOfWeek && rec.daysOfWeek.length > 0) {
|
|
10840
|
+
const d = new Date(prev);
|
|
10841
|
+
const days = [...rec.daysOfWeek].sort((a, b) => a - b);
|
|
10842
|
+
const currentDay = d.getUTCDay();
|
|
10843
|
+
let nextDay = days.find((day) => day > currentDay);
|
|
10844
|
+
if (nextDay !== void 0) {
|
|
10845
|
+
const delta = nextDay - currentDay;
|
|
10846
|
+
next = prev + delta * 24 * 60 * 60 * 1e3;
|
|
10847
|
+
} else {
|
|
10848
|
+
const delta = 7 * interval - currentDay + days[0];
|
|
10849
|
+
next = prev + delta * 24 * 60 * 60 * 1e3;
|
|
10850
|
+
}
|
|
10851
|
+
} else {
|
|
10852
|
+
next = prev + interval * 7 * 24 * 60 * 60 * 1e3;
|
|
10853
|
+
}
|
|
10854
|
+
break;
|
|
10855
|
+
}
|
|
10856
|
+
case "monthly": {
|
|
10857
|
+
const d = new Date(prev);
|
|
10858
|
+
d.setUTCMonth(d.getUTCMonth() + interval);
|
|
10859
|
+
next = d.getTime();
|
|
10860
|
+
break;
|
|
10861
|
+
}
|
|
10862
|
+
case "cron": {
|
|
10863
|
+
if (!rec.expression) return null;
|
|
10864
|
+
const parsed = parseCronExpression(rec.expression);
|
|
10865
|
+
if (!parsed) return null;
|
|
10866
|
+
next = nextCronOccurrence(prev, parsed);
|
|
10867
|
+
if (next <= prev) return null;
|
|
10868
|
+
break;
|
|
10869
|
+
}
|
|
10870
|
+
default:
|
|
10871
|
+
return null;
|
|
10872
|
+
}
|
|
10873
|
+
if (rec.endsAt && next > rec.endsAt) return null;
|
|
10874
|
+
return next;
|
|
10875
|
+
};
|
|
10876
|
+
var expandField = (field, min, max) => {
|
|
10877
|
+
const values = /* @__PURE__ */ new Set();
|
|
10878
|
+
for (const part of field.split(",")) {
|
|
10879
|
+
const stepMatch = part.match(/^(.+)\/(\d+)$/);
|
|
10880
|
+
const step = stepMatch ? parseInt(stepMatch[2], 10) : 1;
|
|
10881
|
+
const range = stepMatch ? stepMatch[1] : part;
|
|
10882
|
+
if (range === "*") {
|
|
10883
|
+
for (let i = min; i <= max; i += step) values.add(i);
|
|
10884
|
+
} else if (range.includes("-")) {
|
|
10885
|
+
const [lo, hi] = range.split("-").map(Number);
|
|
10886
|
+
if (isNaN(lo) || isNaN(hi)) return null;
|
|
10887
|
+
for (let i = lo; i <= hi; i += step) values.add(i);
|
|
10888
|
+
} else {
|
|
10889
|
+
const n = parseInt(range, 10);
|
|
10890
|
+
if (isNaN(n)) return null;
|
|
10891
|
+
values.add(n);
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10894
|
+
return values;
|
|
10895
|
+
};
|
|
10896
|
+
var parseCronExpression = (expr) => {
|
|
10897
|
+
const parts = expr.trim().split(/\s+/);
|
|
10898
|
+
if (parts.length !== 5) return null;
|
|
10899
|
+
const minutes = expandField(parts[0], 0, 59);
|
|
10900
|
+
const hours = expandField(parts[1], 0, 23);
|
|
10901
|
+
const daysOfMonth = expandField(parts[2], 1, 31);
|
|
10902
|
+
const months = expandField(parts[3], 1, 12);
|
|
10903
|
+
const daysOfWeek = expandField(parts[4], 0, 6);
|
|
10904
|
+
if (!minutes || !hours || !daysOfMonth || !months || !daysOfWeek) return null;
|
|
10905
|
+
return { minutes, hours, daysOfMonth, months, daysOfWeek };
|
|
10906
|
+
};
|
|
10907
|
+
var nextCronOccurrence = (afterMs, fields) => {
|
|
10908
|
+
const d = new Date(afterMs);
|
|
10909
|
+
d.setUTCSeconds(0, 0);
|
|
10910
|
+
d.setUTCMinutes(d.getUTCMinutes() + 1);
|
|
10911
|
+
const limit = afterMs + 366 * 24 * 60 * 60 * 1e3;
|
|
10912
|
+
while (d.getTime() < limit) {
|
|
10913
|
+
if (fields.months.has(d.getUTCMonth() + 1) && fields.daysOfMonth.has(d.getUTCDate()) && fields.daysOfWeek.has(d.getUTCDay()) && fields.hours.has(d.getUTCHours()) && fields.minutes.has(d.getUTCMinutes())) {
|
|
10914
|
+
return d.getTime();
|
|
10915
|
+
}
|
|
10916
|
+
d.setUTCMinutes(d.getUTCMinutes() + 1);
|
|
10917
|
+
}
|
|
10918
|
+
return afterMs;
|
|
10919
|
+
};
|
|
10453
10920
|
var createReminderStore = (_agentId, _config, _options) => {
|
|
10454
10921
|
return new InMemoryReminderStore();
|
|
10455
10922
|
};
|
|
@@ -10514,6 +10981,7 @@ var InMemoryConversationStore = class {
|
|
|
10514
10981
|
ownerId: c.ownerId,
|
|
10515
10982
|
tenantId: c.tenantId,
|
|
10516
10983
|
parentConversationId: c.parentConversationId,
|
|
10984
|
+
parentMessageId: c.parentMessageId,
|
|
10517
10985
|
messageCount: c.messages.length,
|
|
10518
10986
|
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
10519
10987
|
channelMeta: c.channelMeta
|
|
@@ -10523,16 +10991,41 @@ var InMemoryConversationStore = class {
|
|
|
10523
10991
|
this.purgeExpired();
|
|
10524
10992
|
return this.conversations.get(conversationId);
|
|
10525
10993
|
}
|
|
10526
|
-
|
|
10994
|
+
// In-memory stores already hold the full conversation object, so there's
|
|
10995
|
+
// no separate archive blob to load. Both variants return the same data.
|
|
10996
|
+
async getWithArchive(conversationId) {
|
|
10997
|
+
return this.get(conversationId);
|
|
10998
|
+
}
|
|
10999
|
+
async getStatusSnapshot(conversationId) {
|
|
11000
|
+
const c = await this.get(conversationId);
|
|
11001
|
+
if (!c) return void 0;
|
|
11002
|
+
return {
|
|
11003
|
+
conversationId: c.conversationId,
|
|
11004
|
+
updatedAt: c.updatedAt,
|
|
11005
|
+
messageCount: c.messages.length,
|
|
11006
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
11007
|
+
hasContinuationMessages: Array.isArray(c._continuationMessages) && c._continuationMessages.length > 0,
|
|
11008
|
+
parentConversationId: c.parentConversationId ?? null,
|
|
11009
|
+
ownerId: c.ownerId,
|
|
11010
|
+
tenantId: c.tenantId,
|
|
11011
|
+
runStatus: c.runStatus ?? null
|
|
11012
|
+
};
|
|
11013
|
+
}
|
|
11014
|
+
async create(ownerId = DEFAULT_OWNER3, title, tenantId = null, init) {
|
|
10527
11015
|
const now2 = Date.now();
|
|
10528
11016
|
const conversation = {
|
|
10529
11017
|
conversationId: globalThis.crypto?.randomUUID?.() ?? `${now2}-${Math.random()}`,
|
|
10530
11018
|
title: normalizeTitle3(title),
|
|
10531
|
-
messages: [],
|
|
11019
|
+
messages: init?.messages ?? [],
|
|
10532
11020
|
ownerId,
|
|
10533
11021
|
tenantId,
|
|
10534
11022
|
createdAt: now2,
|
|
10535
|
-
updatedAt: now2
|
|
11023
|
+
updatedAt: now2,
|
|
11024
|
+
...init?.parentConversationId !== void 0 ? { parentConversationId: init.parentConversationId } : {},
|
|
11025
|
+
...init?.parentMessageId !== void 0 ? { parentMessageId: init.parentMessageId } : {},
|
|
11026
|
+
...init?.threadMeta !== void 0 ? { threadMeta: init.threadMeta } : {},
|
|
11027
|
+
...init?.subagentMeta !== void 0 ? { subagentMeta: init.subagentMeta } : {},
|
|
11028
|
+
...init?.channelMeta !== void 0 ? { channelMeta: init.channelMeta } : {}
|
|
10536
11029
|
};
|
|
10537
11030
|
this.conversations.set(conversation.conversationId, conversation);
|
|
10538
11031
|
return conversation;
|
|
@@ -10571,6 +11064,24 @@ var InMemoryConversationStore = class {
|
|
|
10571
11064
|
conversation.updatedAt = Date.now();
|
|
10572
11065
|
return conversation;
|
|
10573
11066
|
}
|
|
11067
|
+
async listThreads(parentConversationId) {
|
|
11068
|
+
this.purgeExpired();
|
|
11069
|
+
return Array.from(this.conversations.values()).filter(
|
|
11070
|
+
(c) => c.parentConversationId === parentConversationId && typeof c.parentMessageId === "string"
|
|
11071
|
+
).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
|
|
11072
|
+
conversationId: c.conversationId,
|
|
11073
|
+
title: c.title,
|
|
11074
|
+
updatedAt: c.updatedAt,
|
|
11075
|
+
createdAt: c.createdAt,
|
|
11076
|
+
ownerId: c.ownerId,
|
|
11077
|
+
tenantId: c.tenantId,
|
|
11078
|
+
parentConversationId: c.parentConversationId,
|
|
11079
|
+
parentMessageId: c.parentMessageId,
|
|
11080
|
+
messageCount: c.messages.length,
|
|
11081
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
11082
|
+
channelMeta: c.channelMeta
|
|
11083
|
+
}));
|
|
11084
|
+
}
|
|
10574
11085
|
};
|
|
10575
11086
|
var createStateStore = (config, _options) => {
|
|
10576
11087
|
const ttl = config?.ttl;
|
|
@@ -10607,31 +11118,1608 @@ function extractMetadata(payload) {
|
|
|
10607
11118
|
return void 0;
|
|
10608
11119
|
}
|
|
10609
11120
|
|
|
11121
|
+
// src/orchestrator/history.ts
|
|
11122
|
+
var isMessageArray = (value) => Array.isArray(value) && value.every((entry) => {
|
|
11123
|
+
if (!entry || typeof entry !== "object") return false;
|
|
11124
|
+
const row = entry;
|
|
11125
|
+
const role = row.role;
|
|
11126
|
+
const content = row.content;
|
|
11127
|
+
const roleOk = role === "system" || role === "user" || role === "assistant" || role === "tool";
|
|
11128
|
+
const contentOk = typeof content === "string" || Array.isArray(content);
|
|
11129
|
+
return roleOk && contentOk;
|
|
11130
|
+
});
|
|
11131
|
+
var loadCanonicalHistory = (conversation) => {
|
|
11132
|
+
if (isMessageArray(conversation._harnessMessages) && conversation._harnessMessages.length > 0) {
|
|
11133
|
+
return { messages: [...conversation._harnessMessages], source: "harness" };
|
|
11134
|
+
}
|
|
11135
|
+
return { messages: [...conversation.messages], source: "messages" };
|
|
11136
|
+
};
|
|
11137
|
+
var loadRunHistory = (conversation, options) => {
|
|
11138
|
+
if (options?.preferContinuation && isMessageArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0) {
|
|
11139
|
+
return {
|
|
11140
|
+
messages: [...conversation._continuationMessages],
|
|
11141
|
+
source: "continuation",
|
|
11142
|
+
shouldRebuildCanonical: !isMessageArray(conversation._harnessMessages) || conversation._harnessMessages.length === 0
|
|
11143
|
+
};
|
|
11144
|
+
}
|
|
11145
|
+
const canonical = loadCanonicalHistory(conversation);
|
|
11146
|
+
return {
|
|
11147
|
+
...canonical,
|
|
11148
|
+
shouldRebuildCanonical: canonical.source !== "harness"
|
|
11149
|
+
};
|
|
11150
|
+
};
|
|
11151
|
+
var resolveRunRequest = (conversation, request) => {
|
|
11152
|
+
const resolved = loadRunHistory(conversation, {
|
|
11153
|
+
preferContinuation: request.preferContinuation
|
|
11154
|
+
});
|
|
11155
|
+
return {
|
|
11156
|
+
source: resolved.source,
|
|
11157
|
+
shouldRebuildCanonical: resolved.shouldRebuildCanonical,
|
|
11158
|
+
messages: resolved.messages.length > 0 ? resolved.messages : request.messages
|
|
11159
|
+
};
|
|
11160
|
+
};
|
|
11161
|
+
|
|
11162
|
+
// src/orchestrator/turn.ts
|
|
11163
|
+
var createTurnDraftState = () => ({
|
|
11164
|
+
assistantResponse: "",
|
|
11165
|
+
toolTimeline: [],
|
|
11166
|
+
sections: [],
|
|
11167
|
+
currentTools: [],
|
|
11168
|
+
currentText: ""
|
|
11169
|
+
});
|
|
11170
|
+
var cloneSections = (sections) => sections.map((section) => ({
|
|
11171
|
+
type: section.type,
|
|
11172
|
+
content: Array.isArray(section.content) ? [...section.content] : section.content
|
|
11173
|
+
}));
|
|
11174
|
+
var flushTurnDraft = (draft) => {
|
|
11175
|
+
if (draft.currentTools.length > 0) {
|
|
11176
|
+
draft.sections.push({ type: "tools", content: draft.currentTools });
|
|
11177
|
+
draft.currentTools = [];
|
|
11178
|
+
}
|
|
11179
|
+
if (draft.currentText.length > 0) {
|
|
11180
|
+
draft.sections.push({ type: "text", content: draft.currentText });
|
|
11181
|
+
draft.currentText = "";
|
|
11182
|
+
}
|
|
11183
|
+
};
|
|
11184
|
+
var buildToolCompletedText = (event) => {
|
|
11185
|
+
const input = event.input;
|
|
11186
|
+
const output = event.output;
|
|
11187
|
+
const meta = [`${event.duration}ms`];
|
|
11188
|
+
let detail = "";
|
|
11189
|
+
if (event.tool === "bash" && input && typeof input.command === "string") {
|
|
11190
|
+
detail = input.command;
|
|
11191
|
+
} else if (event.tool === "web_search") {
|
|
11192
|
+
const q = input?.query || output?.query || "";
|
|
11193
|
+
if (q) detail = `"${q.length > 60 ? q.slice(0, 57) + "..." : q}"`;
|
|
11194
|
+
} else if (event.tool === "web_fetch") {
|
|
11195
|
+
const u = input?.url || output?.url || "";
|
|
11196
|
+
if (u) detail = u;
|
|
11197
|
+
} else if (event.tool === "spawn_subagent") {
|
|
11198
|
+
if (input && typeof input.task === "string") detail = input.task;
|
|
11199
|
+
} else if (input) {
|
|
11200
|
+
for (const [, v] of Object.entries(input)) {
|
|
11201
|
+
if (typeof v === "string" && v.length > 0) {
|
|
11202
|
+
detail = v.length > 80 ? v.slice(0, 77) + "..." : v;
|
|
11203
|
+
break;
|
|
11204
|
+
}
|
|
11205
|
+
}
|
|
11206
|
+
}
|
|
11207
|
+
if (detail) {
|
|
11208
|
+
detail = detail.replace(/\n/g, " ");
|
|
11209
|
+
meta.push(detail);
|
|
11210
|
+
}
|
|
11211
|
+
let text = `- done \`${event.tool}\`` + (meta.length > 0 ? ` (${meta.join(", ")})` : "");
|
|
11212
|
+
if (event.tool === "spawn_subagent" && output?.subagentId) {
|
|
11213
|
+
text += ` [subagent:${output.subagentId}]`;
|
|
11214
|
+
}
|
|
11215
|
+
if (event.tool === "bash" && typeof output?.exitCode === "number" && output.exitCode !== 0) {
|
|
11216
|
+
text += ` \u2014 exit ${output.exitCode}`;
|
|
11217
|
+
}
|
|
11218
|
+
if (event.tool === "web_search" && Array.isArray(output?.results)) {
|
|
11219
|
+
text += ` \u2014 ${output.results.length} result${output.results.length !== 1 ? "s" : ""}`;
|
|
11220
|
+
}
|
|
11221
|
+
return text;
|
|
11222
|
+
};
|
|
11223
|
+
var recordStandardTurnEvent = (draft, event) => {
|
|
11224
|
+
if (event.type === "model:chunk") {
|
|
11225
|
+
if (draft.currentTools.length > 0) {
|
|
11226
|
+
draft.sections.push({ type: "tools", content: draft.currentTools });
|
|
11227
|
+
draft.currentTools = [];
|
|
11228
|
+
if (draft.assistantResponse.length > 0 && !/\s$/.test(draft.assistantResponse)) {
|
|
11229
|
+
draft.assistantResponse += " ";
|
|
11230
|
+
}
|
|
11231
|
+
}
|
|
11232
|
+
draft.assistantResponse += event.content;
|
|
11233
|
+
draft.currentText += event.content;
|
|
11234
|
+
return;
|
|
11235
|
+
}
|
|
11236
|
+
if (event.type === "tool:started") {
|
|
11237
|
+
if (draft.currentText.length > 0) {
|
|
11238
|
+
draft.sections.push({ type: "text", content: draft.currentText });
|
|
11239
|
+
draft.currentText = "";
|
|
11240
|
+
}
|
|
11241
|
+
const input = event.input;
|
|
11242
|
+
let startDetail = "";
|
|
11243
|
+
if (event.tool === "bash" && input && typeof input.command === "string") {
|
|
11244
|
+
startDetail = input.command;
|
|
11245
|
+
} else if (event.tool === "web_fetch" && input && typeof input.url === "string") {
|
|
11246
|
+
startDetail = input.url;
|
|
11247
|
+
} else if (event.tool === "web_search" && input && typeof input.query === "string") {
|
|
11248
|
+
startDetail = `"${input.query}"`;
|
|
11249
|
+
} else if (input) {
|
|
11250
|
+
for (const [, v] of Object.entries(input)) {
|
|
11251
|
+
if (typeof v === "string" && v.length > 0) {
|
|
11252
|
+
startDetail = v.length > 80 ? v.slice(0, 77) + "..." : v;
|
|
11253
|
+
break;
|
|
11254
|
+
}
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
11257
|
+
if (startDetail) startDetail = startDetail.replace(/\n/g, " ");
|
|
11258
|
+
const toolText = `- start \`${event.tool}\`` + (startDetail ? ` (${startDetail})` : "");
|
|
11259
|
+
draft.toolTimeline.push(toolText);
|
|
11260
|
+
draft.currentTools.push(toolText);
|
|
11261
|
+
return;
|
|
11262
|
+
}
|
|
11263
|
+
if (event.type === "tool:completed") {
|
|
11264
|
+
const toolText = buildToolCompletedText(event);
|
|
11265
|
+
draft.toolTimeline.push(toolText);
|
|
11266
|
+
draft.currentTools.push(toolText);
|
|
11267
|
+
return;
|
|
11268
|
+
}
|
|
11269
|
+
if (event.type === "tool:error") {
|
|
11270
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
11271
|
+
draft.toolTimeline.push(toolText);
|
|
11272
|
+
draft.currentTools.push(toolText);
|
|
11273
|
+
}
|
|
11274
|
+
};
|
|
11275
|
+
var buildAssistantMetadata = (draft, sectionsOverride, opts) => {
|
|
11276
|
+
const sections = sectionsOverride ?? cloneSections(draft.sections);
|
|
11277
|
+
const hasContent = draft.toolTimeline.length > 0 || sections.length > 0;
|
|
11278
|
+
if (!hasContent && !opts?.id) return void 0;
|
|
11279
|
+
const meta = {};
|
|
11280
|
+
if (opts?.id) meta.id = opts.id;
|
|
11281
|
+
if (opts?.timestamp) meta.timestamp = opts.timestamp;
|
|
11282
|
+
if (draft.toolTimeline.length > 0) meta.toolActivity = [...draft.toolTimeline];
|
|
11283
|
+
if (sections.length > 0) meta.sections = sections;
|
|
11284
|
+
return meta;
|
|
11285
|
+
};
|
|
11286
|
+
var executeConversationTurn = async ({
|
|
11287
|
+
harness,
|
|
11288
|
+
runInput,
|
|
11289
|
+
events,
|
|
11290
|
+
initialContextTokens = 0,
|
|
11291
|
+
initialContextWindow = 0,
|
|
11292
|
+
onEvent
|
|
11293
|
+
}) => {
|
|
11294
|
+
const draft = createTurnDraftState();
|
|
11295
|
+
let latestRunId = "";
|
|
11296
|
+
let runCancelled = false;
|
|
11297
|
+
let runContinuation = false;
|
|
11298
|
+
let runContinuationMessages;
|
|
11299
|
+
let runHarnessMessages;
|
|
11300
|
+
let runContextTokens = initialContextTokens;
|
|
11301
|
+
let runContextWindow = initialContextWindow;
|
|
11302
|
+
let runSteps = 0;
|
|
11303
|
+
let runMaxSteps;
|
|
11304
|
+
const source = events ?? harness.runWithTelemetry(runInput);
|
|
11305
|
+
for await (const event of source) {
|
|
11306
|
+
recordStandardTurnEvent(draft, event);
|
|
11307
|
+
if (event.type === "run:started") {
|
|
11308
|
+
latestRunId = event.runId;
|
|
11309
|
+
}
|
|
11310
|
+
if (event.type === "run:cancelled") {
|
|
11311
|
+
runCancelled = true;
|
|
11312
|
+
}
|
|
11313
|
+
if (event.type === "run:completed") {
|
|
11314
|
+
runContinuation = event.result.continuation === true;
|
|
11315
|
+
runContinuationMessages = event.result.continuationMessages;
|
|
11316
|
+
runHarnessMessages = event.result.continuationMessages;
|
|
11317
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
11318
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
11319
|
+
runSteps = event.result.steps;
|
|
11320
|
+
if (typeof event.result.maxSteps === "number") {
|
|
11321
|
+
runMaxSteps = event.result.maxSteps;
|
|
11322
|
+
}
|
|
11323
|
+
if (draft.assistantResponse.length === 0 && event.result.response) {
|
|
11324
|
+
draft.assistantResponse = event.result.response;
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
if (event.type === "run:error") {
|
|
11328
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${event.error.message}]`;
|
|
11329
|
+
}
|
|
11330
|
+
if (onEvent) {
|
|
11331
|
+
await onEvent(event, draft);
|
|
11332
|
+
}
|
|
11333
|
+
}
|
|
11334
|
+
return {
|
|
11335
|
+
latestRunId,
|
|
11336
|
+
runCancelled,
|
|
11337
|
+
runContinuation,
|
|
11338
|
+
runContinuationMessages,
|
|
11339
|
+
runHarnessMessages,
|
|
11340
|
+
runContextTokens,
|
|
11341
|
+
runContextWindow,
|
|
11342
|
+
runSteps,
|
|
11343
|
+
runMaxSteps,
|
|
11344
|
+
draft
|
|
11345
|
+
};
|
|
11346
|
+
};
|
|
11347
|
+
var normalizePendingToolCalls = (value) => {
|
|
11348
|
+
if (!Array.isArray(value)) return [];
|
|
11349
|
+
return value.filter((entry) => {
|
|
11350
|
+
if (!entry || typeof entry !== "object") return false;
|
|
11351
|
+
const row = entry;
|
|
11352
|
+
return typeof row.id === "string" && typeof row.name === "string" && typeof row.input === "object" && row.input !== null;
|
|
11353
|
+
}).map((entry) => ({ id: entry.id, name: entry.name, input: entry.input }));
|
|
11354
|
+
};
|
|
11355
|
+
var normalizeApprovalCheckpoint = (approval, fallbackMessages) => ({
|
|
11356
|
+
...approval,
|
|
11357
|
+
checkpointMessages: isMessageArray(approval.checkpointMessages) ? approval.checkpointMessages : [...fallbackMessages],
|
|
11358
|
+
baseMessageCount: typeof approval.baseMessageCount === "number" && approval.baseMessageCount >= 0 ? approval.baseMessageCount : 0,
|
|
11359
|
+
pendingToolCalls: normalizePendingToolCalls(approval.pendingToolCalls)
|
|
11360
|
+
});
|
|
11361
|
+
var buildApprovalCheckpoints = ({
|
|
11362
|
+
approvals,
|
|
11363
|
+
runId,
|
|
11364
|
+
checkpointMessages,
|
|
11365
|
+
baseMessageCount,
|
|
11366
|
+
pendingToolCalls
|
|
11367
|
+
}) => approvals.map((approval) => ({
|
|
11368
|
+
approvalId: approval.approvalId,
|
|
11369
|
+
runId,
|
|
11370
|
+
tool: approval.tool,
|
|
11371
|
+
toolCallId: approval.toolCallId,
|
|
11372
|
+
input: approval.input,
|
|
11373
|
+
checkpointMessages,
|
|
11374
|
+
baseMessageCount,
|
|
11375
|
+
pendingToolCalls
|
|
11376
|
+
}));
|
|
11377
|
+
var applyTurnMetadata = (conv, meta, opts = {}) => {
|
|
11378
|
+
const {
|
|
11379
|
+
clearContinuation = true,
|
|
11380
|
+
clearApprovals = true,
|
|
11381
|
+
setIdle = true,
|
|
11382
|
+
shouldRebuildCanonical = false
|
|
11383
|
+
} = opts;
|
|
11384
|
+
if (meta.continuation && meta.continuationMessages) {
|
|
11385
|
+
conv._continuationMessages = meta.continuationMessages;
|
|
11386
|
+
} else if (clearContinuation) {
|
|
11387
|
+
conv._continuationMessages = void 0;
|
|
11388
|
+
conv._continuationCount = void 0;
|
|
11389
|
+
}
|
|
11390
|
+
if (meta.harnessMessages) {
|
|
11391
|
+
conv._harnessMessages = meta.harnessMessages;
|
|
11392
|
+
} else if (shouldRebuildCanonical) {
|
|
11393
|
+
conv._harnessMessages = conv.messages;
|
|
11394
|
+
}
|
|
11395
|
+
if (meta.toolResultArchive !== void 0) {
|
|
11396
|
+
conv._toolResultArchive = meta.toolResultArchive;
|
|
11397
|
+
}
|
|
11398
|
+
conv.runtimeRunId = meta.latestRunId || conv.runtimeRunId;
|
|
11399
|
+
if (clearApprovals) conv.pendingApprovals = [];
|
|
11400
|
+
if (setIdle) conv.runStatus = "idle";
|
|
11401
|
+
if (meta.contextTokens > 0) conv.contextTokens = meta.contextTokens;
|
|
11402
|
+
if (meta.contextWindow > 0) conv.contextWindow = meta.contextWindow;
|
|
11403
|
+
conv.updatedAt = Date.now();
|
|
11404
|
+
};
|
|
11405
|
+
|
|
11406
|
+
// src/orchestrator/continuation.ts
|
|
11407
|
+
var TOOL_RESULT_ARCHIVE_PARAM2 = "__toolResultArchive";
|
|
11408
|
+
var withToolResultArchiveParam = (parameters, conversation) => ({
|
|
11409
|
+
...parameters ?? {},
|
|
11410
|
+
[TOOL_RESULT_ARCHIVE_PARAM2]: conversation._toolResultArchive ?? {}
|
|
11411
|
+
});
|
|
11412
|
+
var MAX_CONTINUATION_COUNT = 20;
|
|
11413
|
+
|
|
11414
|
+
// src/orchestrator/subagents.ts
|
|
11415
|
+
var MAX_SUBAGENT_NESTING = 3;
|
|
11416
|
+
var MAX_CONCURRENT_SUBAGENTS = 2;
|
|
11417
|
+
var MAX_SUBAGENT_CALLBACK_COUNT = 20;
|
|
11418
|
+
var CALLBACK_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
11419
|
+
var STALE_SUBAGENT_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
11420
|
+
|
|
11421
|
+
// src/orchestrator/orchestrator.ts
|
|
11422
|
+
var AgentOrchestrator = class {
|
|
11423
|
+
harness;
|
|
11424
|
+
conversationStore;
|
|
11425
|
+
eventSink;
|
|
11426
|
+
telemetry;
|
|
11427
|
+
/** @internal */
|
|
11428
|
+
hooks;
|
|
11429
|
+
agentId;
|
|
11430
|
+
workingDir;
|
|
11431
|
+
// ── Runtime state (conversation runs) ──
|
|
11432
|
+
activeConversationRuns = /* @__PURE__ */ new Map();
|
|
11433
|
+
runOwners = /* @__PURE__ */ new Map();
|
|
11434
|
+
runConversations = /* @__PURE__ */ new Map();
|
|
11435
|
+
approvalDecisionTracker = /* @__PURE__ */ new Map();
|
|
11436
|
+
// ── Runtime state (subagents) ──
|
|
11437
|
+
activeSubagentRuns = /* @__PURE__ */ new Map();
|
|
11438
|
+
recentlySpawnedParents = /* @__PURE__ */ new Map();
|
|
11439
|
+
pendingSubagentApprovals = /* @__PURE__ */ new Map();
|
|
11440
|
+
pendingCallbackNeeded = /* @__PURE__ */ new Set();
|
|
11441
|
+
constructor(options) {
|
|
11442
|
+
this.harness = options.harness;
|
|
11443
|
+
this.conversationStore = options.conversationStore;
|
|
11444
|
+
this.eventSink = options.eventSink;
|
|
11445
|
+
this.telemetry = options.telemetry;
|
|
11446
|
+
this.hooks = options.hooks ?? options.continuationHooks;
|
|
11447
|
+
this.agentId = options.agentId ?? options.harness.frontmatter?.id ?? "";
|
|
11448
|
+
this.workingDir = options.workingDir ?? "";
|
|
11449
|
+
}
|
|
11450
|
+
setHooks(hooks) {
|
|
11451
|
+
this.hooks = hooks;
|
|
11452
|
+
}
|
|
11453
|
+
/** @deprecated Use setHooks instead */
|
|
11454
|
+
setContinuationHooks(hooks) {
|
|
11455
|
+
this.hooks = hooks;
|
|
11456
|
+
}
|
|
11457
|
+
get isServerless() {
|
|
11458
|
+
return typeof this.hooks?.dispatchBackground === "function";
|
|
11459
|
+
}
|
|
11460
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11461
|
+
// Continuation
|
|
11462
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11463
|
+
async runContinuation(conversationId, onYield) {
|
|
11464
|
+
const conversation = await this.conversationStore.getWithArchive(conversationId);
|
|
11465
|
+
if (!conversation) return;
|
|
11466
|
+
if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
|
|
11467
|
+
if (!conversation._continuationMessages?.length) return;
|
|
11468
|
+
if (conversation.runStatus === "running") return;
|
|
11469
|
+
const count = (conversation._continuationCount ?? 0) + 1;
|
|
11470
|
+
if (count > MAX_CONTINUATION_COUNT) {
|
|
11471
|
+
console.warn(`[poncho][continuation] Max continuation count (${MAX_CONTINUATION_COUNT}) reached for ${conversationId}`);
|
|
11472
|
+
conversation._continuationMessages = void 0;
|
|
11473
|
+
conversation._continuationCount = void 0;
|
|
11474
|
+
await this.conversationStore.update(conversation);
|
|
11475
|
+
return;
|
|
11476
|
+
}
|
|
11477
|
+
const continuationMessages = [...conversation._continuationMessages];
|
|
11478
|
+
conversation._continuationMessages = void 0;
|
|
11479
|
+
conversation._continuationCount = count;
|
|
11480
|
+
conversation.runStatus = "running";
|
|
11481
|
+
await this.conversationStore.update(conversation);
|
|
11482
|
+
const abortController = new AbortController();
|
|
11483
|
+
this.activeConversationRuns.set(conversationId, {
|
|
11484
|
+
ownerId: conversation.ownerId,
|
|
11485
|
+
abortController,
|
|
11486
|
+
runId: null
|
|
11487
|
+
});
|
|
11488
|
+
this.hooks?.onContinuationStart?.(conversationId);
|
|
11489
|
+
try {
|
|
11490
|
+
if (conversation.parentConversationId) {
|
|
11491
|
+
for await (const event of this.runSubagentContinuation(conversationId, conversation, continuationMessages)) {
|
|
11492
|
+
if (onYield) await onYield(event);
|
|
11493
|
+
}
|
|
11494
|
+
} else {
|
|
11495
|
+
await this.runChatContinuation(conversationId, conversation, continuationMessages, onYield);
|
|
11496
|
+
}
|
|
11497
|
+
} finally {
|
|
11498
|
+
this.activeConversationRuns.delete(conversationId);
|
|
11499
|
+
this.hooks?.onContinuationEnd?.(conversationId);
|
|
11500
|
+
}
|
|
11501
|
+
}
|
|
11502
|
+
async runChatContinuation(conversationId, conversation, continuationMessages, onYield) {
|
|
11503
|
+
const execution = await executeConversationTurn({
|
|
11504
|
+
harness: this.harness,
|
|
11505
|
+
runInput: {
|
|
11506
|
+
conversationId,
|
|
11507
|
+
tenantId: conversation.tenantId ?? void 0,
|
|
11508
|
+
parameters: withToolResultArchiveParam({
|
|
11509
|
+
__activeConversationId: conversationId,
|
|
11510
|
+
__ownerId: conversation.ownerId
|
|
11511
|
+
}, conversation),
|
|
11512
|
+
messages: continuationMessages,
|
|
11513
|
+
abortSignal: this.activeConversationRuns.get(conversationId)?.abortController.signal
|
|
11514
|
+
},
|
|
11515
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
11516
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
11517
|
+
onEvent: async (event) => {
|
|
11518
|
+
if (event.type === "run:started") {
|
|
11519
|
+
this.runOwners.set(event.runId, conversation.ownerId);
|
|
11520
|
+
this.runConversations.set(event.runId, conversationId);
|
|
11521
|
+
const active = this.activeConversationRuns.get(conversationId);
|
|
11522
|
+
if (active) active.runId = event.runId;
|
|
11523
|
+
}
|
|
11524
|
+
if (this.telemetry) await this.telemetry.emit(event);
|
|
11525
|
+
await this.eventSink(conversationId, event);
|
|
11526
|
+
if (onYield) await onYield(event);
|
|
11527
|
+
}
|
|
11528
|
+
});
|
|
11529
|
+
flushTurnDraft(execution.draft);
|
|
11530
|
+
const freshConv = await this.conversationStore.get(conversationId);
|
|
11531
|
+
if (!freshConv) return;
|
|
11532
|
+
const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
|
|
11533
|
+
if (hasContent) {
|
|
11534
|
+
freshConv.messages = [
|
|
11535
|
+
...freshConv.messages,
|
|
11536
|
+
{
|
|
11537
|
+
role: "assistant",
|
|
11538
|
+
content: execution.draft.assistantResponse,
|
|
11539
|
+
metadata: buildAssistantMetadata(execution.draft)
|
|
11540
|
+
}
|
|
11541
|
+
];
|
|
11542
|
+
}
|
|
11543
|
+
applyTurnMetadata(freshConv, {
|
|
11544
|
+
latestRunId: execution.latestRunId,
|
|
11545
|
+
contextTokens: execution.runContextTokens,
|
|
11546
|
+
contextWindow: execution.runContextWindow,
|
|
11547
|
+
continuation: execution.runContinuation,
|
|
11548
|
+
continuationMessages: execution.runContinuationMessages,
|
|
11549
|
+
harnessMessages: execution.runHarnessMessages,
|
|
11550
|
+
toolResultArchive: this.harness.getToolResultArchive(conversationId)
|
|
11551
|
+
}, { shouldRebuildCanonical: true });
|
|
11552
|
+
if (execution.runContinuation) {
|
|
11553
|
+
freshConv._continuationCount = conversation._continuationCount;
|
|
11554
|
+
}
|
|
11555
|
+
await this.conversationStore.update(freshConv);
|
|
11556
|
+
}
|
|
11557
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11558
|
+
// Approval checkpoint management
|
|
11559
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11560
|
+
async findPendingApproval(approvalId, owner) {
|
|
11561
|
+
const searchedConversationIds = /* @__PURE__ */ new Set();
|
|
11562
|
+
const scan = async (conversations) => {
|
|
11563
|
+
for (const conv of conversations) {
|
|
11564
|
+
if (searchedConversationIds.has(conv.conversationId)) continue;
|
|
11565
|
+
searchedConversationIds.add(conv.conversationId);
|
|
11566
|
+
if (!Array.isArray(conv.pendingApprovals)) continue;
|
|
11567
|
+
const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
|
|
11568
|
+
if (match) {
|
|
11569
|
+
return { conversation: conv, approval: match };
|
|
11570
|
+
}
|
|
11571
|
+
}
|
|
11572
|
+
return void 0;
|
|
11573
|
+
};
|
|
11574
|
+
const ownerScoped = await scan(await this.conversationStore.list(owner));
|
|
11575
|
+
if (ownerScoped) return ownerScoped;
|
|
11576
|
+
if (owner === "local-owner") {
|
|
11577
|
+
return await scan(await this.conversationStore.list());
|
|
11578
|
+
}
|
|
11579
|
+
return void 0;
|
|
11580
|
+
}
|
|
11581
|
+
async resumeRunFromCheckpoint(conversationId, conversation, checkpoint, toolResults) {
|
|
11582
|
+
const abortController = new AbortController();
|
|
11583
|
+
this.activeConversationRuns.set(conversationId, {
|
|
11584
|
+
ownerId: conversation.ownerId,
|
|
11585
|
+
abortController,
|
|
11586
|
+
runId: null
|
|
11587
|
+
});
|
|
11588
|
+
let latestRunId = conversation.runtimeRunId ?? "";
|
|
11589
|
+
let checkpointedRun = false;
|
|
11590
|
+
const normalizedCheckpoint = normalizeApprovalCheckpoint(checkpoint, conversation.messages);
|
|
11591
|
+
const baseMessages = normalizedCheckpoint.baseMessageCount != null ? conversation.messages.slice(0, normalizedCheckpoint.baseMessageCount) : [];
|
|
11592
|
+
const fullCheckpointMessages = [...baseMessages, ...normalizedCheckpoint.checkpointMessages];
|
|
11593
|
+
let resumeToolResultMsg;
|
|
11594
|
+
const lastCpMsg = fullCheckpointMessages[fullCheckpointMessages.length - 1];
|
|
11595
|
+
if (lastCpMsg?.role === "assistant") {
|
|
11596
|
+
try {
|
|
11597
|
+
const parsed = JSON.parse(typeof lastCpMsg.content === "string" ? lastCpMsg.content : "");
|
|
11598
|
+
const cpToolCalls = parsed.tool_calls ?? [];
|
|
11599
|
+
if (cpToolCalls.length > 0) {
|
|
11600
|
+
const providedMap = new Map(toolResults.map((r) => [r.callId, r]));
|
|
11601
|
+
resumeToolResultMsg = {
|
|
11602
|
+
role: "tool",
|
|
11603
|
+
content: JSON.stringify(cpToolCalls.map((tc) => {
|
|
11604
|
+
const provided = providedMap.get(tc.id);
|
|
11605
|
+
return {
|
|
11606
|
+
type: "tool_result",
|
|
11607
|
+
tool_use_id: tc.id,
|
|
11608
|
+
tool_name: provided?.toolName ?? tc.name,
|
|
11609
|
+
content: provided ? provided.error ? `Tool error: ${provided.error}` : JSON.stringify(provided.result ?? null) : "Tool error: Tool execution deferred (pending approval checkpoint)"
|
|
11610
|
+
};
|
|
11611
|
+
})),
|
|
11612
|
+
metadata: { timestamp: Date.now() }
|
|
11613
|
+
};
|
|
11614
|
+
}
|
|
11615
|
+
} catch {
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
const fullCheckpointWithResults = resumeToolResultMsg ? [...fullCheckpointMessages, resumeToolResultMsg] : fullCheckpointMessages;
|
|
11619
|
+
let draftRef;
|
|
11620
|
+
let execution;
|
|
11621
|
+
try {
|
|
11622
|
+
execution = await executeConversationTurn({
|
|
11623
|
+
harness: this.harness,
|
|
11624
|
+
events: this.harness.continueFromToolResult({
|
|
11625
|
+
messages: fullCheckpointMessages,
|
|
11626
|
+
toolResults,
|
|
11627
|
+
conversationId,
|
|
11628
|
+
abortSignal: abortController.signal
|
|
11629
|
+
}),
|
|
11630
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
11631
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
11632
|
+
onEvent: async (event, draft2) => {
|
|
11633
|
+
draftRef = draft2;
|
|
11634
|
+
if (event.type === "run:started") {
|
|
11635
|
+
latestRunId = event.runId;
|
|
11636
|
+
this.runOwners.set(event.runId, conversation.ownerId);
|
|
11637
|
+
this.runConversations.set(event.runId, conversationId);
|
|
11638
|
+
const active = this.activeConversationRuns.get(conversationId);
|
|
11639
|
+
if (active && active.abortController === abortController) {
|
|
11640
|
+
active.runId = event.runId;
|
|
11641
|
+
}
|
|
11642
|
+
}
|
|
11643
|
+
if (event.type === "tool:approval:required") {
|
|
11644
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
11645
|
+
draft2.toolTimeline.push(toolText);
|
|
11646
|
+
draft2.currentTools.push(toolText);
|
|
11647
|
+
}
|
|
11648
|
+
if (event.type === "tool:approval:checkpoint") {
|
|
11649
|
+
const cpEvent = event;
|
|
11650
|
+
const conv = await this.conversationStore.get(conversationId);
|
|
11651
|
+
if (conv) {
|
|
11652
|
+
conv.pendingApprovals = buildApprovalCheckpoints({
|
|
11653
|
+
approvals: cpEvent.approvals,
|
|
11654
|
+
runId: latestRunId,
|
|
11655
|
+
checkpointMessages: [...fullCheckpointWithResults, ...cpEvent.checkpointMessages],
|
|
11656
|
+
baseMessageCount: 0,
|
|
11657
|
+
pendingToolCalls: cpEvent.pendingToolCalls
|
|
11658
|
+
});
|
|
11659
|
+
conv.updatedAt = Date.now();
|
|
11660
|
+
await this.conversationStore.update(conv);
|
|
11661
|
+
this.hooks?.onApprovalCheckpoint?.(
|
|
11662
|
+
conversationId,
|
|
11663
|
+
cpEvent.approvals.map((a) => ({ approvalId: a.approvalId, tool: a.tool, input: a.input }))
|
|
11664
|
+
);
|
|
11665
|
+
}
|
|
11666
|
+
checkpointedRun = true;
|
|
11667
|
+
}
|
|
11668
|
+
if (this.telemetry) await this.telemetry.emit(event);
|
|
11669
|
+
await this.eventSink(conversationId, event);
|
|
11670
|
+
}
|
|
11671
|
+
});
|
|
11672
|
+
flushTurnDraft(execution.draft);
|
|
11673
|
+
latestRunId = execution.latestRunId || latestRunId;
|
|
11674
|
+
} catch (err) {
|
|
11675
|
+
console.error("[resume-run] error:", err instanceof Error ? err.message : err);
|
|
11676
|
+
if (draftRef) {
|
|
11677
|
+
draftRef.assistantResponse = draftRef.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
11678
|
+
flushTurnDraft(draftRef);
|
|
11679
|
+
}
|
|
11680
|
+
}
|
|
11681
|
+
const draft = execution?.draft ?? draftRef ?? createTurnDraftState();
|
|
11682
|
+
if (!checkpointedRun) {
|
|
11683
|
+
const conv = await this.conversationStore.get(conversationId);
|
|
11684
|
+
if (conv) {
|
|
11685
|
+
const hasAssistantContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0;
|
|
11686
|
+
if (hasAssistantContent) {
|
|
11687
|
+
const prevMessages = conv.messages;
|
|
11688
|
+
const lastMsg = prevMessages[prevMessages.length - 1];
|
|
11689
|
+
if (lastMsg && lastMsg.role === "assistant" && lastMsg.metadata) {
|
|
11690
|
+
const existingToolActivity = lastMsg.metadata.toolActivity;
|
|
11691
|
+
const existingSections = lastMsg.metadata.sections;
|
|
11692
|
+
const mergedTimeline = [
|
|
11693
|
+
...Array.isArray(existingToolActivity) ? existingToolActivity : [],
|
|
11694
|
+
...draft.toolTimeline
|
|
11695
|
+
];
|
|
11696
|
+
const mergedSections = [
|
|
11697
|
+
...Array.isArray(existingSections) ? existingSections : [],
|
|
11698
|
+
...draft.sections
|
|
11699
|
+
];
|
|
11700
|
+
const mergedText = (typeof lastMsg.content === "string" ? lastMsg.content : "") + draft.assistantResponse;
|
|
11701
|
+
conv.messages = [
|
|
11702
|
+
...prevMessages.slice(0, -1),
|
|
11703
|
+
{
|
|
11704
|
+
role: "assistant",
|
|
11705
|
+
content: mergedText,
|
|
11706
|
+
metadata: {
|
|
11707
|
+
toolActivity: mergedTimeline,
|
|
11708
|
+
sections: mergedSections.length > 0 ? mergedSections : void 0
|
|
11709
|
+
}
|
|
11710
|
+
}
|
|
11711
|
+
];
|
|
11712
|
+
} else {
|
|
11713
|
+
conv.messages = [
|
|
11714
|
+
...prevMessages,
|
|
11715
|
+
{
|
|
11716
|
+
role: "assistant",
|
|
11717
|
+
content: draft.assistantResponse,
|
|
11718
|
+
metadata: buildAssistantMetadata(draft)
|
|
11719
|
+
}
|
|
11720
|
+
];
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
applyTurnMetadata(conv, {
|
|
11724
|
+
latestRunId,
|
|
11725
|
+
contextTokens: execution?.runContextTokens ?? 0,
|
|
11726
|
+
contextWindow: execution?.runContextWindow ?? 0,
|
|
11727
|
+
harnessMessages: execution?.runHarnessMessages
|
|
11728
|
+
}, { shouldRebuildCanonical: true });
|
|
11729
|
+
await this.conversationStore.update(conv);
|
|
11730
|
+
}
|
|
11731
|
+
} else {
|
|
11732
|
+
const conv = await this.conversationStore.get(conversationId);
|
|
11733
|
+
if (conv) {
|
|
11734
|
+
conv.runStatus = "idle";
|
|
11735
|
+
conv.updatedAt = Date.now();
|
|
11736
|
+
await this.conversationStore.update(conv);
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11739
|
+
this.activeConversationRuns.delete(conversationId);
|
|
11740
|
+
if (latestRunId) {
|
|
11741
|
+
this.runOwners.delete(latestRunId);
|
|
11742
|
+
this.runConversations.delete(latestRunId);
|
|
11743
|
+
}
|
|
11744
|
+
console.log("[resume-run] complete for", conversationId);
|
|
11745
|
+
if (this.hooks?.onResumeComplete) {
|
|
11746
|
+
this.hooks.onResumeComplete(conversationId, checkpointedRun);
|
|
11747
|
+
} else {
|
|
11748
|
+
await this._handlePostResumeWork(conversationId);
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
/** After a resume completes, check for deferred subagent callbacks. */
|
|
11752
|
+
async _handlePostResumeWork(conversationId) {
|
|
11753
|
+
const hadDeferred = this.pendingCallbackNeeded.delete(conversationId);
|
|
11754
|
+
const postConv = await this.conversationStore.get(conversationId);
|
|
11755
|
+
const needsCallback = hadDeferred || !!postConv?.pendingSubagentResults?.length;
|
|
11756
|
+
const hasRunningChildren = this.hasRunningSubagentsForParent(conversationId);
|
|
11757
|
+
if (!needsCallback && !hasRunningChildren) {
|
|
11758
|
+
this.hooks?.onStreamEnd?.(conversationId);
|
|
11759
|
+
}
|
|
11760
|
+
if (needsCallback) {
|
|
11761
|
+
this.processSubagentCallback(conversationId, true).catch(
|
|
11762
|
+
(err) => console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err)
|
|
11763
|
+
);
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11767
|
+
// Subagent lifecycle
|
|
11768
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
11769
|
+
// ── Helpers ──
|
|
11770
|
+
hasRunningSubagentsForParent(parentConversationId) {
|
|
11771
|
+
for (const run of this.activeSubagentRuns.values()) {
|
|
11772
|
+
if (run.parentConversationId === parentConversationId) return true;
|
|
11773
|
+
}
|
|
11774
|
+
return false;
|
|
11775
|
+
}
|
|
11776
|
+
getRunningSubagentCountForParent(parentId) {
|
|
11777
|
+
let count = 0;
|
|
11778
|
+
for (const run of this.activeSubagentRuns.values()) {
|
|
11779
|
+
if (run.parentConversationId === parentId) count += 1;
|
|
11780
|
+
}
|
|
11781
|
+
return count;
|
|
11782
|
+
}
|
|
11783
|
+
async getSubagentDepth(conversationId) {
|
|
11784
|
+
let depth = 0;
|
|
11785
|
+
let current = await this.conversationStore.get(conversationId);
|
|
11786
|
+
while (current?.parentConversationId) {
|
|
11787
|
+
depth += 1;
|
|
11788
|
+
current = await this.conversationStore.get(current.parentConversationId);
|
|
11789
|
+
}
|
|
11790
|
+
return depth;
|
|
11791
|
+
}
|
|
11792
|
+
async hasPendingSubagentWorkForParent(parentConversationId, _owner) {
|
|
11793
|
+
if (this.hasRunningSubagentsForParent(parentConversationId)) return true;
|
|
11794
|
+
if (this.recentlySpawnedParents.has(parentConversationId)) return true;
|
|
11795
|
+
if (this.pendingCallbackNeeded.has(parentConversationId)) return true;
|
|
11796
|
+
const parentConversation = await this.conversationStore.get(parentConversationId);
|
|
11797
|
+
if (!parentConversation) return false;
|
|
11798
|
+
if (Array.isArray(parentConversation.pendingSubagentResults) && parentConversation.pendingSubagentResults.length > 0) return true;
|
|
11799
|
+
if (typeof parentConversation.runningCallbackSince === "number" && parentConversation.runningCallbackSince > 0) return true;
|
|
11800
|
+
return false;
|
|
11801
|
+
}
|
|
11802
|
+
// ── Subagent approval decision ──
|
|
11803
|
+
/**
|
|
11804
|
+
* Submit an approval decision for a pending subagent approval.
|
|
11805
|
+
* Returns whether the approval was found, the child conversation, and whether all approvals are now decided.
|
|
11806
|
+
*/
|
|
11807
|
+
async submitSubagentApprovalDecision(approvalId, approved) {
|
|
11808
|
+
const pending = this.pendingSubagentApprovals.get(approvalId);
|
|
11809
|
+
if (!pending) return { found: false, allDecided: false };
|
|
11810
|
+
const decision = approved ? "approved" : "denied";
|
|
11811
|
+
pending.checkpoint.decision = decision;
|
|
11812
|
+
await this.eventSink(
|
|
11813
|
+
pending.childConversationId,
|
|
11814
|
+
approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
|
|
11815
|
+
);
|
|
11816
|
+
const childConv = await this.conversationStore.get(pending.childConversationId);
|
|
11817
|
+
if (childConv && Array.isArray(childConv.pendingApprovals)) {
|
|
11818
|
+
childConv.pendingApprovals = childConv.pendingApprovals.map(
|
|
11819
|
+
(pa) => pa.approvalId === approvalId ? { ...pa, decision } : pa
|
|
11820
|
+
);
|
|
11821
|
+
await this.conversationStore.update(childConv);
|
|
11822
|
+
}
|
|
11823
|
+
const allApprovals = childConv?.pendingApprovals ?? [];
|
|
11824
|
+
const allDecided = allApprovals.length > 0 && allApprovals.every((pa) => pa.decision != null);
|
|
11825
|
+
if (allDecided) {
|
|
11826
|
+
for (const pa of allApprovals) this.pendingSubagentApprovals.delete(pa.approvalId);
|
|
11827
|
+
if (childConv) {
|
|
11828
|
+
childConv.pendingApprovals = [];
|
|
11829
|
+
await this.conversationStore.update(childConv);
|
|
11830
|
+
}
|
|
11831
|
+
pending.resolve(allApprovals);
|
|
11832
|
+
}
|
|
11833
|
+
return { found: true, childConversationId: pending.childConversationId, allDecided };
|
|
11834
|
+
}
|
|
11835
|
+
// ── Completion + checkpoint resume ──
|
|
11836
|
+
async handleSubagentCompletion(subagentId) {
|
|
11837
|
+
const conv = await this.conversationStore.get(subagentId);
|
|
11838
|
+
if (!conv || !conv.parentConversationId) return;
|
|
11839
|
+
if (conv.subagentMeta?.status === "completed" || conv.subagentMeta?.status === "error") return;
|
|
11840
|
+
conv.subagentMeta = { ...conv.subagentMeta, status: "completed" };
|
|
11841
|
+
conv.updatedAt = Date.now();
|
|
11842
|
+
await this.conversationStore.update(conv);
|
|
11843
|
+
const lastMsg = conv.messages[conv.messages.length - 1];
|
|
11844
|
+
const responseText = lastMsg?.role === "assistant" && typeof lastMsg.content === "string" ? lastMsg.content : "";
|
|
11845
|
+
const pendingResult = {
|
|
11846
|
+
subagentId,
|
|
11847
|
+
task: conv.subagentMeta?.task ?? conv.title,
|
|
11848
|
+
status: "completed",
|
|
11849
|
+
result: { status: "completed", response: responseText, steps: 0, tokens: { input: 0, output: 0, cached: 0 }, duration: 0 },
|
|
11850
|
+
timestamp: Date.now()
|
|
11851
|
+
};
|
|
11852
|
+
await this.conversationStore.appendSubagentResult(conv.parentConversationId, pendingResult);
|
|
11853
|
+
await this.eventSink(conv.parentConversationId, {
|
|
11854
|
+
type: "subagent:completed",
|
|
11855
|
+
subagentId,
|
|
11856
|
+
conversationId: subagentId
|
|
11857
|
+
});
|
|
11858
|
+
await this.triggerParentCallback(conv.parentConversationId);
|
|
11859
|
+
}
|
|
11860
|
+
async resumeSubagentFromCheckpoint(subagentId) {
|
|
11861
|
+
const conv = await this.conversationStore.get(subagentId);
|
|
11862
|
+
if (!conv || !conv.parentConversationId) return;
|
|
11863
|
+
const allApprovals = (conv.pendingApprovals ?? []).map(
|
|
11864
|
+
(approval) => normalizeApprovalCheckpoint(approval, conv.messages)
|
|
11865
|
+
);
|
|
11866
|
+
if (allApprovals.length === 0) return;
|
|
11867
|
+
const allDecided = allApprovals.every((a) => a.decision != null);
|
|
11868
|
+
if (!allDecided) return;
|
|
11869
|
+
conv.pendingApprovals = [];
|
|
11870
|
+
conv.subagentMeta = { ...conv.subagentMeta, status: "running" };
|
|
11871
|
+
await this.conversationStore.update(conv);
|
|
11872
|
+
const checkpointRef = allApprovals[0];
|
|
11873
|
+
const toolContext = {
|
|
11874
|
+
runId: checkpointRef.runId,
|
|
11875
|
+
agentId: this.agentId,
|
|
11876
|
+
step: 0,
|
|
11877
|
+
workingDir: this.workingDir,
|
|
11878
|
+
parameters: {},
|
|
11879
|
+
conversationId: subagentId
|
|
11880
|
+
};
|
|
11881
|
+
const approvalToolCallIds = new Set(allApprovals.map((a) => a.toolCallId));
|
|
11882
|
+
const callsToExecute = [];
|
|
11883
|
+
const deniedResults = [];
|
|
11884
|
+
for (const a of allApprovals) {
|
|
11885
|
+
if (a.decision === "approved" && a.toolCallId) {
|
|
11886
|
+
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
11887
|
+
} else if (a.decision === "denied" && a.toolCallId) {
|
|
11888
|
+
deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
|
|
11889
|
+
}
|
|
11890
|
+
}
|
|
11891
|
+
const pendingToolCalls = checkpointRef.pendingToolCalls ?? [];
|
|
11892
|
+
for (const tc of pendingToolCalls) {
|
|
11893
|
+
if (!approvalToolCallIds.has(tc.id)) callsToExecute.push(tc);
|
|
11894
|
+
}
|
|
11895
|
+
let toolResults = [...deniedResults];
|
|
11896
|
+
if (callsToExecute.length > 0) {
|
|
11897
|
+
const execResults = await this.harness.executeTools(callsToExecute, toolContext);
|
|
11898
|
+
toolResults.push(...execResults.map((r) => ({
|
|
11899
|
+
callId: r.callId,
|
|
11900
|
+
toolName: r.tool,
|
|
11901
|
+
result: r.output,
|
|
11902
|
+
error: r.error
|
|
11903
|
+
})));
|
|
11904
|
+
}
|
|
11905
|
+
await this.resumeRunFromCheckpoint(subagentId, conv, checkpointRef, toolResults);
|
|
11906
|
+
await this.handleSubagentCompletion(subagentId);
|
|
11907
|
+
}
|
|
11908
|
+
// ── Subagent run ──
|
|
11909
|
+
async runSubagent(childConversationId, parentConversationId, task, ownerId) {
|
|
11910
|
+
if (!this.hooks?.createChildHarness) {
|
|
11911
|
+
throw new Error("createChildHarness hook is required for subagent support");
|
|
11912
|
+
}
|
|
11913
|
+
const childHarness = await this.hooks.createChildHarness();
|
|
11914
|
+
const childAbortController = new AbortController();
|
|
11915
|
+
this.activeSubagentRuns.set(childConversationId, { abortController: childAbortController, harness: childHarness, parentConversationId });
|
|
11916
|
+
this.activeConversationRuns.set(childConversationId, {
|
|
11917
|
+
ownerId,
|
|
11918
|
+
abortController: childAbortController,
|
|
11919
|
+
runId: null
|
|
11920
|
+
});
|
|
11921
|
+
const spawnCount = this.recentlySpawnedParents.get(parentConversationId) ?? 0;
|
|
11922
|
+
if (spawnCount <= 1) {
|
|
11923
|
+
this.recentlySpawnedParents.delete(parentConversationId);
|
|
11924
|
+
} else {
|
|
11925
|
+
this.recentlySpawnedParents.set(parentConversationId, spawnCount - 1);
|
|
11926
|
+
}
|
|
11927
|
+
childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
|
|
11928
|
+
const draft = createTurnDraftState();
|
|
11929
|
+
let latestRunId = "";
|
|
11930
|
+
let runResult;
|
|
11931
|
+
try {
|
|
11932
|
+
const conversation = await this.conversationStore.getWithArchive(childConversationId);
|
|
11933
|
+
if (!conversation) throw new Error("Subagent conversation not found");
|
|
11934
|
+
if (conversation.subagentMeta?.status === "stopped") return;
|
|
11935
|
+
conversation.lastActivityAt = Date.now();
|
|
11936
|
+
await this.conversationStore.update(conversation);
|
|
11937
|
+
const runOutcome = resolveRunRequest(conversation, {
|
|
11938
|
+
conversationId: childConversationId,
|
|
11939
|
+
messages: conversation.messages
|
|
11940
|
+
});
|
|
11941
|
+
const harnessMessages = [...runOutcome.messages];
|
|
11942
|
+
const recallParams = this.hooks?.buildRecallParams?.({ ownerId, tenantId: conversation.tenantId, excludeConversationId: childConversationId }) ?? {};
|
|
11943
|
+
for await (const event of childHarness.runWithTelemetry({
|
|
11944
|
+
task,
|
|
11945
|
+
conversationId: childConversationId,
|
|
11946
|
+
tenantId: conversation.tenantId ?? void 0,
|
|
11947
|
+
parameters: withToolResultArchiveParam({
|
|
11948
|
+
...recallParams,
|
|
11949
|
+
__activeConversationId: childConversationId,
|
|
11950
|
+
__ownerId: ownerId
|
|
11951
|
+
}, conversation),
|
|
11952
|
+
messages: harnessMessages,
|
|
11953
|
+
abortSignal: childAbortController.signal
|
|
11954
|
+
})) {
|
|
11955
|
+
if (event.type === "run:started") {
|
|
11956
|
+
latestRunId = event.runId;
|
|
11957
|
+
const active = this.activeConversationRuns.get(childConversationId);
|
|
11958
|
+
if (active) active.runId = event.runId;
|
|
11959
|
+
}
|
|
11960
|
+
recordStandardTurnEvent(draft, event);
|
|
11961
|
+
if (event.type === "tool:approval:required") {
|
|
11962
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
11963
|
+
draft.toolTimeline.push(toolText);
|
|
11964
|
+
draft.currentTools.push(toolText);
|
|
11965
|
+
await this.eventSink(parentConversationId, {
|
|
11966
|
+
type: "subagent:approval_needed",
|
|
11967
|
+
subagentId: childConversationId,
|
|
11968
|
+
conversationId: childConversationId,
|
|
11969
|
+
tool: event.tool,
|
|
11970
|
+
approvalId: event.approvalId,
|
|
11971
|
+
input: event.input
|
|
11972
|
+
});
|
|
11973
|
+
}
|
|
11974
|
+
if (event.type === "tool:approval:checkpoint") {
|
|
11975
|
+
const cpConv = await this.conversationStore.get(childConversationId);
|
|
11976
|
+
if (cpConv) {
|
|
11977
|
+
const allCpData = buildApprovalCheckpoints({
|
|
11978
|
+
approvals: event.approvals,
|
|
11979
|
+
runId: latestRunId,
|
|
11980
|
+
checkpointMessages: [...harnessMessages, ...event.checkpointMessages],
|
|
11981
|
+
baseMessageCount: 0,
|
|
11982
|
+
pendingToolCalls: event.pendingToolCalls
|
|
11983
|
+
});
|
|
11984
|
+
cpConv.pendingApprovals = allCpData;
|
|
11985
|
+
cpConv.updatedAt = Date.now();
|
|
11986
|
+
await this.conversationStore.update(cpConv);
|
|
11987
|
+
const decidedApprovals = await new Promise((resolve12) => {
|
|
11988
|
+
for (const cpData of allCpData) {
|
|
11989
|
+
this.pendingSubagentApprovals.set(cpData.approvalId, {
|
|
11990
|
+
resolve: resolve12,
|
|
11991
|
+
childHarness,
|
|
11992
|
+
checkpoint: cpData,
|
|
11993
|
+
childConversationId,
|
|
11994
|
+
parentConversationId
|
|
11995
|
+
});
|
|
11996
|
+
}
|
|
11997
|
+
});
|
|
11998
|
+
const checkpointRef = normalizeApprovalCheckpoint(allCpData[0], [...harnessMessages]);
|
|
11999
|
+
const toolContext = {
|
|
12000
|
+
runId: checkpointRef.runId,
|
|
12001
|
+
agentId: this.agentId,
|
|
12002
|
+
step: 0,
|
|
12003
|
+
workingDir: this.workingDir,
|
|
12004
|
+
parameters: {},
|
|
12005
|
+
conversationId: childConversationId
|
|
12006
|
+
};
|
|
12007
|
+
const approvalToolCallIds = new Set(decidedApprovals.map((a) => a.toolCallId));
|
|
12008
|
+
const callsToExecute = [];
|
|
12009
|
+
const deniedResults = [];
|
|
12010
|
+
for (const a of decidedApprovals) {
|
|
12011
|
+
if (a.decision === "approved" && a.toolCallId) {
|
|
12012
|
+
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
12013
|
+
const toolText = `- done \`${a.tool}\``;
|
|
12014
|
+
draft.toolTimeline.push(toolText);
|
|
12015
|
+
draft.currentTools.push(toolText);
|
|
12016
|
+
} else if (a.toolCallId) {
|
|
12017
|
+
deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
|
|
12018
|
+
const toolText = `- denied \`${a.tool}\``;
|
|
12019
|
+
draft.toolTimeline.push(toolText);
|
|
12020
|
+
draft.currentTools.push(toolText);
|
|
12021
|
+
}
|
|
12022
|
+
}
|
|
12023
|
+
const cpPendingToolCalls = checkpointRef.pendingToolCalls ?? [];
|
|
12024
|
+
for (const tc of cpPendingToolCalls) {
|
|
12025
|
+
if (!approvalToolCallIds.has(tc.id)) {
|
|
12026
|
+
callsToExecute.push(tc);
|
|
12027
|
+
}
|
|
12028
|
+
}
|
|
12029
|
+
let toolResults = [...deniedResults];
|
|
12030
|
+
if (callsToExecute.length > 0) {
|
|
12031
|
+
const execResults = await childHarness.executeTools(callsToExecute, toolContext);
|
|
12032
|
+
toolResults.push(...execResults.map((r) => ({
|
|
12033
|
+
callId: r.callId,
|
|
12034
|
+
toolName: r.tool,
|
|
12035
|
+
result: r.output,
|
|
12036
|
+
error: r.error
|
|
12037
|
+
})));
|
|
12038
|
+
}
|
|
12039
|
+
const resumeMessages = [...checkpointRef.checkpointMessages];
|
|
12040
|
+
for await (const resumeEvent of childHarness.continueFromToolResult({
|
|
12041
|
+
messages: resumeMessages,
|
|
12042
|
+
toolResults,
|
|
12043
|
+
conversationId: childConversationId,
|
|
12044
|
+
abortSignal: childAbortController.signal
|
|
12045
|
+
})) {
|
|
12046
|
+
recordStandardTurnEvent(draft, resumeEvent);
|
|
12047
|
+
if (resumeEvent.type === "run:completed") {
|
|
12048
|
+
runResult = { status: resumeEvent.result.status, response: resumeEvent.result.response, steps: resumeEvent.result.steps, duration: resumeEvent.result.duration };
|
|
12049
|
+
if (draft.assistantResponse.length === 0 && resumeEvent.result.response) {
|
|
12050
|
+
draft.assistantResponse = resumeEvent.result.response;
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
if (resumeEvent.type === "run:error") {
|
|
12054
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${resumeEvent.error.message}]`;
|
|
12055
|
+
}
|
|
12056
|
+
await this.eventSink(childConversationId, resumeEvent);
|
|
12057
|
+
}
|
|
12058
|
+
}
|
|
12059
|
+
}
|
|
12060
|
+
if (event.type === "run:completed") {
|
|
12061
|
+
runResult = { status: event.result.status, response: event.result.response, steps: event.result.steps, duration: event.result.duration, continuation: event.result.continuation, continuationMessages: event.result.continuationMessages };
|
|
12062
|
+
if (draft.assistantResponse.length === 0 && event.result.response) {
|
|
12063
|
+
draft.assistantResponse = event.result.response;
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
if (event.type === "run:error") {
|
|
12067
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${event.error.message}]`;
|
|
12068
|
+
}
|
|
12069
|
+
await this.eventSink(childConversationId, event);
|
|
12070
|
+
}
|
|
12071
|
+
flushTurnDraft(draft);
|
|
12072
|
+
const conv = await this.conversationStore.get(childConversationId);
|
|
12073
|
+
if (conv) {
|
|
12074
|
+
const hasContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0;
|
|
12075
|
+
if (hasContent) {
|
|
12076
|
+
conv.messages.push({
|
|
12077
|
+
role: "assistant",
|
|
12078
|
+
content: draft.assistantResponse,
|
|
12079
|
+
metadata: buildAssistantMetadata(draft)
|
|
12080
|
+
});
|
|
12081
|
+
}
|
|
12082
|
+
if (runResult?.continuation && runResult.continuationMessages) {
|
|
12083
|
+
conv._continuationMessages = runResult.continuationMessages;
|
|
12084
|
+
} else {
|
|
12085
|
+
conv._continuationMessages = void 0;
|
|
12086
|
+
conv._continuationCount = void 0;
|
|
12087
|
+
}
|
|
12088
|
+
if (runResult?.continuationMessages) {
|
|
12089
|
+
conv._harnessMessages = runResult.continuationMessages;
|
|
12090
|
+
} else if (runOutcome.shouldRebuildCanonical) {
|
|
12091
|
+
conv._harnessMessages = conv.messages;
|
|
12092
|
+
}
|
|
12093
|
+
conv._toolResultArchive = childHarness.getToolResultArchive(childConversationId);
|
|
12094
|
+
conv.lastActivityAt = Date.now();
|
|
12095
|
+
conv.updatedAt = Date.now();
|
|
12096
|
+
if (runResult?.continuation) {
|
|
12097
|
+
await this.conversationStore.update(conv);
|
|
12098
|
+
this.hooks?.onStreamEnd?.(childConversationId);
|
|
12099
|
+
this.activeSubagentRuns.delete(childConversationId);
|
|
12100
|
+
this.activeConversationRuns.delete(childConversationId);
|
|
12101
|
+
try {
|
|
12102
|
+
await childHarness.shutdown();
|
|
12103
|
+
} catch {
|
|
12104
|
+
}
|
|
12105
|
+
if (this.isServerless) {
|
|
12106
|
+
this.hooks.dispatchBackground("continuation", childConversationId);
|
|
12107
|
+
} else {
|
|
12108
|
+
this.runContinuation(childConversationId).catch(
|
|
12109
|
+
(err) => console.error(`[poncho][subagent] Continuation failed:`, err instanceof Error ? err.message : err)
|
|
12110
|
+
);
|
|
12111
|
+
}
|
|
12112
|
+
return;
|
|
12113
|
+
}
|
|
12114
|
+
conv.subagentMeta = { ...conv.subagentMeta, status: "completed" };
|
|
12115
|
+
await this.conversationStore.update(conv);
|
|
12116
|
+
}
|
|
12117
|
+
this.hooks?.onStreamEnd?.(childConversationId);
|
|
12118
|
+
await this.eventSink(parentConversationId, {
|
|
12119
|
+
type: "subagent:completed",
|
|
12120
|
+
subagentId: childConversationId,
|
|
12121
|
+
conversationId: childConversationId
|
|
12122
|
+
});
|
|
12123
|
+
let subagentResponse = runResult?.response ?? draft.assistantResponse;
|
|
12124
|
+
if (!subagentResponse) {
|
|
12125
|
+
const freshSubConv = await this.conversationStore.get(childConversationId);
|
|
12126
|
+
if (freshSubConv) {
|
|
12127
|
+
const lastAssistant = [...freshSubConv.messages].reverse().find((m) => m.role === "assistant");
|
|
12128
|
+
if (lastAssistant && typeof lastAssistant.content === "string") {
|
|
12129
|
+
subagentResponse = lastAssistant.content;
|
|
12130
|
+
}
|
|
12131
|
+
}
|
|
12132
|
+
}
|
|
12133
|
+
const pendingResult = {
|
|
12134
|
+
subagentId: childConversationId,
|
|
12135
|
+
task,
|
|
12136
|
+
status: "completed",
|
|
12137
|
+
result: runResult ? { status: runResult.status, response: subagentResponse, steps: runResult.steps, tokens: { input: 0, output: 0, cached: 0 }, duration: runResult.duration } : void 0,
|
|
12138
|
+
timestamp: Date.now()
|
|
12139
|
+
};
|
|
12140
|
+
await this.conversationStore.appendSubagentResult(parentConversationId, pendingResult);
|
|
12141
|
+
this.triggerParentCallback(parentConversationId).catch(
|
|
12142
|
+
(err) => console.error(`[poncho][subagent] Parent callback failed:`, err instanceof Error ? err.message : err)
|
|
12143
|
+
);
|
|
12144
|
+
} catch (err) {
|
|
12145
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12146
|
+
console.error(`[poncho][subagent] Error in subagent ${childConversationId}:`, errMsg);
|
|
12147
|
+
const conv = await this.conversationStore.get(childConversationId);
|
|
12148
|
+
if (conv) {
|
|
12149
|
+
conv.subagentMeta = {
|
|
12150
|
+
...conv.subagentMeta,
|
|
12151
|
+
status: "error",
|
|
12152
|
+
error: { code: "SUBAGENT_ERROR", message: errMsg }
|
|
12153
|
+
};
|
|
12154
|
+
conv.updatedAt = Date.now();
|
|
12155
|
+
await this.conversationStore.update(conv);
|
|
12156
|
+
}
|
|
12157
|
+
this.hooks?.onStreamEnd?.(childConversationId);
|
|
12158
|
+
await this.eventSink(parentConversationId, {
|
|
12159
|
+
type: "subagent:error",
|
|
12160
|
+
subagentId: childConversationId,
|
|
12161
|
+
conversationId: childConversationId,
|
|
12162
|
+
error: errMsg
|
|
12163
|
+
});
|
|
12164
|
+
const pendingResult = {
|
|
12165
|
+
subagentId: childConversationId,
|
|
12166
|
+
task,
|
|
12167
|
+
status: "error",
|
|
12168
|
+
error: { code: "SUBAGENT_ERROR", message: errMsg },
|
|
12169
|
+
timestamp: Date.now()
|
|
12170
|
+
};
|
|
12171
|
+
await this.conversationStore.appendSubagentResult(parentConversationId, pendingResult).catch(() => {
|
|
12172
|
+
});
|
|
12173
|
+
this.triggerParentCallback(parentConversationId).catch(
|
|
12174
|
+
(err2) => console.error(`[poncho][subagent] Parent callback failed:`, err2 instanceof Error ? err2.message : err2)
|
|
12175
|
+
);
|
|
12176
|
+
} finally {
|
|
12177
|
+
for (const [aid, pa] of this.pendingSubagentApprovals) {
|
|
12178
|
+
if (pa.childHarness === childHarness) {
|
|
12179
|
+
this.pendingSubagentApprovals.delete(aid);
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
this.activeSubagentRuns.delete(childConversationId);
|
|
12183
|
+
this.activeConversationRuns.delete(childConversationId);
|
|
12184
|
+
try {
|
|
12185
|
+
await childHarness.shutdown();
|
|
12186
|
+
} catch {
|
|
12187
|
+
}
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
// ── Parent callback ──
|
|
12191
|
+
async triggerParentCallback(parentConversationId) {
|
|
12192
|
+
if (this.activeConversationRuns.has(parentConversationId)) {
|
|
12193
|
+
this.pendingCallbackNeeded.add(parentConversationId);
|
|
12194
|
+
return;
|
|
12195
|
+
}
|
|
12196
|
+
if (this.isServerless) {
|
|
12197
|
+
this.hooks.dispatchBackground("subagent-callback", parentConversationId);
|
|
12198
|
+
return;
|
|
12199
|
+
}
|
|
12200
|
+
await this.processSubagentCallback(parentConversationId);
|
|
12201
|
+
}
|
|
12202
|
+
async processSubagentCallback(conversationId, skipLockCheck = false) {
|
|
12203
|
+
const conversation = await this.conversationStore.getWithArchive(conversationId);
|
|
12204
|
+
if (!conversation) return;
|
|
12205
|
+
const pendingResults = conversation.pendingSubagentResults ?? [];
|
|
12206
|
+
const hasOrphanedContinuation = pendingResults.length === 0 && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0 && !this.activeConversationRuns.has(conversationId);
|
|
12207
|
+
if (pendingResults.length === 0 && !hasOrphanedContinuation) return;
|
|
12208
|
+
if (!skipLockCheck && conversation.runningCallbackSince) {
|
|
12209
|
+
const elapsed = Date.now() - conversation.runningCallbackSince;
|
|
12210
|
+
if (elapsed < CALLBACK_LOCK_STALE_MS) return;
|
|
12211
|
+
console.warn(`[poncho][subagent-callback] Stale lock detected (${elapsed}ms) for ${conversationId}, proceeding`);
|
|
12212
|
+
}
|
|
12213
|
+
conversation.pendingSubagentResults = [];
|
|
12214
|
+
conversation.runningCallbackSince = Date.now();
|
|
12215
|
+
conversation.runStatus = "running";
|
|
12216
|
+
const callbackCount = (conversation.subagentCallbackCount ?? 0) + 1;
|
|
12217
|
+
conversation.subagentCallbackCount = callbackCount;
|
|
12218
|
+
for (const pr of pendingResults) {
|
|
12219
|
+
const resultBody = pr.result ? `Status: ${pr.result.status}
|
|
12220
|
+
Response: ${pr.result.response ?? "(no response)"}
|
|
12221
|
+
Steps: ${pr.result.steps}, Duration: ${pr.result.duration}ms` : pr.error ? `Error: ${pr.error.message}` : "(no result)";
|
|
12222
|
+
conversation.messages.push({
|
|
12223
|
+
role: "user",
|
|
12224
|
+
content: `[Subagent Result] Subagent "${pr.task}" (${pr.subagentId}) ${pr.status}:
|
|
12225
|
+
|
|
12226
|
+
${resultBody}`,
|
|
12227
|
+
metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp }
|
|
12228
|
+
});
|
|
12229
|
+
}
|
|
12230
|
+
const processedIds = new Set(pendingResults.map((pr) => pr.subagentId));
|
|
12231
|
+
const freshForPending = await this.conversationStore.get(conversationId);
|
|
12232
|
+
const arrivedDuringCallback = (freshForPending?.pendingSubagentResults ?? []).filter((pr) => !processedIds.has(pr.subagentId));
|
|
12233
|
+
conversation.pendingSubagentResults = arrivedDuringCallback;
|
|
12234
|
+
conversation._harnessMessages = [...conversation.messages];
|
|
12235
|
+
conversation.updatedAt = Date.now();
|
|
12236
|
+
await this.conversationStore.update(conversation);
|
|
12237
|
+
if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
|
|
12238
|
+
console.warn(`[poncho][subagent-callback] Circuit breaker: ${callbackCount} callbacks for ${conversationId}, skipping re-run`);
|
|
12239
|
+
conversation.runningCallbackSince = void 0;
|
|
12240
|
+
conversation.runStatus = "idle";
|
|
12241
|
+
await this.conversationStore.update(conversation);
|
|
12242
|
+
return;
|
|
12243
|
+
}
|
|
12244
|
+
const isContinuationResume = hasOrphanedContinuation && pendingResults.length === 0;
|
|
12245
|
+
console.log(`[poncho][subagent-callback] Processing ${pendingResults.length} result(s) for ${conversationId} (callback #${callbackCount})${isContinuationResume ? " (continuation resume)" : ""}`);
|
|
12246
|
+
const abortController = new AbortController();
|
|
12247
|
+
this.activeConversationRuns.set(conversationId, {
|
|
12248
|
+
ownerId: conversation.ownerId,
|
|
12249
|
+
abortController,
|
|
12250
|
+
runId: null
|
|
12251
|
+
});
|
|
12252
|
+
this.hooks?.onCallbackStreamReset?.(conversationId);
|
|
12253
|
+
const historySelection = resolveRunRequest(conversation, {
|
|
12254
|
+
conversationId,
|
|
12255
|
+
messages: conversation.messages,
|
|
12256
|
+
preferContinuation: isContinuationResume
|
|
12257
|
+
});
|
|
12258
|
+
const historyMessages = [...historySelection.messages];
|
|
12259
|
+
console.info(
|
|
12260
|
+
`[poncho][subagent-callback] conversation="${conversationId}" history_source=${historySelection.source}`
|
|
12261
|
+
);
|
|
12262
|
+
let execution;
|
|
12263
|
+
try {
|
|
12264
|
+
execution = await executeConversationTurn({
|
|
12265
|
+
harness: this.harness,
|
|
12266
|
+
runInput: {
|
|
12267
|
+
task: void 0,
|
|
12268
|
+
conversationId,
|
|
12269
|
+
tenantId: conversation.tenantId ?? void 0,
|
|
12270
|
+
parameters: withToolResultArchiveParam({
|
|
12271
|
+
__activeConversationId: conversationId,
|
|
12272
|
+
__ownerId: conversation.ownerId
|
|
12273
|
+
}, conversation),
|
|
12274
|
+
messages: historyMessages,
|
|
12275
|
+
abortSignal: abortController.signal
|
|
12276
|
+
},
|
|
12277
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
12278
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
12279
|
+
onEvent: (event) => {
|
|
12280
|
+
if (event.type === "run:started") {
|
|
12281
|
+
const active = this.activeConversationRuns.get(conversationId);
|
|
12282
|
+
if (active) active.runId = event.runId;
|
|
12283
|
+
}
|
|
12284
|
+
this.eventSink(conversationId, event);
|
|
12285
|
+
}
|
|
12286
|
+
});
|
|
12287
|
+
flushTurnDraft(execution.draft);
|
|
12288
|
+
const callbackNeedsContinuation = execution.runContinuation && execution.runContinuationMessages;
|
|
12289
|
+
if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
|
|
12290
|
+
const freshConv = await this.conversationStore.get(conversationId);
|
|
12291
|
+
if (freshConv) {
|
|
12292
|
+
if (!callbackNeedsContinuation) {
|
|
12293
|
+
freshConv.messages.push({
|
|
12294
|
+
role: "assistant",
|
|
12295
|
+
content: execution.draft.assistantResponse,
|
|
12296
|
+
metadata: buildAssistantMetadata(execution.draft)
|
|
12297
|
+
});
|
|
12298
|
+
}
|
|
12299
|
+
applyTurnMetadata(freshConv, {
|
|
12300
|
+
latestRunId: execution.latestRunId,
|
|
12301
|
+
contextTokens: execution.runContextTokens,
|
|
12302
|
+
contextWindow: execution.runContextWindow,
|
|
12303
|
+
continuation: !!callbackNeedsContinuation,
|
|
12304
|
+
continuationMessages: execution.runContinuationMessages,
|
|
12305
|
+
harnessMessages: callbackNeedsContinuation ? execution.runHarnessMessages : void 0,
|
|
12306
|
+
toolResultArchive: this.harness.getToolResultArchive(conversationId)
|
|
12307
|
+
}, { shouldRebuildCanonical: true, clearApprovals: false });
|
|
12308
|
+
freshConv.runningCallbackSince = void 0;
|
|
12309
|
+
await this.conversationStore.update(freshConv);
|
|
12310
|
+
if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
|
|
12311
|
+
this.hooks?.onMessagingNotify?.(conversationId, execution.draft.assistantResponse);
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
if (execution.runContinuation) {
|
|
12316
|
+
if (this.isServerless) {
|
|
12317
|
+
this.hooks.dispatchBackground("subagent-callback", conversationId);
|
|
12318
|
+
} else {
|
|
12319
|
+
this.processSubagentCallback(conversationId, true).catch(
|
|
12320
|
+
(err) => console.error(`[poncho][subagent-callback] Continuation failed:`, err instanceof Error ? err.message : err)
|
|
12321
|
+
);
|
|
12322
|
+
}
|
|
12323
|
+
}
|
|
12324
|
+
} catch (err) {
|
|
12325
|
+
console.error(`[poncho][subagent-callback] Error during parent re-run for ${conversationId}:`, err instanceof Error ? err.message : err);
|
|
12326
|
+
const errConv = await this.conversationStore.get(conversationId);
|
|
12327
|
+
if (errConv) {
|
|
12328
|
+
errConv.runningCallbackSince = void 0;
|
|
12329
|
+
errConv.runStatus = "idle";
|
|
12330
|
+
await this.conversationStore.update(errConv);
|
|
12331
|
+
}
|
|
12332
|
+
} finally {
|
|
12333
|
+
this.activeConversationRuns.delete(conversationId);
|
|
12334
|
+
const hadDeferredTrigger = this.pendingCallbackNeeded.delete(conversationId);
|
|
12335
|
+
const freshConv = await this.conversationStore.get(conversationId);
|
|
12336
|
+
const hasPendingInStore = !!freshConv?.pendingSubagentResults?.length;
|
|
12337
|
+
const hasRunningCallbackChildren = this.hasRunningSubagentsForParent(conversationId);
|
|
12338
|
+
if (!hadDeferredTrigger && !hasPendingInStore && !hasRunningCallbackChildren) {
|
|
12339
|
+
this.hooks?.onStreamEnd?.(conversationId);
|
|
12340
|
+
}
|
|
12341
|
+
if (hadDeferredTrigger || hasPendingInStore) {
|
|
12342
|
+
if (this.isServerless) {
|
|
12343
|
+
this.hooks.dispatchBackground("subagent-callback", conversationId);
|
|
12344
|
+
} else {
|
|
12345
|
+
this.processSubagentCallback(conversationId, true).catch(
|
|
12346
|
+
(err) => console.error(`[poncho][subagent-callback] Recursive callback failed:`, err instanceof Error ? err.message : err)
|
|
12347
|
+
);
|
|
12348
|
+
}
|
|
12349
|
+
} else if (freshConv?.runningCallbackSince) {
|
|
12350
|
+
const afterClear = await this.conversationStore.clearCallbackLock(conversationId);
|
|
12351
|
+
if (afterClear?.pendingSubagentResults?.length) {
|
|
12352
|
+
if (this.isServerless) {
|
|
12353
|
+
this.hooks.dispatchBackground("subagent-callback", conversationId);
|
|
12354
|
+
} else {
|
|
12355
|
+
this.processSubagentCallback(conversationId, true).catch(
|
|
12356
|
+
(err) => console.error(`[poncho][subagent-callback] Post-clear callback failed:`, err instanceof Error ? err.message : err)
|
|
12357
|
+
);
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
}
|
|
12361
|
+
}
|
|
12362
|
+
}
|
|
12363
|
+
// ── Subagent continuation ──
|
|
12364
|
+
async *runSubagentContinuation(conversationId, conversation, continuationMessages) {
|
|
12365
|
+
if (!this.hooks?.createChildHarness) {
|
|
12366
|
+
throw new Error("createChildHarness hook is required for subagent support");
|
|
12367
|
+
}
|
|
12368
|
+
const parentConversationId = conversation.parentConversationId;
|
|
12369
|
+
const task = conversation.subagentMeta?.task ?? "";
|
|
12370
|
+
const ownerId = conversation.ownerId;
|
|
12371
|
+
const childHarness = await this.hooks.createChildHarness();
|
|
12372
|
+
childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
|
|
12373
|
+
const childAbortController = this.activeConversationRuns.get(conversationId)?.abortController ?? new AbortController();
|
|
12374
|
+
this.activeSubagentRuns.set(conversationId, { abortController: childAbortController, harness: childHarness, parentConversationId });
|
|
12375
|
+
const draft = createTurnDraftState();
|
|
12376
|
+
let runResult;
|
|
12377
|
+
try {
|
|
12378
|
+
const recallParams = this.hooks?.buildRecallParams?.({ ownerId, tenantId: conversation.tenantId, excludeConversationId: conversationId }) ?? {};
|
|
12379
|
+
for await (const event of childHarness.runWithTelemetry({
|
|
12380
|
+
conversationId,
|
|
12381
|
+
tenantId: conversation.tenantId ?? void 0,
|
|
12382
|
+
parameters: withToolResultArchiveParam({
|
|
12383
|
+
...recallParams,
|
|
12384
|
+
__activeConversationId: conversationId,
|
|
12385
|
+
__ownerId: ownerId
|
|
12386
|
+
}, conversation),
|
|
12387
|
+
messages: continuationMessages,
|
|
12388
|
+
abortSignal: childAbortController.signal
|
|
12389
|
+
})) {
|
|
12390
|
+
if (event.type === "run:started") {
|
|
12391
|
+
const active = this.activeConversationRuns.get(conversationId);
|
|
12392
|
+
if (active) active.runId = event.runId;
|
|
12393
|
+
}
|
|
12394
|
+
recordStandardTurnEvent(draft, event);
|
|
12395
|
+
if (event.type === "run:completed") {
|
|
12396
|
+
runResult = {
|
|
12397
|
+
status: event.result.status,
|
|
12398
|
+
response: event.result.response,
|
|
12399
|
+
steps: event.result.steps,
|
|
12400
|
+
duration: event.result.duration,
|
|
12401
|
+
continuation: event.result.continuation,
|
|
12402
|
+
continuationMessages: event.result.continuationMessages
|
|
12403
|
+
};
|
|
12404
|
+
if (!draft.assistantResponse && event.result.response) {
|
|
12405
|
+
draft.assistantResponse = event.result.response;
|
|
12406
|
+
}
|
|
12407
|
+
}
|
|
12408
|
+
if (event.type === "run:error") {
|
|
12409
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${event.error.message}]`;
|
|
12410
|
+
}
|
|
12411
|
+
await this.eventSink(conversationId, event);
|
|
12412
|
+
yield event;
|
|
12413
|
+
}
|
|
12414
|
+
flushTurnDraft(draft);
|
|
12415
|
+
const conv = await this.conversationStore.get(conversationId);
|
|
12416
|
+
if (conv) {
|
|
12417
|
+
const hasContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0;
|
|
12418
|
+
if (runResult?.continuation && runResult.continuationMessages) {
|
|
12419
|
+
if (hasContent) {
|
|
12420
|
+
conv.messages.push({
|
|
12421
|
+
role: "assistant",
|
|
12422
|
+
content: draft.assistantResponse,
|
|
12423
|
+
metadata: buildAssistantMetadata(draft)
|
|
12424
|
+
});
|
|
12425
|
+
}
|
|
12426
|
+
conv._continuationMessages = runResult.continuationMessages;
|
|
12427
|
+
conv._continuationCount = conversation._continuationCount;
|
|
12428
|
+
} else {
|
|
12429
|
+
conv._continuationMessages = void 0;
|
|
12430
|
+
conv._continuationCount = void 0;
|
|
12431
|
+
if (hasContent) {
|
|
12432
|
+
conv.messages.push({
|
|
12433
|
+
role: "assistant",
|
|
12434
|
+
content: draft.assistantResponse,
|
|
12435
|
+
metadata: buildAssistantMetadata(draft)
|
|
12436
|
+
});
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
if (runResult?.continuationMessages) {
|
|
12440
|
+
conv._harnessMessages = runResult.continuationMessages;
|
|
12441
|
+
} else {
|
|
12442
|
+
conv._harnessMessages = conv.messages;
|
|
12443
|
+
}
|
|
12444
|
+
conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
|
|
12445
|
+
conv.lastActivityAt = Date.now();
|
|
12446
|
+
conv.runStatus = "idle";
|
|
12447
|
+
conv.updatedAt = Date.now();
|
|
12448
|
+
if (runResult?.continuation) {
|
|
12449
|
+
await this.conversationStore.update(conv);
|
|
12450
|
+
this.activeSubagentRuns.delete(conversationId);
|
|
12451
|
+
try {
|
|
12452
|
+
await childHarness.shutdown();
|
|
12453
|
+
} catch {
|
|
12454
|
+
}
|
|
12455
|
+
return;
|
|
12456
|
+
}
|
|
12457
|
+
conv.subagentMeta = { ...conv.subagentMeta, status: "completed" };
|
|
12458
|
+
await this.conversationStore.update(conv);
|
|
12459
|
+
}
|
|
12460
|
+
this.activeSubagentRuns.delete(conversationId);
|
|
12461
|
+
await this.eventSink(parentConversationId, {
|
|
12462
|
+
type: "subagent:completed",
|
|
12463
|
+
subagentId: conversationId,
|
|
12464
|
+
conversationId
|
|
12465
|
+
});
|
|
12466
|
+
let subagentResponse = runResult?.response ?? draft.assistantResponse;
|
|
12467
|
+
if (!subagentResponse) {
|
|
12468
|
+
const freshSubConv = await this.conversationStore.get(conversationId);
|
|
12469
|
+
if (freshSubConv) {
|
|
12470
|
+
const lastAssistant = [...freshSubConv.messages].reverse().find((m) => m.role === "assistant");
|
|
12471
|
+
if (lastAssistant) {
|
|
12472
|
+
subagentResponse = typeof lastAssistant.content === "string" ? lastAssistant.content : "";
|
|
12473
|
+
}
|
|
12474
|
+
}
|
|
12475
|
+
}
|
|
12476
|
+
const parentConv = await this.conversationStore.get(parentConversationId);
|
|
12477
|
+
if (parentConv) {
|
|
12478
|
+
const result = {
|
|
12479
|
+
subagentId: conversationId,
|
|
12480
|
+
task,
|
|
12481
|
+
status: "completed",
|
|
12482
|
+
result: { status: "completed", response: subagentResponse, steps: runResult?.steps ?? 0, tokens: { input: 0, output: 0, cached: 0 }, duration: runResult?.duration ?? 0 },
|
|
12483
|
+
timestamp: Date.now()
|
|
12484
|
+
};
|
|
12485
|
+
await this.conversationStore.appendSubagentResult(parentConversationId, result);
|
|
12486
|
+
if (this.isServerless) {
|
|
12487
|
+
this.hooks.dispatchBackground("subagent-callback", parentConversationId);
|
|
12488
|
+
} else {
|
|
12489
|
+
this.processSubagentCallback(parentConversationId).catch(
|
|
12490
|
+
(err) => console.error(`[poncho][subagent] Callback failed:`, err instanceof Error ? err.message : err)
|
|
12491
|
+
);
|
|
12492
|
+
}
|
|
12493
|
+
}
|
|
12494
|
+
try {
|
|
12495
|
+
await childHarness.shutdown();
|
|
12496
|
+
} catch {
|
|
12497
|
+
}
|
|
12498
|
+
} catch (err) {
|
|
12499
|
+
this.activeSubagentRuns.delete(conversationId);
|
|
12500
|
+
try {
|
|
12501
|
+
await childHarness.shutdown();
|
|
12502
|
+
} catch {
|
|
12503
|
+
}
|
|
12504
|
+
const conv = await this.conversationStore.get(conversationId);
|
|
12505
|
+
if (conv) {
|
|
12506
|
+
conv.subagentMeta = { ...conv.subagentMeta, status: "error", error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) } };
|
|
12507
|
+
conv.runStatus = "idle";
|
|
12508
|
+
conv._continuationMessages = void 0;
|
|
12509
|
+
conv._continuationCount = void 0;
|
|
12510
|
+
conv.updatedAt = Date.now();
|
|
12511
|
+
await this.conversationStore.update(conv);
|
|
12512
|
+
}
|
|
12513
|
+
await this.eventSink(conversation.parentConversationId, {
|
|
12514
|
+
type: "subagent:completed",
|
|
12515
|
+
subagentId: conversationId,
|
|
12516
|
+
conversationId
|
|
12517
|
+
});
|
|
12518
|
+
const parentConv = await this.conversationStore.get(conversation.parentConversationId);
|
|
12519
|
+
if (parentConv) {
|
|
12520
|
+
const result = {
|
|
12521
|
+
subagentId: conversationId,
|
|
12522
|
+
task,
|
|
12523
|
+
status: "error",
|
|
12524
|
+
error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
|
|
12525
|
+
timestamp: Date.now()
|
|
12526
|
+
};
|
|
12527
|
+
await this.conversationStore.appendSubagentResult(conversation.parentConversationId, result);
|
|
12528
|
+
if (this.isServerless) {
|
|
12529
|
+
this.hooks.dispatchBackground("subagent-callback", conversation.parentConversationId);
|
|
12530
|
+
} else {
|
|
12531
|
+
this.processSubagentCallback(conversation.parentConversationId).catch(() => {
|
|
12532
|
+
});
|
|
12533
|
+
}
|
|
12534
|
+
}
|
|
12535
|
+
}
|
|
12536
|
+
}
|
|
12537
|
+
// ── SubagentManager factory ──
|
|
12538
|
+
createSubagentManager() {
|
|
12539
|
+
return {
|
|
12540
|
+
spawn: async (opts) => {
|
|
12541
|
+
const depth = await this.getSubagentDepth(opts.parentConversationId);
|
|
12542
|
+
if (depth >= MAX_SUBAGENT_NESTING - 1) {
|
|
12543
|
+
throw new Error(`Maximum subagent nesting (${MAX_SUBAGENT_NESTING} levels) reached. Cannot spawn deeper subagents.`);
|
|
12544
|
+
}
|
|
12545
|
+
if (this.getRunningSubagentCountForParent(opts.parentConversationId) >= MAX_CONCURRENT_SUBAGENTS) {
|
|
12546
|
+
throw new Error(`Maximum concurrent subagents (${MAX_CONCURRENT_SUBAGENTS}) per parent reached. Wait for running subagents to complete or stop some first.`);
|
|
12547
|
+
}
|
|
12548
|
+
const conversation = await this.conversationStore.create(
|
|
12549
|
+
opts.ownerId,
|
|
12550
|
+
opts.task.slice(0, 80),
|
|
12551
|
+
opts.tenantId ?? null,
|
|
12552
|
+
{
|
|
12553
|
+
parentConversationId: opts.parentConversationId,
|
|
12554
|
+
subagentMeta: { task: opts.task, status: "running" },
|
|
12555
|
+
messages: [{ role: "user", content: opts.task }]
|
|
12556
|
+
}
|
|
12557
|
+
);
|
|
12558
|
+
this.recentlySpawnedParents.set(
|
|
12559
|
+
opts.parentConversationId,
|
|
12560
|
+
(this.recentlySpawnedParents.get(opts.parentConversationId) ?? 0) + 1
|
|
12561
|
+
);
|
|
12562
|
+
await this.eventSink(opts.parentConversationId, {
|
|
12563
|
+
type: "subagent:spawned",
|
|
12564
|
+
subagentId: conversation.conversationId,
|
|
12565
|
+
conversationId: conversation.conversationId,
|
|
12566
|
+
task: opts.task
|
|
12567
|
+
});
|
|
12568
|
+
if (this.isServerless) {
|
|
12569
|
+
this.hooks.dispatchBackground("subagent-run", conversation.conversationId);
|
|
12570
|
+
} else {
|
|
12571
|
+
this.runSubagent(
|
|
12572
|
+
conversation.conversationId,
|
|
12573
|
+
opts.parentConversationId,
|
|
12574
|
+
opts.task,
|
|
12575
|
+
opts.ownerId
|
|
12576
|
+
).catch((err) => console.error(`[poncho][subagent] Background spawn failed:`, err instanceof Error ? err.message : err));
|
|
12577
|
+
}
|
|
12578
|
+
return { subagentId: conversation.conversationId };
|
|
12579
|
+
},
|
|
12580
|
+
sendMessage: async (subagentId, message) => {
|
|
12581
|
+
const conversation = await this.conversationStore.get(subagentId);
|
|
12582
|
+
if (!conversation) {
|
|
12583
|
+
console.error(`[poncho][subagent] sendMessage: conversation "${subagentId}" not found in store`);
|
|
12584
|
+
throw new Error(`Subagent "${subagentId}" not found.`);
|
|
12585
|
+
}
|
|
12586
|
+
if (!conversation.parentConversationId) {
|
|
12587
|
+
throw new Error(`Conversation "${subagentId}" is not a subagent.`);
|
|
12588
|
+
}
|
|
12589
|
+
if (!conversation.subagentMeta) {
|
|
12590
|
+
console.warn(`[poncho][subagent] sendMessage: conversation "${subagentId}" missing subagentMeta, recovering`);
|
|
12591
|
+
conversation.subagentMeta = { task: conversation.title, status: "stopped" };
|
|
12592
|
+
await this.conversationStore.update(conversation);
|
|
12593
|
+
}
|
|
12594
|
+
if (conversation.subagentMeta.status === "running") {
|
|
12595
|
+
throw new Error(`Subagent "${subagentId}" is currently running. Wait for it to complete before sending a new message.`);
|
|
12596
|
+
}
|
|
12597
|
+
conversation.messages.push({ role: "user", content: message });
|
|
12598
|
+
conversation.subagentMeta.status = "running";
|
|
12599
|
+
conversation.updatedAt = Date.now();
|
|
12600
|
+
await this.conversationStore.update(conversation);
|
|
12601
|
+
if (this.isServerless) {
|
|
12602
|
+
this.hooks.dispatchBackground("subagent-run", subagentId);
|
|
12603
|
+
} else {
|
|
12604
|
+
this.runSubagent(
|
|
12605
|
+
subagentId,
|
|
12606
|
+
conversation.parentConversationId,
|
|
12607
|
+
message,
|
|
12608
|
+
conversation.ownerId
|
|
12609
|
+
).catch((err) => console.error(`[poncho][subagent] Background sendMessage failed:`, err instanceof Error ? err.message : err));
|
|
12610
|
+
}
|
|
12611
|
+
return { subagentId };
|
|
12612
|
+
},
|
|
12613
|
+
stop: async (subagentId) => {
|
|
12614
|
+
const active = this.activeSubagentRuns.get(subagentId);
|
|
12615
|
+
if (active) {
|
|
12616
|
+
active.abortController.abort();
|
|
12617
|
+
}
|
|
12618
|
+
const conversation = await this.conversationStore.get(subagentId);
|
|
12619
|
+
if (conversation?.subagentMeta && conversation.subagentMeta.status === "running") {
|
|
12620
|
+
conversation.subagentMeta.status = "stopped";
|
|
12621
|
+
conversation.updatedAt = Date.now();
|
|
12622
|
+
await this.conversationStore.update(conversation);
|
|
12623
|
+
}
|
|
12624
|
+
},
|
|
12625
|
+
list: async (parentConversationId) => {
|
|
12626
|
+
const parentConv = await this.conversationStore.get(parentConversationId);
|
|
12627
|
+
const summaries = await this.conversationStore.listSummaries(parentConv?.ownerId ?? "local-owner");
|
|
12628
|
+
const childSummaries = summaries.filter((s) => s.parentConversationId === parentConversationId);
|
|
12629
|
+
const results = [];
|
|
12630
|
+
for (const s of childSummaries) {
|
|
12631
|
+
const c = await this.conversationStore.get(s.conversationId);
|
|
12632
|
+
if (c) {
|
|
12633
|
+
results.push({
|
|
12634
|
+
subagentId: c.conversationId,
|
|
12635
|
+
task: c.subagentMeta?.task ?? c.title,
|
|
12636
|
+
status: c.subagentMeta?.status ?? "stopped",
|
|
12637
|
+
messageCount: c.messages.length
|
|
12638
|
+
});
|
|
12639
|
+
}
|
|
12640
|
+
}
|
|
12641
|
+
return results;
|
|
12642
|
+
}
|
|
12643
|
+
};
|
|
12644
|
+
}
|
|
12645
|
+
// ── Stale subagent recovery ──
|
|
12646
|
+
async recoverStaleSubagents() {
|
|
12647
|
+
const allSummaries = await this.conversationStore.listSummaries();
|
|
12648
|
+
const subagentSummaries = allSummaries.filter((s) => s.parentConversationId);
|
|
12649
|
+
if (subagentSummaries.length === 0) return;
|
|
12650
|
+
const parentsToCallback = /* @__PURE__ */ new Set();
|
|
12651
|
+
const CONCURRENCY = 10;
|
|
12652
|
+
for (let i = 0; i < subagentSummaries.length; i += CONCURRENCY) {
|
|
12653
|
+
const batch = subagentSummaries.slice(i, i + CONCURRENCY);
|
|
12654
|
+
const convs = await Promise.all(batch.map((s) => this.conversationStore.get(s.conversationId)));
|
|
12655
|
+
for (const conv of convs) {
|
|
12656
|
+
if (conv?.subagentMeta?.status === "running" && conv.parentConversationId) {
|
|
12657
|
+
const lastActivity = conv.lastActivityAt ?? conv.updatedAt;
|
|
12658
|
+
const elapsed = Date.now() - lastActivity;
|
|
12659
|
+
if (elapsed < STALE_SUBAGENT_THRESHOLD_MS) continue;
|
|
12660
|
+
conv.subagentMeta.status = "error";
|
|
12661
|
+
conv.subagentMeta.error = { code: "STALE_SUBAGENT", message: `Subagent inactive for ${Math.round(elapsed / 1e3)}s (threshold: ${STALE_SUBAGENT_THRESHOLD_MS / 1e3}s)` };
|
|
12662
|
+
conv.updatedAt = Date.now();
|
|
12663
|
+
await this.conversationStore.update(conv);
|
|
12664
|
+
const pendingResult = {
|
|
12665
|
+
subagentId: conv.conversationId,
|
|
12666
|
+
task: conv.subagentMeta.task,
|
|
12667
|
+
status: "error",
|
|
12668
|
+
error: conv.subagentMeta.error,
|
|
12669
|
+
timestamp: Date.now()
|
|
12670
|
+
};
|
|
12671
|
+
await this.conversationStore.appendSubagentResult(conv.parentConversationId, pendingResult);
|
|
12672
|
+
parentsToCallback.add(conv.parentConversationId);
|
|
12673
|
+
}
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
for (const parentId of parentsToCallback) {
|
|
12677
|
+
this.processSubagentCallback(parentId).catch(
|
|
12678
|
+
(err) => console.error(`[poncho][subagent] Recovery callback failed for ${parentId}:`, err instanceof Error ? err.message : err)
|
|
12679
|
+
);
|
|
12680
|
+
}
|
|
12681
|
+
}
|
|
12682
|
+
};
|
|
12683
|
+
|
|
10610
12684
|
// src/index.ts
|
|
10611
12685
|
import { defineTool as defineTool13 } from "@poncho-ai/sdk";
|
|
10612
12686
|
export {
|
|
10613
12687
|
AgentHarness,
|
|
12688
|
+
AgentOrchestrator,
|
|
10614
12689
|
BashEnvironmentManager,
|
|
12690
|
+
CALLBACK_LOCK_STALE_MS,
|
|
10615
12691
|
InMemoryConversationStore,
|
|
10616
12692
|
InMemoryEngine,
|
|
10617
12693
|
InMemoryStateStore,
|
|
10618
12694
|
LocalMcpBridge,
|
|
10619
12695
|
LocalUploadStore,
|
|
12696
|
+
MAX_CONCURRENT_SUBAGENTS,
|
|
12697
|
+
MAX_CONTINUATION_COUNT,
|
|
12698
|
+
MAX_SUBAGENT_CALLBACK_COUNT,
|
|
12699
|
+
MAX_SUBAGENT_NESTING,
|
|
10620
12700
|
OPENAI_CODEX_CLIENT_ID,
|
|
10621
12701
|
PONCHO_UPLOAD_SCHEME,
|
|
10622
12702
|
PonchoFsAdapter,
|
|
10623
12703
|
PostgresEngine,
|
|
10624
12704
|
S3UploadStore,
|
|
12705
|
+
STALE_SUBAGENT_THRESHOLD_MS,
|
|
10625
12706
|
STORAGE_SCHEMA_VERSION,
|
|
10626
12707
|
SqliteEngine,
|
|
12708
|
+
TOOL_RESULT_ARCHIVE_PARAM2 as TOOL_RESULT_ARCHIVE_PARAM,
|
|
10627
12709
|
TelemetryEmitter,
|
|
10628
12710
|
ToolDispatcher,
|
|
10629
12711
|
VFS_SCHEME,
|
|
10630
12712
|
VercelBlobUploadStore,
|
|
12713
|
+
applyTurnMetadata,
|
|
10631
12714
|
buildAgentDirectoryName,
|
|
12715
|
+
buildApprovalCheckpoints,
|
|
12716
|
+
buildAssistantMetadata,
|
|
10632
12717
|
buildSkillContextWindow,
|
|
12718
|
+
buildToolCompletedText,
|
|
12719
|
+
cloneSections,
|
|
10633
12720
|
compactMessages,
|
|
10634
12721
|
completeOpenAICodexDeviceAuth,
|
|
12722
|
+
computeNextOccurrence,
|
|
10635
12723
|
createBashTool,
|
|
10636
12724
|
createConversationStore,
|
|
10637
12725
|
createConversationStoreFromEngine,
|
|
@@ -10653,6 +12741,7 @@ export {
|
|
|
10653
12741
|
createStorageEngine,
|
|
10654
12742
|
createSubagentTools,
|
|
10655
12743
|
createTodoStoreFromEngine,
|
|
12744
|
+
createTurnDraftState,
|
|
10656
12745
|
createUploadStore,
|
|
10657
12746
|
createWriteTool,
|
|
10658
12747
|
defineTool13 as defineTool,
|
|
@@ -10661,7 +12750,9 @@ export {
|
|
|
10661
12750
|
ensureAgentIdentity,
|
|
10662
12751
|
estimateTokens,
|
|
10663
12752
|
estimateTotalTokens,
|
|
12753
|
+
executeConversationTurn,
|
|
10664
12754
|
findSafeSplitPoint,
|
|
12755
|
+
flushTurnDraft,
|
|
10665
12756
|
generateAgentId,
|
|
10666
12757
|
getAgentStoreDirectory,
|
|
10667
12758
|
getModelContextWindow,
|
|
@@ -10669,11 +12760,15 @@ export {
|
|
|
10669
12760
|
getOpenAICodexAuthFilePath,
|
|
10670
12761
|
getOpenAICodexRequiredScopes,
|
|
10671
12762
|
getPonchoStoreRoot,
|
|
12763
|
+
isMessageArray,
|
|
10672
12764
|
jsonSchemaToZod,
|
|
12765
|
+
loadCanonicalHistory,
|
|
10673
12766
|
loadPonchoConfig,
|
|
12767
|
+
loadRunHistory,
|
|
10674
12768
|
loadSkillContext,
|
|
10675
12769
|
loadSkillInstructions,
|
|
10676
12770
|
loadSkillMetadata,
|
|
12771
|
+
normalizeApprovalCheckpoint,
|
|
10677
12772
|
normalizeOtlp,
|
|
10678
12773
|
normalizeScriptPolicyPath,
|
|
10679
12774
|
parseAgentFile,
|
|
@@ -10681,15 +12776,18 @@ export {
|
|
|
10681
12776
|
ponchoDocsTool,
|
|
10682
12777
|
readOpenAICodexSession,
|
|
10683
12778
|
readSkillResource,
|
|
12779
|
+
recordStandardTurnEvent,
|
|
10684
12780
|
renderAgentPrompt,
|
|
10685
12781
|
resolveAgentIdentity,
|
|
10686
12782
|
resolveCompactionConfig,
|
|
10687
12783
|
resolveEnv,
|
|
10688
12784
|
resolveMemoryConfig,
|
|
12785
|
+
resolveRunRequest,
|
|
10689
12786
|
resolveSkillDirs,
|
|
10690
12787
|
resolveStateConfig,
|
|
10691
12788
|
slugifyStorageComponent,
|
|
10692
12789
|
startOpenAICodexDeviceAuth,
|
|
10693
12790
|
verifyTenantToken,
|
|
12791
|
+
withToolResultArchiveParam,
|
|
10694
12792
|
writeOpenAICodexSession
|
|
10695
12793
|
};
|