@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/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 (const msg of messagesToCompact) {
440
+ for (let i = 0; i < messagesToCompact.length; i++) {
441
+ const msg = messagesToCompact[i];
420
442
  const text = getTextContent(msg);
421
- const truncated = text.length > SUMMARIZATION_MESSAGE_TRUNCATION_CHARS ? text.slice(0, SUMMARIZATION_MESSAGE_TRUNCATION_CHARS) + "\n...[truncated]" : text;
422
- conversationLines.push(`[${msg.role}]: ${truncated}`);
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
- const prompt = instructions ? `${SUMMARIZATION_PROMPT}
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}` : SUMMARIZATION_PROMPT;
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 continuationMessage = buildContinuationMessage(summary);
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 = getTextContent3(message).trim();
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
- conv.messages = [
12694
- ...prevMessages,
12695
- {
12696
- role: "assistant",
12697
- content: draft.assistantResponse,
12698
- metadata: buildAssistantMetadata(draft)
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
- conversation.messages.push({
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
- freshConv.messages.push({
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 randomUUID6 } from "crypto";
13826
- import { createLogger as createLogger7 } from "@poncho-ai/sdk";
13827
- var log = createLogger7("orchestrator");
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: randomUUID6(), timestamp: turnTimestamp }
14435
+ metadata: { id: randomUUID7(), timestamp: turnTimestamp }
13866
14436
  };
13867
- const assistantId = randomUUID6();
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