@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.
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ buildLlmContext,
4
+ buildDisplaySnapshot,
5
+ getPendingSubagentResults,
6
+ type ConversationEntry,
7
+ } from "../src/storage/entries.js";
8
+ import type { Message } from "@poncho-ai/sdk";
9
+
10
+ const msg = (role: Message["role"], content: string): Message => ({ role, content });
11
+
12
+ let seq = 0;
13
+ const reset = () => { seq = 0; };
14
+ const next = () => ++seq;
15
+
16
+ const harness = (content: string, turnId = "t1"): ConversationEntry => ({
17
+ type: "harness_message", id: `h${seq + 1}`, seq: next(), createdAt: 0,
18
+ message: msg("assistant", content), turnId,
19
+ });
20
+ const user = (content: string, opts: { hidden?: boolean } = {}): ConversationEntry => ({
21
+ type: "user_message", id: `u${seq + 1}`, seq: next(), createdAt: 0,
22
+ message: msg("user", content), turnId: "t1", hidden: opts.hidden,
23
+ });
24
+ const assistant = (id: string, content: string): ConversationEntry => ({
25
+ type: "assistant_message", id, seq: next(), createdAt: 0,
26
+ message: msg("assistant", content), turnId: "t1", runId: "r1",
27
+ });
28
+
29
+ describe("buildLlmContext", () => {
30
+ it("returns all harness messages in order with no compaction", () => {
31
+ reset();
32
+ const entries = [harness("a"), harness("b"), harness("c")];
33
+ expect(buildLlmContext(entries).map((m) => m.content)).toEqual(["a", "b", "c"]);
34
+ });
35
+
36
+ it("applies a compaction overlay: summary + messages from firstKeptSeq", () => {
37
+ reset();
38
+ const h1 = harness("old1"); // seq 1
39
+ const h2 = harness("old2"); // seq 2
40
+ const h3 = harness("kept3"); // seq 3
41
+ const h4 = harness("kept4"); // seq 4
42
+ const compaction: ConversationEntry = {
43
+ type: "compaction", id: "c1", seq: next(), createdAt: 0,
44
+ summaryMessage: msg("user", "[summary]"), firstKeptSeq: 3,
45
+ };
46
+ const ctx = buildLlmContext([h1, h2, h3, h4, compaction]);
47
+ expect(ctx.map((m) => m.content)).toEqual(["[summary]", "kept3", "kept4"]);
48
+ });
49
+
50
+ it("uses the LATEST compaction when several exist (layered)", () => {
51
+ reset();
52
+ const h1 = harness("a");
53
+ const c1: ConversationEntry = { type: "compaction", id: "c1", seq: next(), createdAt: 0, summaryMessage: msg("user", "[sum1]"), firstKeptSeq: 1 };
54
+ const h2 = harness("b"); // seq 3
55
+ const c2: ConversationEntry = { type: "compaction", id: "c2", seq: next(), createdAt: 0, summaryMessage: msg("user", "[sum2]"), firstKeptSeq: 3 };
56
+ const ctx = buildLlmContext([h1, c1, h2, c2]);
57
+ expect(ctx.map((m) => m.content)).toEqual(["[sum2]", "b"]);
58
+ });
59
+ });
60
+
61
+ describe("buildDisplaySnapshot", () => {
62
+ it("drops hidden user messages and returns the tail", () => {
63
+ reset();
64
+ const entries = [
65
+ user("hidden-framed", { hidden: true }),
66
+ user("hello"),
67
+ assistant("a1", "hi"),
68
+ user("again"),
69
+ assistant("a2", "yo"),
70
+ ];
71
+ const snap = buildDisplaySnapshot(entries, 10);
72
+ expect(snap.messages.map((m) => m.content)).toEqual(["hello", "hi", "again", "yo"]);
73
+ expect(snap.totalMessages).toBe(4);
74
+ });
75
+
76
+ it("folds amendments into their target assistant message", () => {
77
+ reset();
78
+ const a = assistant("a1", "part1");
79
+ const amend: ConversationEntry = {
80
+ type: "assistant_amendment", id: "am1", seq: next(), createdAt: 0,
81
+ targetEntryId: "a1", appendText: " + part2",
82
+ };
83
+ const snap = buildDisplaySnapshot([user("q"), a, amend], 10);
84
+ expect(snap.messages.map((m) => m.content)).toEqual(["q", "part1 + part2"]);
85
+ });
86
+
87
+ it("returns only the trailing tailN messages", () => {
88
+ reset();
89
+ const entries = [user("1"), assistant("a", "2"), user("3"), assistant("b", "4")];
90
+ const snap = buildDisplaySnapshot(entries, 2);
91
+ expect(snap.messages.map((m) => m.content)).toEqual(["3", "4"]);
92
+ expect(snap.totalMessages).toBe(4);
93
+ });
94
+ });
95
+
96
+ describe("getPendingSubagentResults", () => {
97
+ const result = (subagentId: string): ConversationEntry => ({
98
+ type: "subagent_result", id: `sr-${subagentId}`, seq: next(), createdAt: 0,
99
+ result: { subagentId, task: "t", status: "completed", timestamp: 0 },
100
+ });
101
+
102
+ it("returns results not yet consumed by a callback", () => {
103
+ reset();
104
+ const r1 = result("s1"); // seq 1
105
+ const r2 = result("s2"); // seq 2
106
+ const callback: ConversationEntry = {
107
+ type: "callback_started", id: "cb1", seq: next(), createdAt: 0,
108
+ consumedSeqs: [1],
109
+ };
110
+ const r3 = result("s3"); // seq 4
111
+ const pending = getPendingSubagentResults([r1, r2, callback, r3]);
112
+ // s1 consumed; s2 + s3 still pending
113
+ expect(pending.map((p) => p.subagentId)).toEqual(["s2", "s3"]);
114
+ });
115
+
116
+ it("returns empty when all consumed", () => {
117
+ reset();
118
+ const r1 = result("s1");
119
+ const callback: ConversationEntry = {
120
+ type: "callback_started", id: "cb1", seq: next(), createdAt: 0,
121
+ consumedSeqs: [1],
122
+ };
123
+ expect(getPendingSubagentResults([r1, callback])).toEqual([]);
124
+ });
125
+ });