larkin 0.5.4 → 0.5.6

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 (31) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +7 -1
  3. package/artifacts/release/THIRD_PARTY_NOTICES.txt +1 -100
  4. package/dist/agent/context-prompt.cjs +19 -17
  5. package/dist/agent/context-prompt.mjs +18 -16
  6. package/dist/runtime/pi-state-dir.cjs +105 -0
  7. package/dist/runtime/pi-state-dir.mjs +94 -0
  8. package/dist/runtime/pi-tmux-extension.mjs +137 -0
  9. package/dist/runtime/pi-tmux-injection.mjs +49 -0
  10. package/dist/runtime/pi-tmux.bundle.js +600 -0
  11. package/dist/runtime/pi-tmux.mjs +408 -0
  12. package/dist/runtime/runtime-adapters.mjs +34 -114
  13. package/dist/runtime/runtime-host.cjs +3 -413
  14. package/dist/runtime/runtime-host.mjs +2 -412
  15. package/package.json +2 -8
  16. package/dist/runtime/pi-bash-timeout-extension.mjs +0 -60
  17. package/dist/runtime/pi-bash-timeout-injection.mjs +0 -54
  18. package/dist/runtime/pi-bash-timeout.bundle.js +0 -43
  19. package/dist/runtime/pi-extension-api.mjs +0 -1
  20. package/dist/runtime/pi-subagent-injection.mjs +0 -290
  21. package/dist/runtime/pi-subagent-ledger.cjs +0 -632
  22. package/dist/runtime/pi-subagent-ledger.mjs +0 -594
  23. package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +0 -43
  24. package/dist/runtime/pi-subagent-record-watchdog.bundle.js +0 -183
  25. package/dist/runtime/pi-subagent-record-watchdog.mjs +0 -46
  26. package/dist/runtime/pi-subagents-notification.mjs +0 -175
  27. package/dist/runtime/pi-subagents.bundle.js +0 -9110
  28. package/dist/runtime/pi-supervised-command-extension.mjs +0 -72
  29. package/dist/runtime/pi-supervised-command.bundle.js +0 -4735
  30. package/dist/runtime/pi-supervised-command.mjs +0 -420
  31. package/patches/@tintinweb%2Fpi-subagents@0.14.3.patch +0 -945
