@geohar/unbien-durable 0.0.0-stage → 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -2
  3. package/dist/agent_config.d.ts +56 -0
  4. package/dist/agent_config.js +92 -0
  5. package/dist/agent_config.js.map +1 -0
  6. package/dist/bridge.d.ts +175 -0
  7. package/dist/bridge.js +921 -0
  8. package/dist/bridge.js.map +1 -0
  9. package/dist/event_source.d.ts +53 -0
  10. package/dist/event_source.js +124 -0
  11. package/dist/event_source.js.map +1 -0
  12. package/dist/extension.d.ts +34 -0
  13. package/dist/extension.js +46 -0
  14. package/dist/extension.js.map +1 -0
  15. package/dist/host.d.ts +2 -0
  16. package/dist/host.js +261 -0
  17. package/dist/host.js.map +1 -0
  18. package/dist/index.d.ts +17 -0
  19. package/dist/index.js +18 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/launch_wait_quit.d.ts +12 -0
  22. package/dist/launch_wait_quit.js +136 -0
  23. package/dist/launch_wait_quit.js.map +1 -0
  24. package/dist/mesh_delivery.d.ts +28 -0
  25. package/dist/mesh_delivery.js +31 -0
  26. package/dist/mesh_delivery.js.map +1 -0
  27. package/dist/mirror.d.ts +56 -0
  28. package/dist/mirror.js +136 -0
  29. package/dist/mirror.js.map +1 -0
  30. package/dist/ollama_cloud.d.ts +2 -0
  31. package/dist/ollama_cloud.js +98 -0
  32. package/dist/ollama_cloud.js.map +1 -0
  33. package/dist/pi_launch.d.ts +50 -0
  34. package/dist/pi_launch.js +84 -0
  35. package/dist/pi_launch.js.map +1 -0
  36. package/dist/port.d.ts +94 -0
  37. package/dist/port.js +121 -0
  38. package/dist/port.js.map +1 -0
  39. package/dist/registrar.d.ts +7 -0
  40. package/dist/registrar.js +31 -0
  41. package/dist/registrar.js.map +1 -0
  42. package/dist/thinking.d.ts +24 -0
  43. package/dist/thinking.js +36 -0
  44. package/dist/thinking.js.map +1 -0
  45. package/dist/view_translator.d.ts +83 -0
  46. package/dist/view_translator.js +317 -0
  47. package/dist/view_translator.js.map +1 -0
  48. package/package.json +63 -3
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Durable binding's HeadlessLaunchPort implementation: the launch-wait-quit
3
+ * primitive behind the `launch_pi_task` tool — the ONE place the two
4
+ * bindings meet at runtime.
5
+ *
6
+ * Launch path: the pi SDK's headless `createAgentSession` (the programmatic
7
+ * equivalent of `pi` without a TUI — NOT `pi -p`, whose print-mode exit the
8
+ * un-bien extension deliberately never auto-starts the relay in). The session
9
+ * loads its extensions through pi's own resource discovery from the target
10
+ * cwd (installed pi-packages, `<cwd>/.pi/extensions`, `<agentDir>`), so the
11
+ * un-bien pi extension must be discoverable there — when it is, its
12
+ * session_start auto-init joins the UDS mesh under the session's display
13
+ * name, which is the SCOPED name minted per invocation.
14
+ *
15
+ * Readiness: the local UDS broker is probed (observer probe — never
16
+ * registers, never perturbs the roster) until the scoped name appears as a
17
+ * peer address; that address is the agent_send routing key for the task.
18
+ *
19
+ * Teardown: `session.dispose()` — the un-bien extension's session_shutdown
20
+ * handler leaves the mesh and closes the relay, exactly as it does for a
21
+ * quitting interactive pi.
22
+ */
23
+ import { createAgentSession, } from "@earendil-works/pi-coding-agent";
24
+ import { probeListPeers, } from "@geohar/un-bien";
25
+ import { LOCAL_SESSION_NAME, sessionSockPath, } from "@geohar/un-bien/durable";
26
+ /** Default readiness deadline: the launched session's auto-init does a cwd
27
+ * lock + config read + UDS broker join before the name is visible. */
28
+ const DEFAULT_READY_TIMEOUT_MS = 60_000;
29
+ const READY_POLL_INTERVAL_MS = 250;
30
+ export class HeadlessPiLaunch {
31
+ async launchHeadless(opts) {
32
+ const { session } = await createAgentSession({ cwd: opts.cwd });
33
+ return new HeadlessPiSession(session, opts.name);
34
+ }
35
+ }
36
+ export class HeadlessPiSession {
37
+ session;
38
+ name;
39
+ constructor(session, name) {
40
+ this.session = session;
41
+ this.name = name;
42
+ }
43
+ /** join-mesh-under-name: bind the scoped name, then bring the un-bien
44
+ * extension up. `bindExtensions` fires session_start; the extension's
45
+ * auto-init resolves the mesh name from the session display name (set
46
+ * FIRST so the join sees it) and connects the broker. */
47
+ async joinMeshUnderName() {
48
+ this.session.setSessionName(this.name);
49
+ await this.session.bindExtensions({});
50
+ }
51
+ /** await-ready: poll the broker's observer probe until the scoped name is
52
+ * a registered peer. Resolves with the broker-assigned address (the name
53
+ * may carry a `#N` collision suffix the broker added). */
54
+ async awaitReady(opts) {
55
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
56
+ const deadline = Date.now() + timeoutMs;
57
+ for (;;) {
58
+ const probe = await probeListPeers(sessionSockPath(LOCAL_SESSION_NAME), 2_000);
59
+ if (probe.status === "ok") {
60
+ const match = probe.detailed.find((entry) => entry.name === this.name ||
61
+ entry.name.startsWith(`${this.name}#`));
62
+ if (match)
63
+ return { address: match.address };
64
+ }
65
+ if (Date.now() >= deadline) {
66
+ throw new Error(`launched pi session "${this.name}" did not join the mesh within ` +
67
+ `${timeoutMs}ms — the un-bien extension must be discoverable by ` +
68
+ `pi from the target cwd (installed pi-package or .pi/extensions) ` +
69
+ `and that cwd needs a un-bien local config (auto_start_relay)`);
70
+ }
71
+ await new Promise((resolve) => setTimeout(resolve, READY_POLL_INTERVAL_MS));
72
+ }
73
+ }
74
+ /** tear-down: quit gracefully (extension disposal leaves the mesh). */
75
+ async tearDown() {
76
+ try {
77
+ this.session.dispose();
78
+ }
79
+ catch {
80
+ /* best-effort — dispose must not throw even mid-run */
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=pi_launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi_launch.js","sourceRoot":"","sources":["../src/pi_launch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EACL,kBAAkB,GAEnB,MAAM,iCAAiC,CAAA;AAKxC,OAAO,EACL,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,kBAAkB,EAClB,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAEhC;uEACuE;AACvE,MAAM,wBAAwB,GAAG,MAAM,CAAA;AACvC,MAAM,sBAAsB,GAAG,GAAG,CAAA;AAElC,MAAM,OAAO,gBAAgB;IAC3B,KAAK,CAAC,cAAc,CAAC,IAGpB;QACC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,kBAAkB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QAC/D,OAAO,IAAI,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;CACF;AAED,MAAM,OAAO,iBAAiB;IAET,OAAO;IACf,IAAI;IAFf,YACmB,OAAqB,EAC7B,IAAY;uBADJ,OAAO;oBACf,IAAI;IACZ,CAAC;IAEJ;;;8DAG0D;IAC1D,KAAK,CAAC,iBAAiB;QACrB,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtC,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;IACvC,CAAC;IAED;;+DAE2D;IAC3D,KAAK,CAAC,UAAU,CAAC,IAEhB;QACC,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,wBAAwB,CAAA;QAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAA;QACvC,SAAS,CAAC;YACR,MAAM,KAAK,GAAG,MAAM,cAAc,CAChC,eAAe,CAAC,kBAAkB,CAAC,EACnC,KAAK,CACN,CAAA;YACD,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAC/B,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;oBACxB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CACzC,CAAA;gBACD,IAAI,KAAK;oBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;YAC9C,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CACb,wBAAwB,IAAI,CAAC,IAAI,iCAAiC;oBAChE,GAAG,SAAS,qDAAqD;oBACjE,kEAAkE;oBAClE,8DAA8D,CACjE,CAAA;YACH,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAC5B,UAAU,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAC5C,CAAA;QACH,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;IACH,CAAC;CACF"}
package/dist/port.d.ts ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Durable binding's AgentSessionPort + SessionControlPort implementations.
3
+ *
4
+ * A durable Conversation already has everything the port promises, with ONE
5
+ * structural difference from pi: its transcript and busy state are read
6
+ * asynchronously (Chord commits), while the port is synchronous (inbound rpc
7
+ * dispatch must not await the store). The bridge closes that gap with a
8
+ * TRANSCRIPT MIRROR (mirror.ts) kept in sync by the ConversationViewSource,
9
+ * which grows it with every entry the conversation view adds. Because
10
+ * durable conversation IDs are
11
+ * stable across restarts, there is NO session-replacement machinery here —
12
+ * no generations, no disposal re-arm, no stale-ctx juggling. A replacement
13
+ * is just a new conversation handle retargeted into a fresh port.
14
+ *
15
+ * ID mapping (documented v1 contract): durable ids are Seq-orderable
16
+ * branded numbers; the app's wire protocol (pi `SessionEntry` shape) keys
17
+ * entries by STRING ids and resumes paging with `since: <string leafId>`.
18
+ * The mirror stringifies every entry id (`String(id)`, `<id>.<n>` for an
19
+ * entry's later model messages) and the session-control verbs parse the
20
+ * app's string ids back with `entryIdOf`.
21
+ */
22
+ import type { Context } from "@earendil-works/chord";
23
+ import type { Conversation } from "@earendil-works/pi-durable";
24
+ import type { AgentSessionPort, EntriesPage, EntryRange, NotifyLevel, SessionControlPort, SubmitContent, SubmitOptions, SubmitResult } from "@geohar/un-bien/core";
25
+ import { type DurableTranscriptMirror } from "./mirror.js";
26
+ export { DurableTranscriptMirror } from "./mirror.js";
27
+ export interface DurableSessionPortOptions {
28
+ /** The conversation this port serves (stable id across restarts). */
29
+ conversation: Conversation;
30
+ /** Chord context for the async handoffs (BACKGROUND_CONTEXT in the host). */
31
+ context: Context;
32
+ /** Transcript mirror fed by the host's ConversationViewSource. */
33
+ mirror: DurableTranscriptMirror;
34
+ /** The conversation's working directory (host-configured). */
35
+ cwd: string;
36
+ /** Live busy flag, maintained by the host from run_start/run_end events. */
37
+ busy(): boolean;
38
+ /** Append a custom entry via a committed app entry (best-effort). */
39
+ appendAppEntry(entry: {
40
+ customType: string;
41
+ content: string;
42
+ display?: boolean;
43
+ details?: unknown;
44
+ }): void;
45
+ /** Log surface for notify + asynchronous submission failures. */
46
+ log(message: string, level?: NotifyLevel): void;
47
+ /** Event-plane fanout hook (the host's ConversationViewSource); the port's
48
+ * subscribeEvents is additive on it. Absent = no events surface. */
49
+ subscribe?(handler: (event: string, payload: unknown) => void): () => void;
50
+ }
51
+ export declare class DurableSessionPort implements AgentSessionPort {
52
+ private readonly opts;
53
+ constructor(opts: DurableSessionPortOptions);
54
+ private get conversation();
55
+ get sessionId(): string | null;
56
+ get cwd(): string;
57
+ get streaming(): boolean;
58
+ submit(content: SubmitContent, submitOpts?: SubmitOptions): SubmitResult;
59
+ getEntries(range?: EntryRange): EntriesPage;
60
+ abort(): boolean;
61
+ compact(opts?: {
62
+ customInstructions?: string;
63
+ }): void;
64
+ notify(message: string, level?: NotifyLevel): void;
65
+ appendEntry(entry: {
66
+ customType: string;
67
+ content: string;
68
+ display?: boolean;
69
+ details?: unknown;
70
+ }): void;
71
+ subscribeEvents(handler: (event: string, payload: unknown) => void): () => void;
72
+ }
73
+ export interface DurableSessionControlOptions {
74
+ /** Create a conversation on the harness (the pi `new_session` analog). */
75
+ createConversation(context: Context): Promise<Conversation>;
76
+ /** Called with the NEW conversation so the host retargets bridge + port. */
77
+ retarget(conversation: Conversation): Promise<void>;
78
+ /** The CURRENT conversation (for fork). */
79
+ current(): Conversation;
80
+ context: Context;
81
+ /** Graceful host shutdown (close relay + harness; the process exits). */
82
+ shutdownHost(): Promise<void>;
83
+ }
84
+ export declare class DurableSessionControl implements SessionControlPort {
85
+ private readonly opts;
86
+ constructor(opts: DurableSessionControlOptions);
87
+ newSession(): Promise<void>;
88
+ fork(entryId: string, _opts?: {
89
+ position?: "at" | "before";
90
+ requestId?: string;
91
+ }): Promise<void>;
92
+ navigateTree(_entryId: string): Promise<void>;
93
+ shutdown(): Promise<void>;
94
+ }
package/dist/port.js ADDED
@@ -0,0 +1,121 @@
1
+ import { entryIdOf } from "./mirror.js";
2
+ export { DurableTranscriptMirror } from "./mirror.js";
3
+ export class DurableSessionPort {
4
+ opts;
5
+ constructor(opts) {
6
+ this.opts = opts;
7
+ }
8
+ get conversation() {
9
+ return this.opts.conversation;
10
+ }
11
+ get sessionId() {
12
+ return String(this.opts.conversation.id);
13
+ }
14
+ get cwd() {
15
+ return this.opts.cwd;
16
+ }
17
+ get streaming() {
18
+ return this.opts.busy();
19
+ }
20
+ submit(content, submitOpts) {
21
+ // Fire-and-forget, matching the port contract: a refused handoff must be
22
+ // a synchronous answer, but durable's submit is a commit — the wait
23
+ // happens on the harness side and the reply rides the event plane.
24
+ // `steer` maps to durable's whenBusy:"steer" (join the running work);
25
+ // everything else queues as a follow-up, pi's deliverAs:"followUp".
26
+ // The port's content blocks are structurally pi-ai's TextContent |
27
+ // ImageContent — the same blocks durable's user input accepts.
28
+ const draft = {
29
+ type: "input",
30
+ content,
31
+ whenBusy: submitOpts?.deliverAs === "steer" ? "steer" : undefined,
32
+ };
33
+ void this.conversation
34
+ .submit(draft, this.opts.context)
35
+ .then((submission) => submission.wait(this.opts.context))
36
+ .then((settled) => {
37
+ const record = settled;
38
+ if (record?.status === "unanswered") {
39
+ this.opts.log(`submission unanswered: ${String(record.reason ?? "unknown reason")}`, "warning");
40
+ }
41
+ })
42
+ .catch((err) => {
43
+ const detail = err instanceof Error ? err.message : String(err);
44
+ this.opts.log(`submission failed: ${detail}`, "error");
45
+ });
46
+ return { ok: true };
47
+ }
48
+ getEntries(range) {
49
+ return this.opts.mirror.page(range);
50
+ }
51
+ abort() {
52
+ // The port reports whether an abort was REQUESTED (the port surface is
53
+ // synchronous; durable's abort resolves once the conversation is idle).
54
+ if (!this.opts.busy())
55
+ return false;
56
+ // Fire-and-forget per the port contract — but not unguarded: a store
57
+ // failure must log, not surface as an unhandled rejection.
58
+ void this.conversation.abort(this.opts.context).catch((err) => {
59
+ const detail = err instanceof Error ? err.message : String(err);
60
+ this.opts.log(`abort failed: ${detail}`, "warning");
61
+ });
62
+ return true;
63
+ }
64
+ compact(opts) {
65
+ // Manual compaction always works on durable; it runs as a task and the
66
+ // bridge forwards its completion via the compaction events.
67
+ void this.conversation
68
+ .compact(opts?.customInstructions, this.opts.context)
69
+ .catch((err) => {
70
+ const detail = err instanceof Error ? err.message : String(err);
71
+ this.opts.log(`compact failed: ${detail}`, "warning");
72
+ });
73
+ }
74
+ notify(message, level = "info") {
75
+ this.opts.log(message, level);
76
+ }
77
+ appendEntry(entry) {
78
+ // Custom entries are data-only sidecars on the durable side (there is
79
+ // no TUI renderer here); committed best-effort through the host hook.
80
+ this.opts.appendAppEntry(entry);
81
+ }
82
+ subscribeEvents(handler) {
83
+ // The event plane is owned by the host's ConversationViewSource (one watch
84
+ // per conversation); the bridge fans translations to every subscriber.
85
+ // Registering through the port is additive on that fanout.
86
+ return this.opts.subscribe?.(handler) ?? (() => undefined);
87
+ }
88
+ }
89
+ export class DurableSessionControl {
90
+ opts;
91
+ constructor(opts) {
92
+ this.opts = opts;
93
+ }
94
+ async newSession() {
95
+ const created = await this.opts.createConversation(this.opts.context);
96
+ await this.opts.retarget(created);
97
+ }
98
+ async fork(entryId, _opts) {
99
+ // Durable forks always branch AT the entry (there is no "before"
100
+ // placement); the requestId correlation is app-plane state the bridge
101
+ // tracks, not a conversation concern. The app's string id parses back
102
+ // to the durable Seq id (see the module header's ID-mapping note).
103
+ const at = entryIdOf(entryId);
104
+ if (at === undefined)
105
+ throw new Error(`Entry not found: ${entryId}`);
106
+ const forked = await this.opts.current().fork(at, {
107
+ ownership: { kind: "ownerless" },
108
+ }, this.opts.context);
109
+ await this.opts.retarget(forked);
110
+ }
111
+ async navigateTree(_entryId) {
112
+ // Durable transcripts are linear: branching happens by forking a NEW
113
+ // conversation (which keeps a stable id of its own). In-place leaf
114
+ // moves have no durable equivalent.
115
+ throw new Error("navigateTree is not implemented on the durable binding — fork instead");
116
+ }
117
+ async shutdown() {
118
+ await this.opts.shutdownHost();
119
+ }
120
+ }
121
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type ToolRegistration } from "@earendil-works/pi-durable";
2
+ import type { MeshToolDef } from "@geohar/un-bien/core";
3
+ export declare class DurableToolRegistrar {
4
+ /** Registered so far — the host extension installs these in one bundle. */
5
+ readonly registrations: ToolRegistration[];
6
+ registerTool(def: MeshToolDef): void;
7
+ }
@@ -0,0 +1,31 @@
1
+ import { defineTool, } from "@earendil-works/pi-durable";
2
+ export class DurableToolRegistrar {
3
+ /** Registered so far — the host extension installs these in one bundle. */
4
+ registrations = [];
5
+ registerTool(def) {
6
+ this.registrations.push(defineTool({
7
+ name: def.name,
8
+ description: def.promptSnippet
9
+ ? `${def.description}\n\nUsage: ${def.promptSnippet}`
10
+ : def.description,
11
+ parameters: def.parameters,
12
+ // SAFETY: the neutral defs build TypeBox schemas compatible with
13
+ // durable's validation; args arrive already validated, so the
14
+ // `never` parameter is only the generic inference's narrow view.
15
+ execute: async (args, _api, _context) => {
16
+ const outcome = await def.execute(args, undefined);
17
+ return {
18
+ output: outcome.content,
19
+ ...(outcome.details !== undefined
20
+ ? // SAFETY: the mesh tools' details payloads are all JSON
21
+ // trees (ACK results, MeshMessage records, rosters) — the
22
+ // neutral def types them `unknown` only to stay
23
+ // harness-neutral.
24
+ { details: outcome.details }
25
+ : {}),
26
+ };
27
+ },
28
+ }));
29
+ }
30
+ }
31
+ //# sourceMappingURL=registrar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registrar.js","sourceRoot":"","sources":["../src/registrar.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,UAAU,GAGX,MAAM,4BAA4B,CAAA;AAGnC,MAAM,OAAO,oBAAoB;IAC/B,2EAA2E;IAClE,aAAa,GAAuB,EAAE,CAAA;IAE/C,YAAY,CAAC,GAAgB;QAC3B,IAAI,CAAC,aAAa,CAAC,IAAI,CACrB,UAAU,CAAC;YACT,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,aAAa;gBAC5B,CAAC,CAAC,GAAG,GAAG,CAAC,WAAW,cAAc,GAAG,CAAC,aAAa,EAAE;gBACrD,CAAC,CAAC,GAAG,CAAC,WAAW;YACnB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,iEAAiE;YACjE,8DAA8D;YAC9D,iEAAiE;YACjE,OAAO,EAAE,KAAK,EAAE,IAAW,EAAE,IAAI,EAAE,QAAQ,EAAgC,EAAE;gBAC3E,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;gBAClD,OAAO;oBACL,MAAM,EAAE,OAAO,CAAC,OAAO;oBACvB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;wBAC/B,CAAC,CAAC,wDAAwD;4BACxD,0DAA0D;4BAC1D,gDAAgD;4BAChD,mBAAmB;4BACnB,EAAE,OAAO,EAAE,OAAO,CAAC,OAAoB,EAAE;wBAC3C,CAAC,CAAC,EAAE,CAAC;iBACR,CAAA;YACH,CAAC;SACF,CAAC,CACH,CAAA;IACH,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The default thinking level for durable conversations.
3
+ *
4
+ * pi-durable starts every conversation's agent at thinking level "off", so a
5
+ * reasoning model answers without reasoning blocks and its working
6
+ * narration arrives as plain text. pi starts sessions at the user's
7
+ * `defaultThinkingLevel` ("medium" unless configured). The bridge applies
8
+ * the same default to each conversation it attaches whose level is still
9
+ * "off", clamped to what the conversation's model supports (glm-5.3 has no
10
+ * "medium", so it gets "high"). A level other than "off" is left alone.
11
+ */
12
+ import type { Context } from "@earendil-works/chord";
13
+ import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
14
+ import { type Models } from "@earendil-works/pi-ai/models";
15
+ import type { Conversation } from "@earendil-works/pi-durable";
16
+ /** pi's default when settings name none. */
17
+ export declare const DEFAULT_THINKING_LEVEL: ModelThinkingLevel;
18
+ /** Parse a thinking level from a flag or env value; undefined when invalid. */
19
+ export declare function parseThinkingLevel(value: string | undefined): ModelThinkingLevel | undefined;
20
+ /**
21
+ * Raise `conversation` from "off" to `level` (clamped to its model).
22
+ * Returns the level set, or undefined when nothing changed.
23
+ */
24
+ export declare function ensureThinkingLevel(conversation: Conversation, models: Models, level: ModelThinkingLevel, context: Context): Promise<ModelThinkingLevel | undefined>;
@@ -0,0 +1,36 @@
1
+ import { clampThinkingLevel } from "@earendil-works/pi-ai/models";
2
+ /** pi's default when settings name none. */
3
+ export const DEFAULT_THINKING_LEVEL = "medium";
4
+ const LEVELS = [
5
+ "off",
6
+ "minimal",
7
+ "low",
8
+ "medium",
9
+ "high",
10
+ "xhigh",
11
+ "max",
12
+ ];
13
+ /** Parse a thinking level from a flag or env value; undefined when invalid. */
14
+ export function parseThinkingLevel(value) {
15
+ return LEVELS.find((level) => level === value);
16
+ }
17
+ /**
18
+ * Raise `conversation` from "off" to `level` (clamped to its model).
19
+ * Returns the level set, or undefined when nothing changed.
20
+ */
21
+ export async function ensureThinkingLevel(conversation, models, level, context) {
22
+ if (level === "off")
23
+ return undefined;
24
+ const agent = await conversation.agent(context);
25
+ if (agent.thinkingLevel !== "off")
26
+ return undefined;
27
+ const model = agent.model !== undefined
28
+ ? models.getModel(agent.model.provider, agent.model.modelId)
29
+ : undefined;
30
+ const target = model !== undefined ? clampThinkingLevel(model, level) : level;
31
+ if (target === "off")
32
+ return undefined;
33
+ await conversation.configure({ thinkingLevel: target }, context);
34
+ return target;
35
+ }
36
+ //# sourceMappingURL=thinking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking.js","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,kBAAkB,EAAe,MAAM,8BAA8B,CAAA;AAG9E,4CAA4C;AAC5C,MAAM,CAAC,MAAM,sBAAsB,GAAuB,QAAQ,CAAA;AAElE,MAAM,MAAM,GAAkC;IAC5C,KAAK;IACL,SAAS;IACT,KAAK;IACL,QAAQ;IACR,MAAM;IACN,OAAO;IACP,KAAK;CACN,CAAA;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAChC,KAAyB;IAEzB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAA;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,YAA0B,EAC1B,MAAc,EACd,KAAyB,EACzB,OAAgB;IAEhB,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,SAAS,CAAA;IACrC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAC/C,IAAI,KAAK,CAAC,aAAa,KAAK,KAAK;QAAE,OAAO,SAAS,CAAA;IACnD,MAAM,KAAK,GACT,KAAK,CAAC,KAAK,KAAK,SAAS;QACvB,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5D,CAAC,CAAC,SAAS,CAAA;IACf,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC7E,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,SAAS,CAAA;IACtC,MAAM,YAAY,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAA;IAChE,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Translate successive durable conversation-view states into pi's event
3
+ * plane: the event names and payloads `pi.on()` delivers, which the shared
4
+ * rpc-envelope producer turns into `pi --mode rpc` frames. The app therefore
5
+ * sees the same traffic from a durable conversation as from a pi session.
6
+ *
7
+ * pi-durable publishes state, not events: each view value holds the entries
8
+ * appended since the last one and the `pi.live` document (run control, the
9
+ * streaming answer, the tool round). `feed()` diffs that against what it has
10
+ * already emitted:
11
+ *
12
+ * pi.live.run appears / disappears → agent_start / agent_end + agent_settled
13
+ * first message of a turn → turn_start
14
+ * pi.user entry → message_start + message_end
15
+ * pi.live.generation.message → message_start, then message_update per
16
+ * block: text/thinking start, delta, end;
17
+ * toolcall_start, toolcall_end
18
+ * pi.assistant entry → remaining block deltas and ends, then
19
+ * message_end; with no tool calls, turn_end
20
+ * pi.live.tools slot running → tool_execution_start
21
+ * slot output/details change → tool_execution_update {content, details}
22
+ * pi.tool-result entry → tool_execution_end {content, details} with
23
+ * the message's isError, then message_start +
24
+ * message_end; the round's last → turn_end
25
+ * slot done without an entry (faulted) → tool_execution_end, isError
26
+ * pi.compaction entry → session_compact (the summary)
27
+ * un-bien.app entry, display → message_start + message_end, role custom
28
+ * pi.live.runFailure (new) → onRunFailure (the bridge records it as an
29
+ * app entry, which then arrives as above)
30
+ *
31
+ * No toolcall_delta: the view holds a call's parsed arguments, not pi's raw
32
+ * argument text, and the app builds tool cards from tool_execution_start.
33
+ */
34
+ import type { EntryRecord, LiveState, RunFailure } from "@earendil-works/pi-durable";
35
+ type Emit = (name: string, payload: Record<string, unknown>) => void;
36
+ export interface ViewTranslatorOptions {
37
+ emit: Emit;
38
+ onBusyChange?(busy: boolean): void;
39
+ onRunFailure?(failure: RunFailure): void;
40
+ }
41
+ export declare class ViewTranslator {
42
+ private readonly opts;
43
+ private busy;
44
+ private inTurn;
45
+ /** Blocks of the streaming answer already emitted, and which are closed. */
46
+ private stream;
47
+ private lastAssistant;
48
+ private pendingCalls;
49
+ private turnResults;
50
+ private readonly toolsStarted;
51
+ private readonly toolsEnded;
52
+ private readonly toolProgress;
53
+ private runFailureTask;
54
+ constructor(opts: ViewTranslatorOptions);
55
+ /**
56
+ * Take the state at attachment without emitting: a run in flight sets the
57
+ * busy flag, and a run failure already recorded is not reported again.
58
+ */
59
+ prime(live: LiveState | undefined): void;
60
+ /** Emit what changed: `added` are the entries new since the last call. */
61
+ feed(added: readonly EntryRecord[], live: LiveState | undefined): void;
62
+ private entry;
63
+ /** A message that arrives complete: start and end in one go. Only a run's
64
+ * messages open a turn; a notice written while idle (a run failure, a
65
+ * mesh notice) is just the message. */
66
+ private whole;
67
+ private assistant;
68
+ private toolResult;
69
+ private streaming;
70
+ /**
71
+ * Emit the block events that take the emitted blocks to `blocks`. A block
72
+ * ends when a later one starts, or, with `final`, at the message's end.
73
+ */
74
+ private diffBlocks;
75
+ private endBlock;
76
+ private slot;
77
+ private startTool;
78
+ private argsOf;
79
+ private beginTurn;
80
+ private endTurn;
81
+ private endRun;
82
+ }
83
+ export {};