@code-yeongyu/senpi-agent-core 2026.9.30 → 2026.10.1-3

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.
Files changed (64) hide show
  1. package/README.md +49 -20
  2. package/dist/agent-loop.d.ts +27 -3
  3. package/dist/agent-loop.js +192 -56
  4. package/dist/agent.d.ts +21 -7
  5. package/dist/agent.js +38 -18
  6. package/dist/harness/execution/tools.d.ts +1 -1
  7. package/dist/harness/execution/tools.js +0 -2
  8. package/dist/harness/messages.js +1 -0
  9. package/dist/harness/pico3/bash.d.ts +10 -0
  10. package/dist/harness/pico3/bash.js +25 -0
  11. package/dist/harness/pico3/bounded.d.ts +21 -0
  12. package/dist/harness/pico3/bounded.js +95 -0
  13. package/dist/harness/pico3/chord.d.ts +41 -0
  14. package/dist/harness/pico3/chord.js +147 -0
  15. package/dist/harness/pico3/context.d.ts +16 -0
  16. package/dist/harness/pico3/context.js +88 -0
  17. package/dist/harness/pico3/harness.d.ts +198 -0
  18. package/dist/harness/pico3/harness.js +645 -0
  19. package/dist/harness/pico3/hooks.d.ts +10 -0
  20. package/dist/harness/pico3/hooks.js +33 -0
  21. package/dist/harness/pico3/index.d.ts +19 -0
  22. package/dist/harness/pico3/index.js +15 -0
  23. package/dist/harness/pico3/jsonl.d.ts +58 -0
  24. package/dist/harness/pico3/jsonl.js +322 -0
  25. package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
  26. package/dist/harness/pico3/kinds/collapse.js +191 -0
  27. package/dist/harness/pico3/kinds/entries.d.ts +78 -0
  28. package/dist/harness/pico3/kinds/entries.js +13 -0
  29. package/dist/harness/pico3/kinds/frames.d.ts +9 -0
  30. package/dist/harness/pico3/kinds/frames.js +75 -0
  31. package/dist/harness/pico3/kinds/generation.d.ts +94 -0
  32. package/dist/harness/pico3/kinds/generation.js +504 -0
  33. package/dist/harness/pico3/kinds/job.d.ts +44 -0
  34. package/dist/harness/pico3/kinds/job.js +143 -0
  35. package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
  36. package/dist/harness/pico3/kinds/plugin.js +35 -0
  37. package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
  38. package/dist/harness/pico3/kinds/post-tools.js +145 -0
  39. package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
  40. package/dist/harness/pico3/kinds/task-api.js +45 -0
  41. package/dist/harness/pico3/kinds/tool.d.ts +43 -0
  42. package/dist/harness/pico3/kinds/tool.js +373 -0
  43. package/dist/harness/pico3/legacy-tracker.d.ts +11 -0
  44. package/dist/harness/pico3/legacy-tracker.js +39 -0
  45. package/dist/harness/pico3/membrane.d.ts +23 -0
  46. package/dist/harness/pico3/membrane.js +140 -0
  47. package/dist/harness/pico3/memory.d.ts +53 -0
  48. package/dist/harness/pico3/memory.js +265 -0
  49. package/dist/harness/pico3/scheduler.d.ts +49 -0
  50. package/dist/harness/pico3/scheduler.js +437 -0
  51. package/dist/harness/pico3/session.d.ts +264 -0
  52. package/dist/harness/pico3/session.js +1328 -0
  53. package/dist/harness/pico3/system.d.ts +126 -0
  54. package/dist/harness/pico3/system.js +244 -0
  55. package/dist/harness/pico3/types.d.ts +973 -0
  56. package/dist/harness/pico3/types.js +94 -0
  57. package/dist/harness/pico3/view.d.ts +34 -0
  58. package/dist/harness/pico3/view.js +404 -0
  59. package/dist/harness/runtime/drive/tool-placement.js +2 -27
  60. package/dist/harness/telemetry.d.ts +36 -36
  61. package/dist/harness/tools/image.js +1 -1
  62. package/dist/proxy.d.ts +2 -2
  63. package/dist/types.d.ts +111 -34
  64. package/package.json +9 -5