@@ -0,0 +1,137 @@
1
+ import crypto from "node:crypto";
2
+ import { Type } from "typebox";
3
+ import { createLarkinTmux, DEFAULT_BASH_WAIT_SECONDS, ForeignTmuxTaskError, formatTmuxTaskText, LARKIN_TMUX_COMPLETION_TYPE, tmuxAvailable, } from "./pi-tmux.mjs";
4
+ function resultOf(snapshot) {
5
+ return {
6
+ content: [{ type: "text", text: formatTmuxTaskText(snapshot) }],
7
+ details: {
8
+ taskId: snapshot.taskId,
9
+ status: snapshot.status,
10
+ exitCode: snapshot.exitCode,
11
+ output: snapshot.output,
12
+ },
13
+ };
14
+ }
15
+ function errorResult(message) {
16
+ return {
17
+ content: [{ type: "text", text: message }],
18
+ details: { taskId: "", status: "failed", exitCode: null, output: message },
19
+ };
20
+ }
21
+ export function registerLarkinTmuxExtension(pi) {
22
+ if (!tmuxAvailable())
23
+ return false;
24
+ const stateDir = process.env.LARKIN_STATE_DIR || `${process.cwd()}/.larkin`;
25
+ const agentId = process.env.LARKIN_AGENT_ID || "agent";
26
+ const instanceId = process.env.LARKIN_TMUX_INSTANCE_ID
27
+ || process.env.LARKIN_RUNTIME_OBSERVATION_GENERATION
28
+ || crypto.randomUUID();
29
+ const tmux = createLarkinTmux({ stateDir, agentId, instanceId, env: process.env });
30
+ const watchers = new Map();
31
+ const notified = new Set();
32
+ const stopWatchers = () => {
33
+ for (const timer of watchers.values())
34
+ clearInterval(timer);
35
+ watchers.clear();
36
+ };
37
+ const notifyOnce = (snapshot) => {
38
+ if (snapshot.status === "running" || notified.has(snapshot.taskId))
39
+ return;
40
+ notified.add(snapshot.taskId);
41
+ pi.sendMessage({
42
+ customType: LARKIN_TMUX_COMPLETION_TYPE,
43
+ content: formatTmuxTaskText(snapshot),
44
+ display: true,
45
+ details: {
46
+ taskId: snapshot.taskId,
47
+ exitCode: snapshot.exitCode,
48
+ output: snapshot.output,
49
+ },
50
+ }, { triggerTurn: true, deliverAs: "followUp" });
51
+ };
52
+ const watch = (taskId) => {
53
+ if (watchers.has(taskId) || notified.has(taskId))
54
+ return;
55
+ const timer = setInterval(() => {
56
+ try {
57
+ const snapshot = tmux.peek(taskId);
58
+ if (snapshot.status === "running")
59
+ return;
60
+ clearInterval(timer);
61
+ watchers.delete(taskId);
62
+ notifyOnce(snapshot);
63
+ }
64
+ catch {
65
+ clearInterval(timer);
66
+ watchers.delete(taskId);
67
+ }
68
+ }, 250);
69
+ watchers.set(taskId, timer);
70
+ };
71
+ pi.on("session_shutdown", () => {
72
+ stopWatchers();
73
+ });
74
+ pi.registerTool({
75
+ name: "bash",
76
+ label: "bash",
77
+ description: "Run a command in a detached tmux window. timeout is a wait window only and never hard-kills. background:true returns immediately.",
78
+ parameters: Type.Object({
79
+ command: Type.String(),
80
+ timeout: Type.Optional(Type.Number()),
81
+ background: Type.Optional(Type.Boolean()),
82
+ }),
83
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
84
+ const snapshot = tmux.start(params.command, ctx.cwd);
85
+ if (params.background === true) {
86
+ watch(snapshot.taskId);
87
+ return resultOf(tmux.peek(snapshot.taskId));
88
+ }
89
+ const timeout = typeof params.timeout === "number" && params.timeout >= 0 ? params.timeout : DEFAULT_BASH_WAIT_SECONDS;
90
+ const waited = await tmux.wait(snapshot.taskId, timeout, signal);
91
+ if (waited.status === "running")
92
+ watch(waited.taskId);
93
+ return resultOf(waited);
94
+ },
95
+ });
96
+ pi.registerTool({
97
+ name: "tmux",
98
+ label: "tmux",
99
+ description: "List, peek, or kill Larkin-owned tmux tasks for this Agent. Foreign task IDs are rejected.",
100
+ parameters: Type.Object({
101
+ action: Type.Union([Type.Literal("list"), Type.Literal("peek"), Type.Literal("kill")]),
102
+ taskId: Type.Optional(Type.String()),
103
+ }),
104
+ async execute(_toolCallId, params) {
105
+ try {
106
+ if (params.action === "list") {
107
+ const tasks = tmux.list();
108
+ return {
109
+ content: [{ type: "text", text: tasks.map(formatTmuxTaskText).join("\n---\n") || "no owned tmux tasks" }],
110
+ details: { taskId: "", status: "completed", exitCode: 0, output: "" },
111
+ };
112
+ }
113
+ if (!params.taskId)
114
+ return errorResult("taskId is required for peek and kill");
115
+ if (params.action === "peek")
116
+ return resultOf(tmux.peek(params.taskId));
117
+ const killed = tmux.kill(params.taskId);
118
+ notified.add(params.taskId);
119
+ const timer = watchers.get(params.taskId);
120
+ if (timer) {
121
+ clearInterval(timer);
122
+ watchers.delete(params.taskId);
123
+ }
124
+ return resultOf(killed);
125
+ }
126
+ catch (error) {
127
+ if (error instanceof ForeignTmuxTaskError)
128
+ return errorResult(error.message);
129
+ throw error;
130
+ }
131
+ },
132
+ });
133
+ return true;
134
+ }
135
+ export default function larkinTmuxExtension(pi) {
136
+ registerLarkinTmuxExtension(pi);
137
+ }
@@ -0,0 +1,49 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { ensureOwnedTree, writePrivateAtomic } from "./pi-state-dir.mjs";
5
+ import { tmuxAvailable } from "./pi-tmux.mjs";
6
+ const BUNDLE_NAME = "pi-tmux.bundle.js";
7
+ export function materializeEmbeddedPiTmuxBundle(configDir) {
8
+ const embedded = globalThis.__LARKIN_EMBEDDED_PI_TMUX_BUNDLE__;
9
+ if (!embedded || !configDir)
10
+ return null;
11
+ try {
12
+ const dir = ensureOwnedTree(path.resolve(configDir), ["providers", "pi", "extensions"]);
13
+ const target = path.join(dir, BUNDLE_NAME);
14
+ try {
15
+ if (fs.lstatSync(target).isSymbolicLink())
16
+ throw new Error("pi-tmux bundle must not be a symlink");
17
+ if (fs.readFileSync(target, "utf8") === embedded)
18
+ return target;
19
+ }
20
+ catch (error) {
21
+ if (error.code !== "ENOENT")
22
+ throw error;
23
+ }
24
+ writePrivateAtomic(target, embedded);
25
+ return target;
26
+ }
27
+ catch (error) {
28
+ throw new Error("Could not materialize the Larkin tmux extension", { cause: error });
29
+ }
30
+ }
31
+ export function bundledPiTmuxExtensionPath(configDir) {
32
+ try {
33
+ const resolved = fileURLToPath(new URL(`./${BUNDLE_NAME}`, import.meta.url));
34
+ if (fs.existsSync(resolved))
35
+ return resolved;
36
+ }
37
+ catch {
38
+ /* fall through to embedded */
39
+ }
40
+ return materializeEmbeddedPiTmuxBundle(configDir);
41
+ }
42
+ export function resolvePiTmuxExtensionArg(input, resolveBundle = () => bundledPiTmuxExtensionPath(input.env.LARKIN_CONFIG_DIR), available = tmuxAvailable) {
43
+ if (input.platform === "win32" || !available(input.env, input.platform))
44
+ return null;
45
+ const bundle = resolveBundle();
46
+ if (!bundle)
47
+ throw new Error("Larkin tmux extension bundle is missing; rebuild or reinstall Larkin");
48
+ return bundle;
49
+ }