@poncho-ai/harness 0.53.0 → 0.57.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 +79 -0
- package/dist/index.d.ts +216 -2
- package/dist/index.js +670 -27
- package/package.json +1 -1
- package/src/compaction.ts +206 -13
- package/src/index.ts +18 -0
- package/src/orchestrator/entries-dual-write.ts +265 -0
- package/src/orchestrator/index.ts +7 -0
- package/src/orchestrator/orchestrator.ts +179 -13
- package/src/orchestrator/run-conversation-turn.ts +108 -0
- package/src/state.ts +56 -0
- package/src/storage/engine.ts +18 -0
- package/src/storage/entries.ts +217 -0
- package/src/storage/memory-engine.ts +40 -0
- package/src/storage/schema.ts +30 -0
- package/src/storage/sql-dialect.ts +112 -0
- package/src/storage/store-adapters.ts +8 -0
- package/test/compaction.test.ts +274 -0
- package/test/entries-dual-write.test.ts +172 -0
- package/test/entries-store.test.ts +165 -0
- package/test/entries.test.ts +125 -0
package/dist/index.js
CHANGED
|
@@ -374,6 +374,9 @@ var SUMMARIZATION_PROMPT = `Summarize the following conversation into a structur
|
|
|
374
374
|
|
|
375
375
|
Be concise but preserve all information needed to continue the task.
|
|
376
376
|
Omit any section that has no relevant content.`;
|
|
377
|
+
var CUMULATIVE_SUMMARY_PROMPT = `The FIRST message below (tagged [prior-summary]) is an existing working-state summary produced by an earlier compaction. Treat it as the authoritative prior working state: MERGE AND UPDATE it with the newer messages that follow it, carrying forward all still-relevant detail. Do NOT discard or re-compress information from the prior summary just because it is older \u2014 only drop it if the newer messages explicitly supersede it.`;
|
|
378
|
+
var SUBAGENT_DIGEST_CHARS = 500;
|
|
379
|
+
var SUBAGENT_LEDGER_HEADING = "## Subagents";
|
|
377
380
|
var resolveCompactionConfig = (explicit) => {
|
|
378
381
|
if (!explicit) return { ...DEFAULT_COMPACTION_CONFIG };
|
|
379
382
|
return {
|
|
@@ -398,32 +401,57 @@ var estimateTotalTokens = (systemPrompt, messages, toolDefinitionsJson) => {
|
|
|
398
401
|
}
|
|
399
402
|
return tokens;
|
|
400
403
|
};
|
|
404
|
+
var assistantHasToolCalls = (msg) => {
|
|
405
|
+
if (msg.role !== "assistant") return false;
|
|
406
|
+
if (typeof msg.content !== "string") return false;
|
|
407
|
+
if (!msg.content.includes('"tool_calls"')) return false;
|
|
408
|
+
try {
|
|
409
|
+
const parsed = JSON.parse(msg.content);
|
|
410
|
+
return Array.isArray(parsed.tool_calls) && parsed.tool_calls.length > 0;
|
|
411
|
+
} catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var splitOrphansToolCalls = (messages, idx) => {
|
|
416
|
+
if (idx <= 0 || idx >= messages.length) return false;
|
|
417
|
+
const lastCompacted = messages[idx - 1];
|
|
418
|
+
return assistantHasToolCalls(lastCompacted);
|
|
419
|
+
};
|
|
401
420
|
var findSafeSplitPoint = (messages, keepRecentMessages) => {
|
|
402
421
|
const candidateIdx = messages.length - keepRecentMessages;
|
|
403
422
|
if (candidateIdx < MIN_COMPACTABLE_MESSAGES) return -1;
|
|
404
423
|
for (let i = candidateIdx; i >= MIN_COMPACTABLE_MESSAGES; i--) {
|
|
405
|
-
if (messages[i].role === "user") {
|
|
424
|
+
if (messages[i].role === "user" && !splitOrphansToolCalls(messages, i)) {
|
|
406
425
|
return i;
|
|
407
426
|
}
|
|
408
427
|
}
|
|
409
428
|
for (let i = candidateIdx + 1; i < messages.length - 1; i++) {
|
|
410
|
-
if (messages[i].role === "user") {
|
|
429
|
+
if (messages[i].role === "user" && !splitOrphansToolCalls(messages, i)) {
|
|
411
430
|
if (i < MIN_COMPACTABLE_MESSAGES) return -1;
|
|
412
431
|
return i;
|
|
413
432
|
}
|
|
414
433
|
}
|
|
415
434
|
return -1;
|
|
416
435
|
};
|
|
436
|
+
var isCompactionSummary = (msg) => msg.metadata?.isCompactionSummary === true;
|
|
417
437
|
var buildSummarizationMessages = (messagesToCompact, instructions) => {
|
|
438
|
+
const hasPriorSummary = messagesToCompact.length > 0 && isCompactionSummary(messagesToCompact[0]);
|
|
418
439
|
const conversationLines = [];
|
|
419
|
-
for (
|
|
440
|
+
for (let i = 0; i < messagesToCompact.length; i++) {
|
|
441
|
+
const msg = messagesToCompact[i];
|
|
420
442
|
const text = getTextContent(msg);
|
|
421
|
-
const
|
|
422
|
-
|
|
443
|
+
const isPrior = i === 0 && hasPriorSummary;
|
|
444
|
+
const rendered = isPrior || text.length <= SUMMARIZATION_MESSAGE_TRUNCATION_CHARS ? text : text.slice(0, SUMMARIZATION_MESSAGE_TRUNCATION_CHARS) + "\n...[truncated]";
|
|
445
|
+
const tag = isPrior ? "prior-summary" : msg.role;
|
|
446
|
+
conversationLines.push(`[${tag}]: ${rendered}`);
|
|
423
447
|
}
|
|
424
|
-
|
|
448
|
+
let prompt = SUMMARIZATION_PROMPT;
|
|
449
|
+
if (hasPriorSummary) prompt = `${prompt}
|
|
450
|
+
|
|
451
|
+
${CUMULATIVE_SUMMARY_PROMPT}`;
|
|
452
|
+
if (instructions) prompt = `${prompt}
|
|
425
453
|
|
|
426
|
-
Additional focus: ${instructions}
|
|
454
|
+
Additional focus: ${instructions}`;
|
|
427
455
|
return [
|
|
428
456
|
{
|
|
429
457
|
role: "user",
|
|
@@ -435,6 +463,67 @@ ${conversationLines.join("\n\n")}`
|
|
|
435
463
|
}
|
|
436
464
|
];
|
|
437
465
|
};
|
|
466
|
+
var SUBAGENT_RESULT_HEADER = /^\[Subagent Result\] Subagent "([^"]*)" \(([^)]*)\) (\S+):/;
|
|
467
|
+
var parseSubagentCallback = (msg) => {
|
|
468
|
+
if (msg.role !== "user") return null;
|
|
469
|
+
const meta = msg.metadata ?? {};
|
|
470
|
+
const text = getTextContent(msg);
|
|
471
|
+
const hasMetaFlag = meta._subagentCallback === true || meta.subagentCallback === true;
|
|
472
|
+
const hasTextMarker = text.startsWith("[Subagent Result]");
|
|
473
|
+
if (!hasMetaFlag && !hasTextMarker) return null;
|
|
474
|
+
const headerMatch = text.match(SUBAGENT_RESULT_HEADER);
|
|
475
|
+
const subagentId = typeof meta.subagentId === "string" && meta.subagentId ? meta.subagentId : headerMatch?.[2] ?? "";
|
|
476
|
+
if (!subagentId) return null;
|
|
477
|
+
const task = typeof meta.task === "string" && meta.task ? meta.task : headerMatch?.[1] ?? "";
|
|
478
|
+
const status = headerMatch?.[3] ?? "completed";
|
|
479
|
+
const bodyStart = text.indexOf("\n\n");
|
|
480
|
+
const body = bodyStart >= 0 ? text.slice(bodyStart + 2) : text;
|
|
481
|
+
const digest = body.length > SUBAGENT_DIGEST_CHARS ? body.slice(0, SUBAGENT_DIGEST_CHARS) + "\u2026" : body;
|
|
482
|
+
return { subagentId, task, status, digest };
|
|
483
|
+
};
|
|
484
|
+
var parsePriorLedger = (summaryText) => {
|
|
485
|
+
const headingIdx = summaryText.indexOf(SUBAGENT_LEDGER_HEADING);
|
|
486
|
+
if (headingIdx < 0) return [];
|
|
487
|
+
const block = summaryText.slice(headingIdx + SUBAGENT_LEDGER_HEADING.length);
|
|
488
|
+
const entries = [];
|
|
489
|
+
const entryRe = /^- \*\*(.*?)\*\* \((.+?)\) — (\S+)\n {2}(.*)$/gm;
|
|
490
|
+
let m;
|
|
491
|
+
while ((m = entryRe.exec(block)) !== null) {
|
|
492
|
+
entries.push({
|
|
493
|
+
task: m[1],
|
|
494
|
+
subagentId: m[2],
|
|
495
|
+
status: m[3],
|
|
496
|
+
digest: m[4]
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return entries;
|
|
500
|
+
};
|
|
501
|
+
var collectSubagentLedger = (messagesToCompact) => {
|
|
502
|
+
const byId = /* @__PURE__ */ new Map();
|
|
503
|
+
const order = [];
|
|
504
|
+
const upsert = (entry) => {
|
|
505
|
+
if (!byId.has(entry.subagentId)) order.push(entry.subagentId);
|
|
506
|
+
byId.set(entry.subagentId, entry);
|
|
507
|
+
};
|
|
508
|
+
for (const msg of messagesToCompact) {
|
|
509
|
+
if (isCompactionSummary(msg)) {
|
|
510
|
+
for (const prior of parsePriorLedger(getTextContent(msg))) upsert(prior);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const entry = parseSubagentCallback(msg);
|
|
514
|
+
if (entry) upsert(entry);
|
|
515
|
+
}
|
|
516
|
+
return order.map((id) => byId.get(id));
|
|
517
|
+
};
|
|
518
|
+
var renderSubagentLedger = (entries) => {
|
|
519
|
+
if (entries.length === 0) return "";
|
|
520
|
+
const lines = entries.map(
|
|
521
|
+
(e) => `- **${e.task}** (${e.subagentId}) \u2014 ${e.status}
|
|
522
|
+
${e.digest.replace(/\n/g, " ")}`
|
|
523
|
+
);
|
|
524
|
+
return `${SUBAGENT_LEDGER_HEADING}
|
|
525
|
+
${lines.join("\n")}`;
|
|
526
|
+
};
|
|
438
527
|
var buildContinuationMessage = (summary) => ({
|
|
439
528
|
role: "user",
|
|
440
529
|
content: `[CONTEXT COMPACTION] This conversation was automatically compacted. The summary below covers earlier messages.
|
|
@@ -483,7 +572,11 @@ var compactMessages = async (model, messages, config, options) => {
|
|
|
483
572
|
warning: "Summarization returned empty result"
|
|
484
573
|
};
|
|
485
574
|
}
|
|
486
|
-
const
|
|
575
|
+
const ledger = renderSubagentLedger(collectSubagentLedger(toCompact));
|
|
576
|
+
const summaryWithLedger = ledger ? `${summary}
|
|
577
|
+
|
|
578
|
+
${ledger}` : summary;
|
|
579
|
+
const continuationMessage = buildContinuationMessage(summaryWithLedger);
|
|
487
580
|
const compactedMessages = [continuationMessage, ...toPreserve];
|
|
488
581
|
return {
|
|
489
582
|
compacted: true,
|
|
@@ -2696,6 +2789,8 @@ var InMemoryEngine = class {
|
|
|
2696
2789
|
agentId;
|
|
2697
2790
|
// Conversation data
|
|
2698
2791
|
convs = /* @__PURE__ */ new Map();
|
|
2792
|
+
// Append-only conversation entries (Phase 3 substrate)
|
|
2793
|
+
entries = /* @__PURE__ */ new Map();
|
|
2699
2794
|
// Memory data
|
|
2700
2795
|
mem = /* @__PURE__ */ new Map();
|
|
2701
2796
|
// Todos data
|
|
@@ -2825,6 +2920,30 @@ var InMemoryEngine = class {
|
|
|
2825
2920
|
}
|
|
2826
2921
|
results.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
2827
2922
|
return results;
|
|
2923
|
+
},
|
|
2924
|
+
appendEntries: async (conversationId, _agentId, _tenantId, entries) => {
|
|
2925
|
+
const list = this.entries.get(conversationId) ?? [];
|
|
2926
|
+
let nextSeq = list.reduce((max, e) => e.seq > max ? e.seq : max, 0) + 1;
|
|
2927
|
+
const now2 = Date.now();
|
|
2928
|
+
const stored = entries.map(
|
|
2929
|
+
(e) => ({ ...e, seq: nextSeq++, createdAt: now2 })
|
|
2930
|
+
);
|
|
2931
|
+
this.entries.set(conversationId, [...list, ...stored]);
|
|
2932
|
+
return stored;
|
|
2933
|
+
},
|
|
2934
|
+
readEntries: async (conversationId, opts) => {
|
|
2935
|
+
let list = (this.entries.get(conversationId) ?? []).slice().sort((a, b) => a.seq - b.seq);
|
|
2936
|
+
if (opts?.types && opts.types.length > 0) {
|
|
2937
|
+
const allowed = new Set(opts.types);
|
|
2938
|
+
list = list.filter((e) => allowed.has(e.type));
|
|
2939
|
+
}
|
|
2940
|
+
if (typeof opts?.afterSeq === "number") {
|
|
2941
|
+
list = list.filter((e) => e.seq > opts.afterSeq);
|
|
2942
|
+
}
|
|
2943
|
+
if (typeof opts?.limit === "number") {
|
|
2944
|
+
list = list.slice(0, opts.limit);
|
|
2945
|
+
}
|
|
2946
|
+
return list;
|
|
2828
2947
|
}
|
|
2829
2948
|
};
|
|
2830
2949
|
// -----------------------------------------------------------------------
|
|
@@ -3364,6 +3483,36 @@ var migrations = [
|
|
|
3364
3483
|
`ALTER TABLE reminders ALTER COLUMN scheduled_at TYPE BIGINT USING scheduled_at::bigint`
|
|
3365
3484
|
];
|
|
3366
3485
|
}
|
|
3486
|
+
},
|
|
3487
|
+
{
|
|
3488
|
+
version: 8,
|
|
3489
|
+
name: "conversation_entries",
|
|
3490
|
+
// Append-only conversation log (Phase 3 substrate). Additive: no
|
|
3491
|
+
// existing table or behavior changes. `seq` is a per-conversation
|
|
3492
|
+
// monotonic order assigned by the application (NOT an autoincrement
|
|
3493
|
+
// serial), so the same seq space restarts at 1 for every conversation.
|
|
3494
|
+
// The UNIQUE (conversation_id, seq) constraint backstops the
|
|
3495
|
+
// app-assigned ordering against concurrent writers.
|
|
3496
|
+
up: (d) => {
|
|
3497
|
+
const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
|
|
3498
|
+
const tsDefault = d === "sqlite" ? "datetime('now')" : "NOW()";
|
|
3499
|
+
const autoTs = `DEFAULT (${tsDefault})`;
|
|
3500
|
+
return [
|
|
3501
|
+
`CREATE TABLE IF NOT EXISTS conversation_entries (
|
|
3502
|
+
seq INTEGER NOT NULL,
|
|
3503
|
+
id TEXT NOT NULL UNIQUE,
|
|
3504
|
+
agent_id TEXT NOT NULL,
|
|
3505
|
+
tenant_id TEXT NOT NULL DEFAULT '__default__',
|
|
3506
|
+
conversation_id TEXT NOT NULL,
|
|
3507
|
+
type TEXT NOT NULL,
|
|
3508
|
+
payload ${jsonType} NOT NULL,
|
|
3509
|
+
created_at TIMESTAMP ${autoTs},
|
|
3510
|
+
UNIQUE (conversation_id, seq)
|
|
3511
|
+
)`,
|
|
3512
|
+
`CREATE INDEX IF NOT EXISTS idx_conversation_entries_seq
|
|
3513
|
+
ON conversation_entries (conversation_id, seq)`
|
|
3514
|
+
];
|
|
3515
|
+
}
|
|
3367
3516
|
}
|
|
3368
3517
|
];
|
|
3369
3518
|
|
|
@@ -3787,6 +3936,87 @@ var SqlStorageEngine = class {
|
|
|
3787
3936
|
parentConversationId
|
|
3788
3937
|
]);
|
|
3789
3938
|
return rows.map((r) => this.rowToSummary(r));
|
|
3939
|
+
},
|
|
3940
|
+
appendEntries: async (conversationId, agentId, tenantId, entries) => {
|
|
3941
|
+
if (entries.length === 0) return [];
|
|
3942
|
+
const tid = normalizeTenant2(tenantId);
|
|
3943
|
+
const maxAttempts = 3;
|
|
3944
|
+
let lastErr;
|
|
3945
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
3946
|
+
const stored = [];
|
|
3947
|
+
try {
|
|
3948
|
+
await this.executor.transaction(async () => {
|
|
3949
|
+
stored.length = 0;
|
|
3950
|
+
const row = await this.executor.get(
|
|
3951
|
+
rewrite(
|
|
3952
|
+
"SELECT MAX(seq) AS max_seq FROM conversation_entries WHERE conversation_id = $1",
|
|
3953
|
+
this.dialect
|
|
3954
|
+
),
|
|
3955
|
+
[conversationId]
|
|
3956
|
+
);
|
|
3957
|
+
let nextSeq = Number(row?.max_seq ?? 0) + 1;
|
|
3958
|
+
const now2 = Date.now();
|
|
3959
|
+
for (const entry of entries) {
|
|
3960
|
+
const seq = nextSeq++;
|
|
3961
|
+
const createdAt = now2;
|
|
3962
|
+
const payload = JSON.stringify(entry);
|
|
3963
|
+
await this.executor.run(
|
|
3964
|
+
rewrite(
|
|
3965
|
+
`INSERT INTO conversation_entries
|
|
3966
|
+
(seq, id, agent_id, tenant_id, conversation_id, type, payload, created_at)
|
|
3967
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
3968
|
+
this.dialect
|
|
3969
|
+
),
|
|
3970
|
+
[
|
|
3971
|
+
seq,
|
|
3972
|
+
entry.id,
|
|
3973
|
+
agentId,
|
|
3974
|
+
tid,
|
|
3975
|
+
conversationId,
|
|
3976
|
+
entry.type,
|
|
3977
|
+
payload,
|
|
3978
|
+
new Date(createdAt).toISOString()
|
|
3979
|
+
]
|
|
3980
|
+
);
|
|
3981
|
+
stored.push({ ...entry, seq, createdAt });
|
|
3982
|
+
}
|
|
3983
|
+
});
|
|
3984
|
+
return stored;
|
|
3985
|
+
} catch (err) {
|
|
3986
|
+
lastErr = err;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
throw lastErr;
|
|
3990
|
+
},
|
|
3991
|
+
readEntries: async (conversationId, opts) => {
|
|
3992
|
+
const params = [conversationId];
|
|
3993
|
+
let sql = "SELECT seq, id, payload, created_at FROM conversation_entries WHERE conversation_id = $1";
|
|
3994
|
+
if (opts?.types && opts.types.length > 0) {
|
|
3995
|
+
const placeholders = opts.types.map(
|
|
3996
|
+
(_t, i) => `$${params.length + 1 + i}`
|
|
3997
|
+
);
|
|
3998
|
+
sql += ` AND type IN (${placeholders.join(", ")})`;
|
|
3999
|
+
params.push(...opts.types);
|
|
4000
|
+
}
|
|
4001
|
+
if (typeof opts?.afterSeq === "number") {
|
|
4002
|
+
sql += ` AND seq > $${params.length + 1}`;
|
|
4003
|
+
params.push(opts.afterSeq);
|
|
4004
|
+
}
|
|
4005
|
+
sql += " ORDER BY seq ASC";
|
|
4006
|
+
if (typeof opts?.limit === "number") {
|
|
4007
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
4008
|
+
params.push(opts.limit);
|
|
4009
|
+
}
|
|
4010
|
+
const rows = await this.executor.all(rewrite(sql, this.dialect), params);
|
|
4011
|
+
return rows.map((r) => {
|
|
4012
|
+
const parsed = typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload;
|
|
4013
|
+
return {
|
|
4014
|
+
...parsed,
|
|
4015
|
+
id: r.id,
|
|
4016
|
+
seq: Number(r.seq),
|
|
4017
|
+
createdAt: new Date(r.created_at).getTime()
|
|
4018
|
+
};
|
|
4019
|
+
});
|
|
3790
4020
|
}
|
|
3791
4021
|
};
|
|
3792
4022
|
// -----------------------------------------------------------------------
|
|
@@ -4619,7 +4849,9 @@ function createConversationStoreFromEngine(engine) {
|
|
|
4619
4849
|
delete: (conversationId) => engine.conversations.delete(conversationId),
|
|
4620
4850
|
appendSubagentResult: (conversationId, result) => engine.conversations.appendSubagentResult(conversationId, result),
|
|
4621
4851
|
clearCallbackLock: (conversationId) => engine.conversations.clearCallbackLock(conversationId),
|
|
4622
|
-
listThreads: (parentConversationId) => engine.conversations.listThreads(parentConversationId)
|
|
4852
|
+
listThreads: (parentConversationId) => engine.conversations.listThreads(parentConversationId),
|
|
4853
|
+
appendEntries: (conversationId, agentId, tenantId, entries) => engine.conversations.appendEntries(conversationId, agentId, tenantId, entries),
|
|
4854
|
+
readEntries: (conversationId, opts) => engine.conversations.readEntries(conversationId, opts)
|
|
4623
4855
|
};
|
|
4624
4856
|
}
|
|
4625
4857
|
function createMemoryStoreFromEngine(engine, tenantId) {
|
|
@@ -11880,6 +12112,7 @@ var InMemoryStateStore = class {
|
|
|
11880
12112
|
};
|
|
11881
12113
|
var InMemoryConversationStore = class {
|
|
11882
12114
|
conversations = /* @__PURE__ */ new Map();
|
|
12115
|
+
entries = /* @__PURE__ */ new Map();
|
|
11883
12116
|
ttlMs;
|
|
11884
12117
|
constructor(ttlSeconds) {
|
|
11885
12118
|
this.ttlMs = typeof ttlSeconds === "number" ? ttlSeconds * 1e3 : void 0;
|
|
@@ -12009,6 +12242,30 @@ var InMemoryConversationStore = class {
|
|
|
12009
12242
|
channelMeta: c.channelMeta
|
|
12010
12243
|
}));
|
|
12011
12244
|
}
|
|
12245
|
+
async appendEntries(conversationId, _agentId, _tenantId, entries) {
|
|
12246
|
+
const list = this.entries.get(conversationId) ?? [];
|
|
12247
|
+
let nextSeq = list.reduce((max, e) => e.seq > max ? e.seq : max, 0) + 1;
|
|
12248
|
+
const now2 = Date.now();
|
|
12249
|
+
const stored = entries.map(
|
|
12250
|
+
(e) => ({ ...e, seq: nextSeq++, createdAt: now2 })
|
|
12251
|
+
);
|
|
12252
|
+
this.entries.set(conversationId, [...list, ...stored]);
|
|
12253
|
+
return stored;
|
|
12254
|
+
}
|
|
12255
|
+
async readEntries(conversationId, opts) {
|
|
12256
|
+
let list = (this.entries.get(conversationId) ?? []).slice().sort((a, b) => a.seq - b.seq);
|
|
12257
|
+
if (opts?.types && opts.types.length > 0) {
|
|
12258
|
+
const allowed = new Set(opts.types);
|
|
12259
|
+
list = list.filter((e) => allowed.has(e.type));
|
|
12260
|
+
}
|
|
12261
|
+
if (typeof opts?.afterSeq === "number") {
|
|
12262
|
+
list = list.filter((e) => e.seq > opts.afterSeq);
|
|
12263
|
+
}
|
|
12264
|
+
if (typeof opts?.limit === "number") {
|
|
12265
|
+
list = list.slice(0, opts.limit);
|
|
12266
|
+
}
|
|
12267
|
+
return list;
|
|
12268
|
+
}
|
|
12012
12269
|
};
|
|
12013
12270
|
var createStateStore = (config, _options) => {
|
|
12014
12271
|
const ttl = config?.ttl;
|
|
@@ -12019,6 +12276,66 @@ var createConversationStore = (config, _options) => {
|
|
|
12019
12276
|
return new InMemoryConversationStore(ttl);
|
|
12020
12277
|
};
|
|
12021
12278
|
|
|
12279
|
+
// src/storage/entries.ts
|
|
12280
|
+
function buildLlmContext(entries) {
|
|
12281
|
+
let latestCompaction;
|
|
12282
|
+
for (const e of entries) {
|
|
12283
|
+
if (e.type === "compaction" && (!latestCompaction || e.seq > latestCompaction.seq)) {
|
|
12284
|
+
latestCompaction = e;
|
|
12285
|
+
}
|
|
12286
|
+
}
|
|
12287
|
+
const harnessMsgs = entries.filter(
|
|
12288
|
+
(e) => e.type === "harness_message"
|
|
12289
|
+
);
|
|
12290
|
+
if (latestCompaction) {
|
|
12291
|
+
const kept = harnessMsgs.filter((e) => e.seq >= latestCompaction.firstKeptSeq).map((e) => e.message);
|
|
12292
|
+
return [latestCompaction.summaryMessage, ...kept];
|
|
12293
|
+
}
|
|
12294
|
+
return harnessMsgs.map((e) => e.message);
|
|
12295
|
+
}
|
|
12296
|
+
function buildDisplaySnapshot(entries, tailN) {
|
|
12297
|
+
const amendmentsByTarget = /* @__PURE__ */ new Map();
|
|
12298
|
+
for (const e of entries) {
|
|
12299
|
+
if (e.type === "assistant_amendment") {
|
|
12300
|
+
const list = amendmentsByTarget.get(e.targetEntryId) ?? [];
|
|
12301
|
+
list.push(e);
|
|
12302
|
+
amendmentsByTarget.set(e.targetEntryId, list);
|
|
12303
|
+
}
|
|
12304
|
+
}
|
|
12305
|
+
const built = [];
|
|
12306
|
+
for (const e of entries) {
|
|
12307
|
+
if (e.type === "user_message") {
|
|
12308
|
+
if (e.hidden) continue;
|
|
12309
|
+
built.push({ seq: e.seq, message: e.message });
|
|
12310
|
+
} else if (e.type === "assistant_message") {
|
|
12311
|
+
let content = typeof e.message.content === "string" ? e.message.content : "";
|
|
12312
|
+
const amendments = amendmentsByTarget.get(e.id);
|
|
12313
|
+
if (amendments) {
|
|
12314
|
+
for (const a of amendments.sort((x, y) => x.seq - y.seq)) {
|
|
12315
|
+
if (a.appendText) content += a.appendText;
|
|
12316
|
+
}
|
|
12317
|
+
}
|
|
12318
|
+
built.push({ seq: e.seq, message: { ...e.message, content } });
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
const total = built.length;
|
|
12322
|
+
const tail = tailN >= total ? built : built.slice(total - tailN);
|
|
12323
|
+
return {
|
|
12324
|
+
messages: tail.map((b) => b.message),
|
|
12325
|
+
totalMessages: total,
|
|
12326
|
+
headSeq: tail.length > 0 ? tail[0].seq : null
|
|
12327
|
+
};
|
|
12328
|
+
}
|
|
12329
|
+
function getPendingSubagentResults(entries) {
|
|
12330
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
12331
|
+
for (const e of entries) {
|
|
12332
|
+
if (e.type === "callback_started") {
|
|
12333
|
+
for (const s of e.consumedSeqs) consumed.add(s);
|
|
12334
|
+
}
|
|
12335
|
+
}
|
|
12336
|
+
return entries.filter((e) => e.type === "subagent_result").filter((e) => !consumed.has(e.seq)).map((e) => e.result);
|
|
12337
|
+
}
|
|
12338
|
+
|
|
12022
12339
|
// src/tenant-token.ts
|
|
12023
12340
|
import { jwtVerify } from "jose";
|
|
12024
12341
|
async function verifyTenantToken(signingKey, token) {
|
|
@@ -12351,9 +12668,143 @@ var CALLBACK_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
|
12351
12668
|
var STALE_SUBAGENT_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
12352
12669
|
|
|
12353
12670
|
// src/orchestrator/orchestrator.ts
|
|
12671
|
+
import { createLogger as createLogger7, getTextContent as getTextContent4 } from "@poncho-ai/sdk";
|
|
12672
|
+
|
|
12673
|
+
// src/orchestrator/entries-dual-write.ts
|
|
12674
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
12354
12675
|
import { getTextContent as getTextContent3 } from "@poncho-ai/sdk";
|
|
12676
|
+
var entriesParityEnabled = () => process.env.PONCHO_VERIFY_ENTRIES === "1";
|
|
12677
|
+
var appendEntriesSafe = async (store, conversation, entries, log2) => {
|
|
12678
|
+
if (entries.length === 0) return [];
|
|
12679
|
+
try {
|
|
12680
|
+
const withIds = entries.map(
|
|
12681
|
+
(e) => ({ id: randomUUID6(), ...e })
|
|
12682
|
+
);
|
|
12683
|
+
return await store.appendEntries(
|
|
12684
|
+
conversation.conversationId,
|
|
12685
|
+
conversation.ownerId,
|
|
12686
|
+
conversation.tenantId ?? null,
|
|
12687
|
+
withIds
|
|
12688
|
+
);
|
|
12689
|
+
} catch (err) {
|
|
12690
|
+
log2.error(
|
|
12691
|
+
`[entries-dual-write] append failed for ${conversation.conversationId}: ${err instanceof Error ? err.message : String(err)}`
|
|
12692
|
+
);
|
|
12693
|
+
return [];
|
|
12694
|
+
}
|
|
12695
|
+
};
|
|
12696
|
+
var userMessageEntry = (message, turnId, opts) => ({
|
|
12697
|
+
type: "user_message",
|
|
12698
|
+
message,
|
|
12699
|
+
turnId,
|
|
12700
|
+
...opts?.hidden ? { hidden: true } : {}
|
|
12701
|
+
});
|
|
12702
|
+
var assistantMessageEntry = (message, turnId, runId) => ({
|
|
12703
|
+
type: "assistant_message",
|
|
12704
|
+
message,
|
|
12705
|
+
turnId,
|
|
12706
|
+
runId
|
|
12707
|
+
});
|
|
12708
|
+
var harnessMessageEntries = (messages, turnId) => messages.map((message) => ({ type: "harness_message", message, turnId }));
|
|
12709
|
+
var compactionEntry = (summaryMessage, firstKeptSeq, opts) => ({
|
|
12710
|
+
type: "compaction",
|
|
12711
|
+
summaryMessage,
|
|
12712
|
+
firstKeptSeq,
|
|
12713
|
+
...opts?.tokensBefore !== void 0 ? { tokensBefore: opts.tokensBefore } : {},
|
|
12714
|
+
...opts?.tokensAfter !== void 0 ? { tokensAfter: opts.tokensAfter } : {}
|
|
12715
|
+
});
|
|
12716
|
+
var subagentResultEntry = (result) => ({ type: "subagent_result", result });
|
|
12717
|
+
var callbackStartedEntry = (consumedSeqs) => ({
|
|
12718
|
+
type: "callback_started",
|
|
12719
|
+
consumedSeqs
|
|
12720
|
+
});
|
|
12721
|
+
var assistantAmendmentEntry = (targetEntryId, appendText) => ({
|
|
12722
|
+
type: "assistant_amendment",
|
|
12723
|
+
targetEntryId,
|
|
12724
|
+
...appendText ? { appendText } : {}
|
|
12725
|
+
});
|
|
12726
|
+
var newHarnessMessagesThisTurn = (prev, next) => {
|
|
12727
|
+
const prevArr = prev ?? [];
|
|
12728
|
+
const nextArr = next ?? [];
|
|
12729
|
+
if (nextArr.length === 0) return { messages: [], approximate: false };
|
|
12730
|
+
if (prevArr.length === 0) return { messages: nextArr, approximate: false };
|
|
12731
|
+
if (nextArr.length >= prevArr.length) {
|
|
12732
|
+
return { messages: nextArr.slice(prevArr.length), approximate: false };
|
|
12733
|
+
}
|
|
12734
|
+
return { messages: nextArr, approximate: true };
|
|
12735
|
+
};
|
|
12736
|
+
var projectText = (m) => {
|
|
12737
|
+
const role = m.role;
|
|
12738
|
+
const text = getTextContent3(m).replace(/\s+/g, " ").trim();
|
|
12739
|
+
return `${role}:${text}`;
|
|
12740
|
+
};
|
|
12741
|
+
var projectAll = (msgs) => msgs.map(projectText);
|
|
12742
|
+
var countMismatch = (label, a, b) => a === b ? null : `${label} length ${a} (entries) vs ${b} (blob)`;
|
|
12743
|
+
var verifyEntriesParity = async (store, conversationId, blob, log2) => {
|
|
12744
|
+
if (!entriesParityEnabled()) return;
|
|
12745
|
+
try {
|
|
12746
|
+
const entries = await store.readEntries(conversationId);
|
|
12747
|
+
const mismatches = [];
|
|
12748
|
+
if (blob.harnessMessages) {
|
|
12749
|
+
const llm = buildLlmContext(entries);
|
|
12750
|
+
const lenMismatch = countMismatch(
|
|
12751
|
+
"llmContext",
|
|
12752
|
+
llm.length,
|
|
12753
|
+
blob.harnessMessages.length
|
|
12754
|
+
);
|
|
12755
|
+
if (lenMismatch) mismatches.push(lenMismatch);
|
|
12756
|
+
const entriesProj = projectAll(llm);
|
|
12757
|
+
const blobProj = projectAll(blob.harnessMessages);
|
|
12758
|
+
const tail = Math.min(entriesProj.length, blobProj.length, 5);
|
|
12759
|
+
for (let i = 1; i <= tail; i++) {
|
|
12760
|
+
const ep = entriesProj[entriesProj.length - i];
|
|
12761
|
+
const bp = blobProj[blobProj.length - i];
|
|
12762
|
+
if (ep !== bp) {
|
|
12763
|
+
mismatches.push(
|
|
12764
|
+
`llmContext tail[-${i}] differs: entries=${JSON.stringify(ep).slice(0, 120)} blob=${JSON.stringify(bp).slice(0, 120)}`
|
|
12765
|
+
);
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12768
|
+
}
|
|
12769
|
+
if (blob.displayMessages) {
|
|
12770
|
+
const snap = buildDisplaySnapshot(entries, Number.MAX_SAFE_INTEGER);
|
|
12771
|
+
const lenMismatch = countMismatch(
|
|
12772
|
+
"display",
|
|
12773
|
+
snap.totalMessages,
|
|
12774
|
+
blob.displayMessages.length
|
|
12775
|
+
);
|
|
12776
|
+
if (lenMismatch) mismatches.push(lenMismatch);
|
|
12777
|
+
const entriesProj = projectAll(snap.messages);
|
|
12778
|
+
const blobProj = projectAll(blob.displayMessages);
|
|
12779
|
+
const tail = Math.min(entriesProj.length, blobProj.length, 5);
|
|
12780
|
+
for (let i = 1; i <= tail; i++) {
|
|
12781
|
+
const ep = entriesProj[entriesProj.length - i];
|
|
12782
|
+
const bp = blobProj[blobProj.length - i];
|
|
12783
|
+
if (ep !== bp) {
|
|
12784
|
+
mismatches.push(
|
|
12785
|
+
`display tail[-${i}] differs: entries=${JSON.stringify(ep).slice(0, 120)} blob=${JSON.stringify(bp).slice(0, 120)}`
|
|
12786
|
+
);
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
if (mismatches.length > 0) {
|
|
12791
|
+
log2.warn(
|
|
12792
|
+
`[entries-parity] ${conversationId} MISMATCH (${mismatches.length}): ${mismatches.join(" | ")}`
|
|
12793
|
+
);
|
|
12794
|
+
} else {
|
|
12795
|
+
log2.info(`[entries-parity] ${conversationId} OK`);
|
|
12796
|
+
}
|
|
12797
|
+
} catch (err) {
|
|
12798
|
+
log2.error(
|
|
12799
|
+
`[entries-parity] ${conversationId} checker threw (ignored): ${err instanceof Error ? err.message : String(err)}`
|
|
12800
|
+
);
|
|
12801
|
+
}
|
|
12802
|
+
};
|
|
12803
|
+
|
|
12804
|
+
// src/orchestrator/orchestrator.ts
|
|
12805
|
+
var dualWriteLog = createLogger7("orchestrator:entries");
|
|
12355
12806
|
var assistantMessageText = (message) => {
|
|
12356
|
-
const raw =
|
|
12807
|
+
const raw = getTextContent4(message).trim();
|
|
12357
12808
|
if (raw.startsWith("{") && raw.includes('"tool_calls"')) {
|
|
12358
12809
|
try {
|
|
12359
12810
|
const parsed = JSON.parse(raw);
|
|
@@ -12662,6 +13113,8 @@ var AgentOrchestrator = class {
|
|
|
12662
13113
|
if (!checkpointedRun) {
|
|
12663
13114
|
const conv = await this.conversationStore.get(conversationId);
|
|
12664
13115
|
if (conv) {
|
|
13116
|
+
let amendmentText;
|
|
13117
|
+
let pushedAssistant;
|
|
12665
13118
|
const hasAssistantContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0;
|
|
12666
13119
|
if (hasAssistantContent) {
|
|
12667
13120
|
const prevMessages = conv.messages;
|
|
@@ -12689,15 +13142,14 @@ var AgentOrchestrator = class {
|
|
|
12689
13142
|
}
|
|
12690
13143
|
}
|
|
12691
13144
|
];
|
|
13145
|
+
amendmentText = draft.assistantResponse;
|
|
12692
13146
|
} else {
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
}
|
|
12700
|
-
];
|
|
13147
|
+
pushedAssistant = {
|
|
13148
|
+
role: "assistant",
|
|
13149
|
+
content: draft.assistantResponse,
|
|
13150
|
+
metadata: buildAssistantMetadata(draft)
|
|
13151
|
+
};
|
|
13152
|
+
conv.messages = [...prevMessages, pushedAssistant];
|
|
12701
13153
|
}
|
|
12702
13154
|
}
|
|
12703
13155
|
applyTurnMetadata(conv, {
|
|
@@ -12707,6 +13159,54 @@ var AgentOrchestrator = class {
|
|
|
12707
13159
|
harnessMessages: execution?.runHarnessMessages
|
|
12708
13160
|
}, { shouldRebuildCanonical: true });
|
|
12709
13161
|
await this.conversationStore.update(conv);
|
|
13162
|
+
if (amendmentText !== void 0 || pushedAssistant) {
|
|
13163
|
+
const finalConv = conv;
|
|
13164
|
+
const amendText = amendmentText;
|
|
13165
|
+
const pushed = pushedAssistant;
|
|
13166
|
+
void (async () => {
|
|
13167
|
+
try {
|
|
13168
|
+
if (pushed) {
|
|
13169
|
+
await appendEntriesSafe(
|
|
13170
|
+
this.conversationStore,
|
|
13171
|
+
finalConv,
|
|
13172
|
+
[assistantMessageEntry(pushed, `resume-${conversationId}`, latestRunId)],
|
|
13173
|
+
dualWriteLog
|
|
13174
|
+
);
|
|
13175
|
+
} else if (amendText !== void 0) {
|
|
13176
|
+
const existing = await this.conversationStore.readEntries(
|
|
13177
|
+
conversationId,
|
|
13178
|
+
{ types: ["assistant_message"] }
|
|
13179
|
+
);
|
|
13180
|
+
const target = existing[existing.length - 1];
|
|
13181
|
+
if (target) {
|
|
13182
|
+
await appendEntriesSafe(
|
|
13183
|
+
this.conversationStore,
|
|
13184
|
+
finalConv,
|
|
13185
|
+
[assistantAmendmentEntry(target.id, amendText)],
|
|
13186
|
+
dualWriteLog
|
|
13187
|
+
);
|
|
13188
|
+
} else {
|
|
13189
|
+
dualWriteLog.warn(
|
|
13190
|
+
`[entries-dual-write] resume amendment for ${conversationId}: no assistant_message entry to target; skipped`
|
|
13191
|
+
);
|
|
13192
|
+
}
|
|
13193
|
+
}
|
|
13194
|
+
await verifyEntriesParity(
|
|
13195
|
+
this.conversationStore,
|
|
13196
|
+
conversationId,
|
|
13197
|
+
{
|
|
13198
|
+
harnessMessages: finalConv._harnessMessages,
|
|
13199
|
+
displayMessages: finalConv.messages
|
|
13200
|
+
},
|
|
13201
|
+
dualWriteLog
|
|
13202
|
+
);
|
|
13203
|
+
} catch (err) {
|
|
13204
|
+
dualWriteLog.error(
|
|
13205
|
+
`[entries-dual-write] resume finalize append failed for ${conversationId}: ${err instanceof Error ? err.message : String(err)}`
|
|
13206
|
+
);
|
|
13207
|
+
}
|
|
13208
|
+
})();
|
|
13209
|
+
}
|
|
12710
13210
|
}
|
|
12711
13211
|
} else {
|
|
12712
13212
|
const conv = await this.conversationStore.get(conversationId);
|
|
@@ -13217,19 +13717,22 @@ var AgentOrchestrator = class {
|
|
|
13217
13717
|
conversation.runStatus = "running";
|
|
13218
13718
|
const callbackCount = (conversation.subagentCallbackCount ?? 0) + 1;
|
|
13219
13719
|
conversation.subagentCallbackCount = callbackCount;
|
|
13720
|
+
const injectedCallbackMessages = [];
|
|
13220
13721
|
for (const pr of pendingResults) {
|
|
13221
13722
|
const responseText = (pr.result?.response ?? "").trim();
|
|
13222
13723
|
const responseLine = responseText || `(subagent produced no final summary after ${pr.result?.steps ?? 0} step(s); its work may be incomplete. Call read_subagent with subagent_id "${pr.subagentId}" and mode "assistant" to retrieve what it did.)`;
|
|
13223
13724
|
const resultBody = pr.result ? `Status: ${pr.result.status}
|
|
13224
13725
|
Response: ${responseLine}
|
|
13225
13726
|
Steps: ${pr.result.steps}, Duration: ${pr.result.duration}ms` : pr.error ? `Error: ${pr.error.message}` : "(no result)";
|
|
13226
|
-
|
|
13727
|
+
const injected = {
|
|
13227
13728
|
role: "user",
|
|
13228
13729
|
content: `[Subagent Result] Subagent "${pr.task}" (${pr.subagentId}) ${pr.status}:
|
|
13229
13730
|
|
|
13230
13731
|
${resultBody}`,
|
|
13231
13732
|
metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp }
|
|
13232
|
-
}
|
|
13733
|
+
};
|
|
13734
|
+
injectedCallbackMessages.push(injected);
|
|
13735
|
+
conversation.messages.push(injected);
|
|
13233
13736
|
}
|
|
13234
13737
|
const processedIds = new Set(pendingResults.map((pr) => pr.subagentId));
|
|
13235
13738
|
const freshForPending = await this.conversationStore.get(conversationId);
|
|
@@ -13238,6 +13741,36 @@ ${resultBody}`,
|
|
|
13238
13741
|
conversation._harnessMessages = [...conversation.messages];
|
|
13239
13742
|
conversation.updatedAt = Date.now();
|
|
13240
13743
|
await this.conversationStore.update(conversation);
|
|
13744
|
+
if (pendingResults.length > 0) {
|
|
13745
|
+
const turnId = `callback-${callbackCount}-${conversation.conversationId}`;
|
|
13746
|
+
void (async () => {
|
|
13747
|
+
try {
|
|
13748
|
+
const resultEntries = await this.conversationStore.readEntries(
|
|
13749
|
+
conversation.conversationId,
|
|
13750
|
+
{ types: ["subagent_result"] }
|
|
13751
|
+
);
|
|
13752
|
+
const consumedIds = new Set(pendingResults.map((pr) => pr.subagentId));
|
|
13753
|
+
const consumedSeqs = resultEntries.filter(
|
|
13754
|
+
(e) => e.type === "subagent_result" && consumedIds.has(e.result.subagentId)
|
|
13755
|
+
).map((e) => e.seq);
|
|
13756
|
+
await appendEntriesSafe(
|
|
13757
|
+
this.conversationStore,
|
|
13758
|
+
conversation,
|
|
13759
|
+
[
|
|
13760
|
+
callbackStartedEntry(consumedSeqs),
|
|
13761
|
+
...injectedCallbackMessages.map(
|
|
13762
|
+
(m) => userMessageEntry(m, turnId, { hidden: true })
|
|
13763
|
+
)
|
|
13764
|
+
],
|
|
13765
|
+
dualWriteLog
|
|
13766
|
+
);
|
|
13767
|
+
} catch (err) {
|
|
13768
|
+
dualWriteLog.error(
|
|
13769
|
+
`[entries-dual-write] callback_started append failed for ${conversation.conversationId}: ${err instanceof Error ? err.message : String(err)}`
|
|
13770
|
+
);
|
|
13771
|
+
}
|
|
13772
|
+
})();
|
|
13773
|
+
}
|
|
13241
13774
|
if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
|
|
13242
13775
|
console.warn(`[poncho][subagent-callback] Circuit breaker: ${callbackCount} callbacks for ${conversationId}, skipping re-run`);
|
|
13243
13776
|
conversation.runningCallbackSince = void 0;
|
|
@@ -13299,12 +13832,14 @@ ${resultBody}`,
|
|
|
13299
13832
|
if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
|
|
13300
13833
|
const freshConv = await this.conversationStore.get(conversationId);
|
|
13301
13834
|
if (freshConv) {
|
|
13835
|
+
let callbackAssistantMsg;
|
|
13302
13836
|
if (!callbackNeedsContinuation) {
|
|
13303
|
-
|
|
13837
|
+
callbackAssistantMsg = {
|
|
13304
13838
|
role: "assistant",
|
|
13305
13839
|
content: execution.draft.assistantResponse,
|
|
13306
13840
|
metadata: buildAssistantMetadata(execution.draft)
|
|
13307
|
-
}
|
|
13841
|
+
};
|
|
13842
|
+
freshConv.messages.push(callbackAssistantMsg);
|
|
13308
13843
|
}
|
|
13309
13844
|
applyTurnMetadata(freshConv, {
|
|
13310
13845
|
latestRunId: execution.latestRunId,
|
|
@@ -13317,6 +13852,25 @@ ${resultBody}`,
|
|
|
13317
13852
|
}, { shouldRebuildCanonical: true, clearApprovals: false });
|
|
13318
13853
|
freshConv.runningCallbackSince = void 0;
|
|
13319
13854
|
await this.conversationStore.update(freshConv);
|
|
13855
|
+
if (callbackAssistantMsg) {
|
|
13856
|
+
const finalMsg = callbackAssistantMsg;
|
|
13857
|
+
void appendEntriesSafe(
|
|
13858
|
+
this.conversationStore,
|
|
13859
|
+
freshConv,
|
|
13860
|
+
[assistantMessageEntry(finalMsg, `callback-${conversationId}`, execution.latestRunId)],
|
|
13861
|
+
dualWriteLog
|
|
13862
|
+
).then(
|
|
13863
|
+
() => verifyEntriesParity(
|
|
13864
|
+
this.conversationStore,
|
|
13865
|
+
conversationId,
|
|
13866
|
+
{
|
|
13867
|
+
harnessMessages: freshConv._harnessMessages,
|
|
13868
|
+
displayMessages: freshConv.messages
|
|
13869
|
+
},
|
|
13870
|
+
dualWriteLog
|
|
13871
|
+
)
|
|
13872
|
+
);
|
|
13873
|
+
}
|
|
13320
13874
|
if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
|
|
13321
13875
|
this.hooks?.onMessagingNotify?.(conversationId, execution.draft.assistantResponse);
|
|
13322
13876
|
}
|
|
@@ -13721,6 +14275,22 @@ ${resultBody}`,
|
|
|
13721
14275
|
* old `.catch(() => {})` call sites did. Returns whether the result landed.
|
|
13722
14276
|
*/
|
|
13723
14277
|
async appendSubagentResultReliable(parentConversationId, result) {
|
|
14278
|
+
void (async () => {
|
|
14279
|
+
try {
|
|
14280
|
+
const parent = await this.conversationStore.get(parentConversationId);
|
|
14281
|
+
if (!parent) return;
|
|
14282
|
+
await appendEntriesSafe(
|
|
14283
|
+
this.conversationStore,
|
|
14284
|
+
parent,
|
|
14285
|
+
[subagentResultEntry(result)],
|
|
14286
|
+
dualWriteLog
|
|
14287
|
+
);
|
|
14288
|
+
} catch (err) {
|
|
14289
|
+
dualWriteLog.error(
|
|
14290
|
+
`[entries-dual-write] subagent_result append failed for ${parentConversationId}: ${err instanceof Error ? err.message : String(err)}`
|
|
14291
|
+
);
|
|
14292
|
+
}
|
|
14293
|
+
})();
|
|
13724
14294
|
try {
|
|
13725
14295
|
await this.conversationStore.appendSubagentResult(parentConversationId, result);
|
|
13726
14296
|
return true;
|
|
@@ -13822,9 +14392,9 @@ ${resultBody}`,
|
|
|
13822
14392
|
};
|
|
13823
14393
|
|
|
13824
14394
|
// src/orchestrator/run-conversation-turn.ts
|
|
13825
|
-
import { randomUUID as
|
|
13826
|
-
import { createLogger as
|
|
13827
|
-
var log =
|
|
14395
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
14396
|
+
import { createLogger as createLogger8 } from "@poncho-ai/sdk";
|
|
14397
|
+
var log = createLogger8("orchestrator");
|
|
13828
14398
|
var runConversationTurn = async (opts) => {
|
|
13829
14399
|
const conversation = await opts.conversationStore.getWithArchive(opts.conversationId);
|
|
13830
14400
|
if (!conversation) {
|
|
@@ -13862,9 +14432,9 @@ var runConversationTurn = async (opts) => {
|
|
|
13862
14432
|
const userMessage = {
|
|
13863
14433
|
role: "user",
|
|
13864
14434
|
content: userContent,
|
|
13865
|
-
metadata: { id:
|
|
14435
|
+
metadata: { id: randomUUID7(), timestamp: turnTimestamp }
|
|
13866
14436
|
};
|
|
13867
|
-
const assistantId =
|
|
14437
|
+
const assistantId = randomUUID7();
|
|
13868
14438
|
const draft = createTurnDraftState();
|
|
13869
14439
|
let latestRunId = conversation.runtimeRunId ?? "";
|
|
13870
14440
|
let runCancelled = false;
|
|
@@ -13903,6 +14473,8 @@ var runConversationTurn = async (opts) => {
|
|
|
13903
14473
|
conversation.updatedAt = Date.now();
|
|
13904
14474
|
await opts.conversationStore.update(conversation);
|
|
13905
14475
|
};
|
|
14476
|
+
const preTurnHarnessMessages = conversation._harnessMessages ? [...conversation._harnessMessages] : void 0;
|
|
14477
|
+
const turnId = assistantId;
|
|
13906
14478
|
conversation.messages = [...historyMessages, userMessage];
|
|
13907
14479
|
conversation.subagentCallbackCount = 0;
|
|
13908
14480
|
conversation._continuationCount = void 0;
|
|
@@ -13912,6 +14484,12 @@ var runConversationTurn = async (opts) => {
|
|
|
13912
14484
|
`failed to persist user turn: ${err instanceof Error ? err.message : String(err)}`
|
|
13913
14485
|
);
|
|
13914
14486
|
});
|
|
14487
|
+
void appendEntriesSafe(
|
|
14488
|
+
opts.conversationStore,
|
|
14489
|
+
conversation,
|
|
14490
|
+
[userMessageEntry(userMessage, turnId)],
|
|
14491
|
+
log
|
|
14492
|
+
);
|
|
13915
14493
|
try {
|
|
13916
14494
|
const execution = await executeConversationTurn({
|
|
13917
14495
|
harness: opts.harness,
|
|
@@ -13959,6 +14537,34 @@ var runConversationTurn = async (opts) => {
|
|
|
13959
14537
|
...existingHistory,
|
|
13960
14538
|
...preRunMessages.slice(0, removedCount)
|
|
13961
14539
|
];
|
|
14540
|
+
const summaryMessage = event.compactedMessages[0];
|
|
14541
|
+
const keptCount = Math.max(0, event.compactedMessages.length - 1);
|
|
14542
|
+
if (summaryMessage) {
|
|
14543
|
+
void (async () => {
|
|
14544
|
+
try {
|
|
14545
|
+
const existing = await opts.conversationStore.readEntries(
|
|
14546
|
+
opts.conversationId,
|
|
14547
|
+
{ types: ["harness_message"] }
|
|
14548
|
+
);
|
|
14549
|
+
const harnessSeqs = existing.map((e) => e.seq);
|
|
14550
|
+
const firstKeptSeq = harnessSeqs.length >= keptCount && keptCount > 0 ? harnessSeqs[harnessSeqs.length - keptCount] : (harnessSeqs[harnessSeqs.length - 1] ?? 0) + 1;
|
|
14551
|
+
await appendEntriesSafe(
|
|
14552
|
+
opts.conversationStore,
|
|
14553
|
+
conversation,
|
|
14554
|
+
[
|
|
14555
|
+
compactionEntry(summaryMessage, firstKeptSeq, {
|
|
14556
|
+
tokensBefore: conversation.contextTokens
|
|
14557
|
+
})
|
|
14558
|
+
],
|
|
14559
|
+
log
|
|
14560
|
+
);
|
|
14561
|
+
} catch (err) {
|
|
14562
|
+
log.error(
|
|
14563
|
+
`[entries-dual-write] compaction append failed: ${err instanceof Error ? err.message : String(err)}`
|
|
14564
|
+
);
|
|
14565
|
+
}
|
|
14566
|
+
})();
|
|
14567
|
+
}
|
|
13962
14568
|
}
|
|
13963
14569
|
}
|
|
13964
14570
|
if (event.type === "step:completed") {
|
|
@@ -14092,6 +14698,36 @@ var runConversationTurn = async (opts) => {
|
|
|
14092
14698
|
{ shouldRebuildCanonical }
|
|
14093
14699
|
);
|
|
14094
14700
|
await opts.conversationStore.update(conversation);
|
|
14701
|
+
const finalAssistant = conversation.messages[conversation.messages.length - 1];
|
|
14702
|
+
const { messages: newHarness, approximate } = newHarnessMessagesThisTurn(
|
|
14703
|
+
preTurnHarnessMessages,
|
|
14704
|
+
conversation._harnessMessages
|
|
14705
|
+
);
|
|
14706
|
+
if (approximate) {
|
|
14707
|
+
log.warn(
|
|
14708
|
+
`[entries-dual-write] ${opts.conversationId} harness-message diff approximate (blob array shrank this turn \u2014 likely compaction); appended full context`
|
|
14709
|
+
);
|
|
14710
|
+
}
|
|
14711
|
+
const finalizeEntries = [
|
|
14712
|
+
...harnessMessageEntries(newHarness, turnId),
|
|
14713
|
+
...finalAssistant && finalAssistant.role === "assistant" ? [assistantMessageEntry(finalAssistant, turnId, latestRunId)] : []
|
|
14714
|
+
];
|
|
14715
|
+
void appendEntriesSafe(
|
|
14716
|
+
opts.conversationStore,
|
|
14717
|
+
conversation,
|
|
14718
|
+
finalizeEntries,
|
|
14719
|
+
log
|
|
14720
|
+
).then(
|
|
14721
|
+
() => verifyEntriesParity(
|
|
14722
|
+
opts.conversationStore,
|
|
14723
|
+
opts.conversationId,
|
|
14724
|
+
{
|
|
14725
|
+
harnessMessages: conversation._harnessMessages,
|
|
14726
|
+
displayMessages: conversation.messages
|
|
14727
|
+
},
|
|
14728
|
+
log
|
|
14729
|
+
)
|
|
14730
|
+
);
|
|
14095
14731
|
}
|
|
14096
14732
|
return {
|
|
14097
14733
|
latestRunId,
|
|
@@ -14209,10 +14845,13 @@ export {
|
|
|
14209
14845
|
VFS_SCHEME,
|
|
14210
14846
|
VercelBlobUploadStore,
|
|
14211
14847
|
abnormalEndResponse,
|
|
14848
|
+
appendEntriesSafe,
|
|
14212
14849
|
applyTurnMetadata,
|
|
14213
14850
|
buildAgentDirectoryName,
|
|
14214
14851
|
buildApprovalCheckpoints,
|
|
14215
14852
|
buildAssistantMetadata,
|
|
14853
|
+
buildDisplaySnapshot,
|
|
14854
|
+
buildLlmContext,
|
|
14216
14855
|
buildSkillContextWindow,
|
|
14217
14856
|
buildToolCompletedText,
|
|
14218
14857
|
cloneSections,
|
|
@@ -14249,6 +14888,7 @@ export {
|
|
|
14249
14888
|
deleteOpenAICodexSession,
|
|
14250
14889
|
deriveUploadKey,
|
|
14251
14890
|
ensureAgentIdentity,
|
|
14891
|
+
entriesParityEnabled,
|
|
14252
14892
|
estimateTokens,
|
|
14253
14893
|
estimateTotalTokens,
|
|
14254
14894
|
executeConversationTurn,
|
|
@@ -14260,6 +14900,7 @@ export {
|
|
|
14260
14900
|
getOpenAICodexAccessToken,
|
|
14261
14901
|
getOpenAICodexAuthFilePath,
|
|
14262
14902
|
getOpenAICodexRequiredScopes,
|
|
14903
|
+
getPendingSubagentResults,
|
|
14263
14904
|
getPonchoStoreRoot,
|
|
14264
14905
|
isMessageArray,
|
|
14265
14906
|
jsonSchemaToZod,
|
|
@@ -14273,6 +14914,7 @@ export {
|
|
|
14273
14914
|
loadSkillMetadataFromDirs,
|
|
14274
14915
|
loadVfsSkillMetadata,
|
|
14275
14916
|
mergeSkills,
|
|
14917
|
+
newHarnessMessagesThisTurn,
|
|
14276
14918
|
normalizeApprovalCheckpoint,
|
|
14277
14919
|
normalizeOtlp,
|
|
14278
14920
|
normalizeScriptPolicyPath,
|
|
@@ -14296,6 +14938,7 @@ export {
|
|
|
14296
14938
|
runConversationTurn,
|
|
14297
14939
|
slugifyStorageComponent,
|
|
14298
14940
|
startOpenAICodexDeviceAuth,
|
|
14941
|
+
verifyEntriesParity,
|
|
14299
14942
|
verifyTenantToken,
|
|
14300
14943
|
withToolResultArchiveParam,
|
|
14301
14944
|
writeOpenAICodexSession
|