@@ -0,0 +1,265 @@
1
+ import { applyImmutable, isBase } from "@earendil-works/chord/delta";
2
+ /**
3
+ * In-memory Storage. The reference backend, and the read path of JsonlStorage.
4
+ *
5
+ * A batch is validated and staged in full before any table changes: one failed
6
+ * batch changes no table, no document, no ID high-water, no sequence. Committed
7
+ * IDs are never reused; IDs minted but never committed may be reused after reopen.
8
+ */
9
+ export class MemoryStorage {
10
+ constructor() {
11
+ this.conversationsById = new Map();
12
+ this.entriesById = new Map();
13
+ this.entriesByConversation = new Map(); // ascending
14
+ this.tasksById = new Map();
15
+ this.inputsById = new Map();
16
+ this.inputsByRequest = new Map();
17
+ /** Rewindable docs keep their full op history with the seq of each batch, for `docAsOf`. */
18
+ this.rewindableLog = new Map();
19
+ this.stickyLog = new Map();
20
+ this.entrySeq = new Map(); // entry id → seq of its batch
21
+ this.nextIdValue = 1;
22
+ this.seq = 0;
23
+ this.closed = false;
24
+ }
25
+ mintId() {
26
+ return this.nextIdValue++;
27
+ }
28
+ setNextId(n) {
29
+ this.nextIdValue = Math.max(this.nextIdValue, n);
30
+ }
31
+ async commit(writes, _ctx) {
32
+ if (this.closed)
33
+ throw new Error("storage closed");
34
+ const seq = this.seq + 1;
35
+ const staged = this.stage(writes, seq); // throws before any mutation
36
+ staged();
37
+ this.seq = seq;
38
+ return seq;
39
+ }
40
+ /** Validate the whole batch against the current tables; return a function that applies it. */
41
+ stage(writes, seq) {
42
+ const ops = [];
43
+ const created = new Set();
44
+ let maxId = 0;
45
+ const claim = (id, what) => {
46
+ if (!Number.isSafeInteger(id) || id <= 0)
47
+ throw new Error(`${what}: invalid id ${id}`);
48
+ if (created.has(id))
49
+ throw new Error(`${what}: id ${id} created twice in one batch`);
50
+ created.add(id);
51
+ maxId = Math.max(maxId, id);
52
+ };
53
+ for (const w of writes) {
54
+ switch (w.type) {
55
+ case "conversation": {
56
+ if (this.conversationsById.has(w.conversation.id))
57
+ throw new Error(`conversation ${w.conversation.id} exists`);
58
+ claim(w.conversation.id, "conversation");
59
+ const c = clone(w.conversation);
60
+ ops.push(() => {
61
+ this.conversationsById.set(c.id, c);
62
+ this.entriesByConversation.set(c.id, []);
63
+ });
64
+ break;
65
+ }
66
+ case "entry": {
67
+ if (this.entriesById.has(w.entry.id))
68
+ throw new Error(`entry ${w.entry.id} exists`);
69
+ claim(w.entry.id, "entry");
70
+ const e = clone(w.entry);
71
+ ops.push(() => {
72
+ this.entriesById.set(e.id, e);
73
+ (this.entriesByConversation.get(e.conversationId) ??
74
+ this.entriesByConversation.set(e.conversationId, []).get(e.conversationId)).push(e);
75
+ this.entrySeq.set(e.id, seq);
76
+ });
77
+ break;
78
+ }
79
+ case "task": {
80
+ if (this.tasksById.has(w.task.id))
81
+ throw new Error(`task ${w.task.id} exists`);
82
+ claim(w.task.id, "task");
83
+ const t = clone(w.task);
84
+ ops.push(() => this.tasksById.set(t.id, t));
85
+ break;
86
+ }
87
+ case "task.patch": {
88
+ const prev = this.tasksById.get(w.patch.id) ??
89
+ [...writes]
90
+ .filter((x) => x.type === "task")
91
+ .find((x) => x.task.id === w.patch.id)?.task;
92
+ if (prev === undefined)
93
+ throw new Error(`patch for unknown task ${w.patch.id}`);
94
+ const p = clone(w.patch);
95
+ ops.push(() => {
96
+ const cur = this.tasksById.get(p.id);
97
+ const { id: _i, ...fields } = p;
98
+ const next = { ...cur, ...fields };
99
+ if (p.checkpoint === null)
100
+ delete next.checkpoint;
101
+ this.tasksById.set(p.id, next);
102
+ });
103
+ break;
104
+ }
105
+ case "input": {
106
+ const i = clone(w.input);
107
+ if (!this.inputsById.has(i.id))
108
+ claim(i.id, "input");
109
+ ops.push(() => {
110
+ this.inputsById.set(i.id, i);
111
+ if (i.requestId !== undefined)
112
+ this.inputsByRequest.set(`${i.conversationId}:${i.requestId}`, i);
113
+ });
114
+ break;
115
+ }
116
+ case "doc": {
117
+ const ref = clone(w.ref);
118
+ const o = clone(w.ops);
119
+ if (ref.doc === "session")
120
+ ops.push(() => {
121
+ this.sessionDoc = applyImmutable((this.sessionDoc ?? { plugins: {} }), o);
122
+ });
123
+ else if (ref.doc === "rewindable")
124
+ ops.push(() => (this.rewindableLog.get(ref.conversationId) ??
125
+ this.rewindableLog.set(ref.conversationId, []).get(ref.conversationId)).push({ seq, ops: o }));
126
+ else
127
+ ops.push(() => (this.stickyLog.get(ref.conversationId) ??
128
+ this.stickyLog.set(ref.conversationId, []).get(ref.conversationId)).push(o));
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ return () => {
134
+ for (const op of ops)
135
+ op();
136
+ this.setNextId(maxId + 1);
137
+ };
138
+ }
139
+ async conversation(id, _ctx) {
140
+ return optionalClone(this.conversationsById.get(id));
141
+ }
142
+ async conversations(_ctx) {
143
+ return [...this.conversationsById.values()].map(clone);
144
+ }
145
+ async entries(ids, _ctx) {
146
+ const out = new Map();
147
+ for (const id of ids) {
148
+ const e = this.entriesById.get(id);
149
+ if (e)
150
+ out.set(id, clone(e));
151
+ }
152
+ return out;
153
+ }
154
+ /** Newest-first, fork-aware: after this conversation's own entries, the parent's up to the fork point, and so on. */
155
+ async scanEntries(scan, _ctx) {
156
+ const out = [];
157
+ let conversationId = scan.conversationId;
158
+ let cap = scan.before;
159
+ while (conversationId !== undefined && out.length < scan.limit) {
160
+ const own = this.entriesByConversation.get(conversationId) ?? [];
161
+ for (let i = own.length - 1; i >= 0 && out.length < scan.limit; i--) {
162
+ const e = own[i];
163
+ if (cap !== undefined && e.id >= cap)
164
+ continue;
165
+ if (scan.kind !== undefined && e.kind !== scan.kind)
166
+ continue;
167
+ if (scan.withHead && e.head === undefined)
168
+ continue;
169
+ out.push(clone(e));
170
+ }
171
+ const c = this.conversationsById.get(conversationId);
172
+ conversationId = c?.parent?.conversationId;
173
+ cap = c?.parent === undefined ? undefined : Math.min(cap ?? Infinity, c.parent.at + 1);
174
+ }
175
+ return out;
176
+ }
177
+ async task(id, _ctx) {
178
+ return optionalClone(this.tasksById.get(id));
179
+ }
180
+ async scanTasks(scan, _ctx) {
181
+ const out = [];
182
+ for (const t of this.tasksById.values()) {
183
+ if (scan.conversationId !== undefined && t.conversationId !== scan.conversationId)
184
+ continue;
185
+ if (scan.status !== undefined && !scan.status.includes(t.status))
186
+ continue;
187
+ if (scan.kind !== undefined && t.kind !== scan.kind)
188
+ continue;
189
+ out.push(clone(t));
190
+ }
191
+ return out;
192
+ }
193
+ async input(id, _ctx) {
194
+ return optionalClone(this.inputsById.get(id));
195
+ }
196
+ async inputByRequest(conversationId, requestId, _ctx) {
197
+ return optionalClone(this.inputsByRequest.get(`${conversationId}:${requestId}`));
198
+ }
199
+ async doc(ref, _ctx) {
200
+ if (ref.doc === "session")
201
+ return this.sessionDoc === undefined ? { plugins: {} } : structuredClone(this.sessionDoc);
202
+ const log = ref.doc === "rewindable"
203
+ ? this.rewindableLog.get(ref.conversationId)?.map((r) => r.ops)
204
+ : this.stickyLog.get(ref.conversationId);
205
+ if (log === undefined)
206
+ return undefined;
207
+ return clone(this.fold(log));
208
+ }
209
+ /**
210
+ * Commit-granular history: the rewindable state after the atomic commit that contains
211
+ * entry `at`, walking the fork chain for entries owned by ancestors.
212
+ */
213
+ async docAsOf(conversationId, at, _ctx) {
214
+ const owner = this.entriesById.get(at)?.conversationId;
215
+ if (owner === undefined)
216
+ return undefined;
217
+ const chain = [];
218
+ let c = this.conversationsById.get(conversationId);
219
+ while (c !== undefined && c.id !== owner) {
220
+ chain.push(c.id);
221
+ c = c.parent ? this.conversationsById.get(c.parent.conversationId) : undefined;
222
+ }
223
+ if (c === undefined)
224
+ return undefined;
225
+ const seqAt = this.entrySeq.get(at) ?? 0;
226
+ const log = (this.rewindableLog.get(owner) ?? []).filter((r) => r.seq <= seqAt).map((r) => r.ops);
227
+ return log.length === 0 ? undefined : clone(this.fold(log));
228
+ }
229
+ fold(log) {
230
+ let start = 0;
231
+ for (let i = log.length - 1; i >= 0; i--)
232
+ if (isBase(log[i])) {
233
+ start = i;
234
+ break;
235
+ }
236
+ let state = {};
237
+ for (let i = start; i < log.length; i++)
238
+ state = applyImmutable(state, log[i]);
239
+ return state;
240
+ }
241
+ async truncate(ref, _ctx) {
242
+ if (ref.doc !== "sticky")
243
+ return;
244
+ const log = this.stickyLog.get(ref.conversationId);
245
+ if (log === undefined)
246
+ return;
247
+ let start = 0;
248
+ for (let i = log.length - 1; i >= 0; i--)
249
+ if (isBase(log[i])) {
250
+ start = i;
251
+ break;
252
+ }
253
+ log.splice(0, start);
254
+ }
255
+ async close(_ctx) {
256
+ this.closed = true;
257
+ }
258
+ }
259
+ function clone(value) {
260
+ return JSON.parse(JSON.stringify(value));
261
+ }
262
+ function optionalClone(value) {
263
+ return value === undefined ? undefined : clone(value);
264
+ }
265
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1,49 @@
1
+ import type { Context } from "@earendil-works/chord";
2
+ import type { Session } from "./session.ts";
3
+ import { type AnyKind, type Id, type Input, type Invoker, type Runtime, type Task } from "./types.ts";
4
+ export interface SchedulerDeps {
5
+ session: Session;
6
+ kinds: ReadonlyMap<string, AnyKind>;
7
+ runtime(task: Task, invoker: Extract<Invoker, {
8
+ type: "task";
9
+ }>, ctx: Context): Runtime;
10
+ onReport(error: unknown): void;
11
+ ctx: Context;
12
+ }
13
+ export declare class Scheduler {
14
+ private readonly deps;
15
+ private enabled;
16
+ private holds;
17
+ private dirty;
18
+ private draining;
19
+ private readonly invocations;
20
+ private readonly taskWaiters;
21
+ private readonly inputWaiters;
22
+ private readonly idleWaiters;
23
+ constructor(deps: SchedulerDeps);
24
+ resume(): void;
25
+ stop(): void;
26
+ hold(): () => void;
27
+ kick(): void;
28
+ private drain;
29
+ /** On the line: reserve every eligible task. */
30
+ private reserveEligible;
31
+ private dispatch;
32
+ private lease;
33
+ /** The phase loop. Each handler gets a capability lease revoked when that handler's promise returns. */
34
+ private runPhases;
35
+ /** Mark, revoke, signal, join. The scheduler then reserves the fresh abort on its next drain. */
36
+ abortTask(id: Id, ctx: Context): Promise<"marked" | "terminal">;
37
+ waitForTask(id: Id, ctx: Context): Promise<Task>;
38
+ waitForInput(id: Id, ctx: Context): Promise<Input>;
39
+ /** Read state and install the waiter in one line operation; the wait itself happens off-line. */
40
+ private waiter;
41
+ waitForIdle(conversationId: Id | undefined, ctx: Context): Promise<void>;
42
+ private isIdle;
43
+ private checkIdle;
44
+ /** Signal every invocation and wait for them; nothing is written. */
45
+ joinAll(): Promise<void>;
46
+ quiescent(): boolean;
47
+ get liveInvocations(): number;
48
+ }
49
+ //# sourceMappingURL=scheduler.d.ts.map