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

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,126 @@
1
+ import type { Context } from "@earendil-works/chord";
2
+ import type { AnyToolDeclaration, ContextEdit, CoreTx, Entry, HookInfo, HookResult, Id, JsonValue, RewindableState, Runtime, Stored, SystemMessage } from "./types.ts";
3
+ export interface SystemSection<T extends JsonValue = JsonValue> {
4
+ readonly key: string;
5
+ render(value: T): string;
6
+ }
7
+ export declare function defineSystemSection<T extends JsonValue>(definition: {
8
+ key: string;
9
+ render(value: T): string;
10
+ }): SystemSection<T>;
11
+ export type EnvironmentInfo = {
12
+ cwd: string;
13
+ };
14
+ export type SkillInfo = {
15
+ name: string;
16
+ description: string;
17
+ };
18
+ export declare const systemSections: {
19
+ readonly identity: SystemSection<string>;
20
+ readonly environment: SystemSection<EnvironmentInfo>;
21
+ readonly skills: SystemSection<SkillInfo[]>;
22
+ };
23
+ /** Section seed for a new conversation (§8.1): set with a checked pair, or remove an inherited key. */
24
+ export type SectionSeed = {
25
+ key: string;
26
+ value: JsonValue;
27
+ } | {
28
+ key: string;
29
+ remove: true;
30
+ };
31
+ export declare const sectionSeed: <T extends JsonValue>(section: SystemSection<T>, value: T) => SectionSeed;
32
+ export declare const removeSection: (key: string) => SectionSeed;
33
+ export type SectionRecord = {
34
+ key: string;
35
+ action: "set";
36
+ value: JsonValue;
37
+ rendered: string;
38
+ } | {
39
+ key: string;
40
+ action: "remove";
41
+ };
42
+ export type SystemEntryData = {
43
+ baseline?: true;
44
+ sections: SectionRecord[];
45
+ };
46
+ /** A metadata-only delta has `model: []`. */
47
+ export type SystemEntry = Entry & {
48
+ kind: "pi.system";
49
+ model: [] | [Stored<SystemMessage>];
50
+ data: SystemEntryData;
51
+ };
52
+ type SectionState = {
53
+ value: JsonValue;
54
+ rendered: string;
55
+ };
56
+ export type Canonical = Map<string, SectionState>;
57
+ export declare function foldCanonical(reads: Pick<CoreTx, "scanEntries">, conversationId: Id): Promise<{
58
+ canonical: Canonical;
59
+ newestManaged: Id | null;
60
+ newestBaseline: Id | null;
61
+ }>;
62
+ export interface SystemSectionDraft {
63
+ get<T extends JsonValue>(section: SystemSection<T>): T | undefined;
64
+ set<T extends JsonValue>(section: SystemSection<T>, value: T): void;
65
+ delete(section: {
66
+ key: string;
67
+ }): void;
68
+ wrap<T extends JsonValue>(section: SystemSection<T>, transform: (rendered: string) => string): void;
69
+ }
70
+ export interface SectionRegistry {
71
+ readonly map: ReadonlyMap<string, SystemSection>;
72
+ readonly revision: number;
73
+ }
74
+ export interface ToolRegistry {
75
+ readonly map: ReadonlyMap<string, AnyToolDeclaration>;
76
+ readonly revision: number;
77
+ }
78
+ /** Snapshot S. Compared field by field before the `prepared` commit. */
79
+ export interface PreparationSnapshot {
80
+ newestManaged: Id | null;
81
+ newestBaseline: Id | null;
82
+ newestHead: Id | null;
83
+ settings: {
84
+ model?: JsonValue;
85
+ thinkingLevel: JsonValue;
86
+ selectedTools: string[];
87
+ profile: JsonValue;
88
+ };
89
+ sectionsRev: number;
90
+ toolsRev: number;
91
+ }
92
+ export declare const sameSnapshot: (a: PreparationSnapshot, b: PreparationSnapshot) => boolean;
93
+ export declare function takeSnapshot(tx: Pick<CoreTx, "scanEntries" | "newestEntry" | "snapshot" | "conversation">, conversationId: Id, sections: SectionRegistry, tools: ToolRegistry): Promise<{
94
+ snapshot: PreparationSnapshot;
95
+ canonical: Canonical;
96
+ seed: readonly SectionSeed[] | undefined;
97
+ }>;
98
+ export interface SystemInstructionsHooks {
99
+ /** Edit the draft; optionally override the tool loadout (last override wins). A throwing handler's edits are rolled back. */
100
+ systemInstructions(input: {
101
+ sections: SystemSectionDraft;
102
+ config: PreparationSnapshot["settings"];
103
+ tools: readonly AnyToolDeclaration[];
104
+ }, info: HookInfo, ctx: Context): HookResult<{
105
+ tools?: readonly AnyToolDeclaration[];
106
+ }>;
107
+ }
108
+ /** Off the line: seed the draft, run handlers, freeze. */
109
+ export declare function prepareDraft(rt: Pick<Runtime<SystemInstructionsHooks>, "hooks" | "tools">, sections: SectionRegistry, canonical: Canonical, seed: readonly SectionSeed[] | undefined, settings: PreparationSnapshot["settings"], onReport: (m: string) => void, ctx: Context): Promise<{
110
+ desired: Canonical;
111
+ tools: readonly AnyToolDeclaration[];
112
+ }>;
113
+ /** On the line, in the `prepared` commit: diff desired against canonical; baseline if a head intervened. */
114
+ export declare function planManagedEntry(tx: Pick<CoreTx, "context">, conversationId: Id, snapshot: PreparationSnapshot, canonical: Canonical, desired: Canonical, tools: readonly AnyToolDeclaration[], now: number): Promise<{
115
+ data: SystemEntryData;
116
+ model: [] | [Stored<SystemMessage>];
117
+ edits?: ContextEdit[];
118
+ } | undefined>;
119
+ /** Tools effective in a projected request: fold toolsAdded/toolsRemoved across its SystemMessages. */
120
+ export declare function effectiveTools(messages: readonly {
121
+ role: string;
122
+ }[]): {
123
+ name: string;
124
+ }[];
125
+ export type { RewindableState, Runtime };
126
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1,244 @@
1
+ export function defineSystemSection(definition) {
2
+ return Object.freeze(definition);
3
+ }
4
+ export const systemSections = {
5
+ identity: defineSystemSection({ key: "identity", render: (v) => v }),
6
+ environment: defineSystemSection({
7
+ key: "environment",
8
+ render: (v) => `Working directory: ${v.cwd}`,
9
+ }),
10
+ skills: defineSystemSection({
11
+ key: "skills",
12
+ render: (v) => v.map((s) => `- ${s.name}: ${s.description}`).join("\n"),
13
+ }),
14
+ };
15
+ export const sectionSeed = (section, value) => ({
16
+ key: section.key,
17
+ value,
18
+ });
19
+ export const removeSection = (key) => ({ key, remove: true });
20
+ export async function foldCanonical(reads, conversationId) {
21
+ const managed = [];
22
+ let before;
23
+ let baseline = null;
24
+ scan: for (;;) {
25
+ const page = await reads.scanEntries({
26
+ conversationId,
27
+ kind: "pi.system",
28
+ limit: 64,
29
+ ...(before === undefined ? {} : { before }),
30
+ });
31
+ for (const e of page) {
32
+ managed.push(e);
33
+ if (e.data.baseline) {
34
+ baseline = e.id;
35
+ break scan;
36
+ }
37
+ }
38
+ if (page.length < 64)
39
+ break;
40
+ before = page[page.length - 1].id;
41
+ }
42
+ managed.reverse();
43
+ const canonical = new Map();
44
+ for (const e of managed)
45
+ for (const r of e.data.sections)
46
+ r.action === "remove"
47
+ ? canonical.delete(r.key)
48
+ : canonical.set(r.key, { value: r.value, rendered: r.rendered });
49
+ return { canonical, newestManaged: managed[managed.length - 1]?.id ?? null, newestBaseline: baseline };
50
+ }
51
+ class Draft {
52
+ constructor(seed) {
53
+ this.values = new Map();
54
+ this.wrappers = new Map();
55
+ this.touched = new Set();
56
+ for (const [k, s] of seed)
57
+ this.values.set(k, s.value);
58
+ }
59
+ get(section) {
60
+ const v = this.values.get(section.key);
61
+ return v === undefined ? undefined : structuredClone(v);
62
+ }
63
+ set(section, value) {
64
+ this.values.set(section.key, value);
65
+ this.touched.add(section.key);
66
+ }
67
+ delete(section) {
68
+ this.values.delete(section.key);
69
+ this.wrappers.delete(section.key);
70
+ this.touched.add(section.key);
71
+ }
72
+ wrap(section, transform) {
73
+ (this.wrappers.get(section.key) ?? this.wrappers.set(section.key, []).get(section.key)).push(transform);
74
+ this.touched.add(section.key);
75
+ }
76
+ snapshot() {
77
+ return {
78
+ values: new Map(this.values),
79
+ wrappers: new Map([...this.wrappers].map(([k, v]) => [k, [...v]])),
80
+ touched: new Set(this.touched),
81
+ };
82
+ }
83
+ restore(s) {
84
+ this.values = s.values;
85
+ this.wrappers = s.wrappers;
86
+ this.touched = s.touched;
87
+ }
88
+ }
89
+ /** Render touched sections (wrappers apply after rendering, in registration order); untouched keep their stored text. */
90
+ function freeze(draft, canonical, registry) {
91
+ const desired = new Map();
92
+ for (const [key, value] of draft.values) {
93
+ if (!draft.touched.has(key)) {
94
+ const prev = canonical.get(key);
95
+ if (prev)
96
+ desired.set(key, prev);
97
+ continue;
98
+ }
99
+ const def = registry.get(key);
100
+ if (def === undefined)
101
+ continue; // unregistered: cannot re-render; treated as removed
102
+ let rendered = def.render(value);
103
+ for (const w of draft.wrappers.get(key) ?? [])
104
+ rendered = w(rendered);
105
+ desired.set(key, { value, rendered });
106
+ }
107
+ return desired;
108
+ }
109
+ export const sameSnapshot = (a, b) => JSON.stringify(a) === JSON.stringify(b);
110
+ export async function takeSnapshot(tx, conversationId, sections, tools) {
111
+ const { canonical, newestManaged, newestBaseline } = await foldCanonical(tx, conversationId);
112
+ const head = await tx.newestEntry(conversationId, { withHead: true });
113
+ const r = tx.snapshot({ doc: "rewindable", conversationId }); // a plain copy: the snapshot outlives this transaction
114
+ const conv = await tx.conversation(conversationId);
115
+ return {
116
+ snapshot: {
117
+ newestManaged,
118
+ newestBaseline,
119
+ newestHead: head?.id ?? null,
120
+ settings: {
121
+ ...(r.model === undefined ? {} : { model: r.model }),
122
+ thinkingLevel: r.thinkingLevel,
123
+ selectedTools: [...r.selectedTools],
124
+ profile: r.profile,
125
+ },
126
+ sectionsRev: sections.revision,
127
+ toolsRev: tools.revision,
128
+ },
129
+ canonical,
130
+ seed: newestManaged === null ? conv?.sections : undefined, // §8.1: the seed applies while there is no local managed entry
131
+ };
132
+ }
133
+ /** Off the line: seed the draft, run handlers, freeze. */
134
+ export async function prepareDraft(rt, sections, canonical, seed, settings, onReport, ctx) {
135
+ const draft = new Draft(canonical);
136
+ for (const s of seed ?? [])
137
+ "remove" in s ? draft.delete({ key: s.key }) : draft.set({ key: s.key, render: () => "" }, s.value);
138
+ const defaultTools = [];
139
+ for (const name of settings.selectedTools) {
140
+ const t = rt.tools.get(name);
141
+ t ? defaultTools.push(t) : onReport(`selected tool ${name} is not registered`);
142
+ }
143
+ let tools = defaultTools;
144
+ for (const binding of rt.hooks.handlers()) {
145
+ const before = draft.snapshot();
146
+ try {
147
+ const out = await binding.handlers.systemInstructions?.({ sections: draft, config: settings, tools: defaultTools }, binding.api, ctx);
148
+ if (out?.tools)
149
+ tools = out.tools;
150
+ }
151
+ catch (error) {
152
+ if (ctx.abortSignal?.aborted)
153
+ throw error;
154
+ draft.restore(before); // collect / skip: this handler's edits are rolled back
155
+ onReport(String(error));
156
+ }
157
+ }
158
+ return { desired: freeze(draft, canonical, sections.map), tools };
159
+ }
160
+ /** On the line, in the `prepared` commit: diff desired against canonical; baseline if a head intervened. */
161
+ export async function planManagedEntry(tx, conversationId, snapshot, canonical, desired, tools, now) {
162
+ // §12.4: no managed history at all → first preparation appends a full baseline; and a usable
163
+ // baseline must follow the newest head.
164
+ const needBaseline = snapshot.newestManaged === null ||
165
+ (snapshot.newestHead !== null &&
166
+ (snapshot.newestBaseline === null || snapshot.newestHead > snapshot.newestBaseline));
167
+ const toolOf = (t) => ({
168
+ name: t.name,
169
+ description: t.description,
170
+ parameters: structuredClone(t.parameters),
171
+ });
172
+ // Previous effective tools: those declared by the newest managed entry chain (we recompute from canonical's owner: the tail message).
173
+ const { messages } = await tx.context(conversationId);
174
+ const previous = new Map();
175
+ for (const m of messages)
176
+ if (m.role === "system") {
177
+ for (const t of m.toolsRemoved ?? [])
178
+ previous.delete(t.name);
179
+ for (const t of m.toolsAdded ?? [])
180
+ previous.set(t.name, t);
181
+ }
182
+ if (needBaseline) {
183
+ const sections = [...desired].map(([key, s]) => ({
184
+ key,
185
+ action: "set",
186
+ value: s.value,
187
+ rendered: s.rendered,
188
+ }));
189
+ const content = sections.map((s) => (s.action === "set" ? `## ${s.key}\n${s.rendered}` : "")).join("\n\n");
190
+ const { entries } = await tx.context(conversationId);
191
+ const edits = entries
192
+ .filter((e) => e.kind === "pi.system" && e.id > (snapshot.newestHead ?? 0))
193
+ .map((e) => ({ target: e.id, action: "omit" }));
194
+ const message = {
195
+ role: "system",
196
+ content,
197
+ toolsAdded: tools.map(toolOf),
198
+ timestamp: now,
199
+ };
200
+ return { data: { baseline: true, sections }, model: [message], ...(edits.length ? { edits } : {}) };
201
+ }
202
+ const changed = [];
203
+ for (const [key, s] of desired) {
204
+ const prev = canonical.get(key);
205
+ if (!prev || prev.rendered !== s.rendered || JSON.stringify(prev.value) !== JSON.stringify(s.value))
206
+ changed.push({ key, action: "set", value: s.value, rendered: s.rendered });
207
+ }
208
+ for (const key of canonical.keys())
209
+ if (!desired.has(key))
210
+ changed.push({ key, action: "remove" });
211
+ const added = tools.filter((t) => !previous.has(t.name));
212
+ const removed = [...previous.values()].filter((p) => !tools.some((t) => t.name === p.name));
213
+ if (changed.length === 0 && added.length === 0 && removed.length === 0)
214
+ return undefined;
215
+ const renderChanged = changed.some((s) => s.action === "remove" || canonical.get(s.key)?.rendered !== s.rendered);
216
+ if (!renderChanged && added.length === 0 && removed.length === 0)
217
+ return { data: { sections: changed }, model: [] }; // metadata-only delta
218
+ const content = changed
219
+ .map((s) => s.action === "set"
220
+ ? `The ${s.key} section now reads:\n${s.rendered}`
221
+ : `The ${s.key} section no longer applies.`)
222
+ .join("\n\n");
223
+ const message = {
224
+ role: "system",
225
+ content,
226
+ ...(added.length ? { toolsAdded: added.map(toolOf) } : {}),
227
+ ...(removed.length ? { toolsRemoved: removed } : {}),
228
+ timestamp: now,
229
+ };
230
+ return { data: { sections: changed }, model: [message] };
231
+ }
232
+ /** Tools effective in a projected request: fold toolsAdded/toolsRemoved across its SystemMessages. */
233
+ export function effectiveTools(messages) {
234
+ const tools = new Map();
235
+ for (const m of messages)
236
+ if (m.role === "system") {
237
+ for (const t of m.toolsRemoved ?? [])
238
+ tools.delete(t.name);
239
+ for (const t of m.toolsAdded ?? [])
240
+ tools.set(t.name, t);
241
+ }
242
+ return [...tools.values()];
243
+ }
244
+ //# sourceMappingURL=system.js.map