larkin 0.4.18 → 0.4.20
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.
- package/dist/agent/context-prompt.cjs +2 -2
- package/dist/agent/context-prompt.mjs +2 -2
- package/dist/runtime/pi-inline-extensions.mjs +44 -8
- package/dist/runtime/pi-subagent-injection.mjs +103 -10
- package/dist/runtime/pi-subagent-ledger.cjs +632 -0
- package/dist/runtime/pi-subagent-ledger.mjs +594 -0
- package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +43 -0
- package/dist/runtime/pi-subagent-record-watchdog.bundle.js +183 -0
- package/dist/runtime/pi-subagent-record-watchdog.mjs +46 -0
- package/dist/runtime/pi-subagents-notification.mjs +14 -0
- package/dist/runtime/pi-subagents.bundle.js +131 -58
- package/dist/runtime/pi-supervised-command-extension.mjs +72 -0
- package/dist/runtime/pi-supervised-command.bundle.js +4735 -0
- package/dist/runtime/pi-supervised-command.mjs +420 -0
- package/dist/runtime/runtime-adapters.mjs +52 -10
- package/dist/runtime/runtime-host.cjs +187 -14
- package/dist/runtime/runtime-host.mjs +187 -14
- package/package.json +1 -1
- package/patches/@tintinweb%2Fpi-subagents@0.14.3.patch +712 -16
|
@@ -16,7 +16,7 @@ const agent_cli_capabilities_js_1 = require("./agent-cli-capabilities.cjs");
|
|
|
16
16
|
* 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
|
|
17
17
|
* 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
|
|
18
18
|
*/
|
|
19
|
-
exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-
|
|
19
|
+
exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v25";
|
|
20
20
|
/**
|
|
21
21
|
* Agent 间协作唤醒引导(issue #75):纯文本 @ 不会产生飞书 mention 事件,
|
|
22
22
|
* 必须由模型直接构造真实的 mention 元素;`--mention` 扩展参数已移除。
|
|
@@ -106,7 +106,7 @@ class ContextPromptBuilder {
|
|
|
106
106
|
"",
|
|
107
107
|
"## Background subagents (pi)",
|
|
108
108
|
"Long-running, independent work MUST use the Agent tool with run_in_background: true. It is the ONLY supported background mechanism. nohup, `&`, disown, and shell background jobs are forbidden for delegated work.",
|
|
109
|
-
"Foreground bash is hard-capped at 60 seconds. Never pass a bash timeout above 60
|
|
109
|
+
"Foreground bash is hard-capped at 60 seconds. Never pass a bash timeout above 60. Never use nested bash to outlive 60s. If work must outlive 60s, Agent({ prompt, description, run_in_background: true }) and inside that background agent use supervised_start (executable+args, shell:false) once, then loop supervised_wait (timeout <= 60). Wait timeout returns still running and does NOT kill the process; Steer can arrive after each wait. Total lifetime is 600s. Abort/cancel/shutdown reaps the process tree. Do not use nohup / '&' / disown.",
|
|
110
110
|
"Correct pattern:",
|
|
111
111
|
"1. Call Agent with arguments like {\"prompt\": \"<task>\", \"description\": \"<short label>\", \"run_in_background\": true}.",
|
|
112
112
|
"2. The tool returns an agent id immediately. Report it to the user and end the turn.",
|
|
@@ -13,7 +13,7 @@ import { agentCliPromptCapabilities } from "./agent-cli-capabilities.mjs";
|
|
|
13
13
|
* 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
|
|
14
14
|
* 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
|
|
15
15
|
*/
|
|
16
|
-
export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-
|
|
16
|
+
export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v25";
|
|
17
17
|
/**
|
|
18
18
|
* Agent 间协作唤醒引导(issue #75):纯文本 @ 不会产生飞书 mention 事件,
|
|
19
19
|
* 必须由模型直接构造真实的 mention 元素;`--mention` 扩展参数已移除。
|
|
@@ -103,7 +103,7 @@ export class ContextPromptBuilder {
|
|
|
103
103
|
"",
|
|
104
104
|
"## Background subagents (pi)",
|
|
105
105
|
"Long-running, independent work MUST use the Agent tool with run_in_background: true. It is the ONLY supported background mechanism. nohup, `&`, disown, and shell background jobs are forbidden for delegated work.",
|
|
106
|
-
"Foreground bash is hard-capped at 60 seconds. Never pass a bash timeout above 60
|
|
106
|
+
"Foreground bash is hard-capped at 60 seconds. Never pass a bash timeout above 60. Never use nested bash to outlive 60s. If work must outlive 60s, Agent({ prompt, description, run_in_background: true }) and inside that background agent use supervised_start (executable+args, shell:false) once, then loop supervised_wait (timeout <= 60). Wait timeout returns still running and does NOT kill the process; Steer can arrive after each wait. Total lifetime is 600s. Abort/cancel/shutdown reaps the process tree. Do not use nohup / '&' / disown.",
|
|
107
107
|
"Correct pattern:",
|
|
108
108
|
"1. Call Agent with arguments like {\"prompt\": \"<task>\", \"description\": \"<short label>\", \"run_in_background\": true}.",
|
|
109
109
|
"2. The tool returns an agent id immediately. Report it to the user and end the turn.",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { pathToFileURL } from "node:url";
|
|
2
4
|
import bashTimeoutExtension from "./pi-bash-timeout-extension.mjs";
|
|
3
|
-
import { bundledPiSubagentExtensionPath } from "./pi-subagent-injection.mjs";
|
|
5
|
+
import { bundledPiSubagentExtensionPath, materializeEmbeddedPiSubagentBundle } from "./pi-subagent-injection.mjs";
|
|
6
|
+
import piSubagentRecordWatchdog from "./pi-subagent-record-watchdog.mjs";
|
|
4
7
|
/**
|
|
5
8
|
* Load the prebuilt, patched subagent bundle shipped in dist/ rather than the
|
|
6
9
|
* package-manager copy. Standalone binaries additionally load the patched
|
|
@@ -8,11 +11,15 @@ import { bundledPiSubagentExtensionPath } from "./pi-subagent-injection.mjs";
|
|
|
8
11
|
* continue to use the prebuilt patched bundle.
|
|
9
12
|
*/
|
|
10
13
|
async function loadBundledPiSubagentExtension() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Compiled standalone already has the patched package in Bun's module graph.
|
|
15
|
+
// The materialized sibling bundle externalizes @earendil-works/pi-coding-agent
|
|
16
|
+
// and cannot be imported from LARKIN_CONFIG_DIR.
|
|
17
|
+
const compiled = globalThis.__LARKIN_COMPILED_PI_SUBAGENTS__;
|
|
18
|
+
if (compiled) {
|
|
19
|
+
if (typeof compiled !== "function" && typeof compiled.factory !== "function") {
|
|
20
|
+
throw new Error("Larkin compiled pi-subagents factory is missing; refusing to start builtin Pi");
|
|
21
|
+
}
|
|
22
|
+
return compiled;
|
|
16
23
|
}
|
|
17
24
|
const bundle = bundledPiSubagentExtensionPath(process.env.LARKIN_CONFIG_DIR);
|
|
18
25
|
if (!bundle)
|
|
@@ -22,6 +29,24 @@ async function loadBundledPiSubagentExtension() {
|
|
|
22
29
|
throw new Error("Larkin bounded pi-subagents bundle is invalid; refusing to start builtin Pi");
|
|
23
30
|
return loaded.default;
|
|
24
31
|
}
|
|
32
|
+
function asFn(mod, name) {
|
|
33
|
+
if (typeof mod === "function")
|
|
34
|
+
return mod;
|
|
35
|
+
const fallback = mod?.default;
|
|
36
|
+
if (typeof fallback === "function")
|
|
37
|
+
return fallback;
|
|
38
|
+
throw new Error(`${name} extension factory is missing`);
|
|
39
|
+
}
|
|
40
|
+
function wrapFactory(name, factory) {
|
|
41
|
+
return async (pi) => {
|
|
42
|
+
try {
|
|
43
|
+
await factory(pi);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw error instanceof Error ? error : new Error(`${name}: ${String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
25
50
|
/** Builtin Pi extensions are loaded as code, never through Pi's path/data-URL loader. */
|
|
26
51
|
const bundledSubagentsExtension = async (pi) => {
|
|
27
52
|
const extension = await loadBundledPiSubagentExtension();
|
|
@@ -30,12 +55,23 @@ const bundledSubagentsExtension = async (pi) => {
|
|
|
30
55
|
else
|
|
31
56
|
await extension.factory(pi);
|
|
32
57
|
};
|
|
58
|
+
// Watchdog must register session_shutdown before the bundled subagent extension
|
|
59
|
+
// so the final sweep can still read AgentManager.getRecord and bridge consumed
|
|
60
|
+
// terminals before manager teardown.
|
|
33
61
|
export const BUILTIN_PI_EXTENSION_FACTORIES = Object.freeze([
|
|
34
|
-
|
|
35
|
-
|
|
62
|
+
wrapFactory("larkin-pi-subagent-watchdog", asFn(piSubagentRecordWatchdog, "larkin-pi-subagent-watchdog")),
|
|
63
|
+
wrapFactory("larkin-pi-subagents", bundledSubagentsExtension),
|
|
64
|
+
wrapFactory("larkin-pi-bash-timeout", asFn(bashTimeoutExtension, "larkin-pi-bash-timeout")),
|
|
36
65
|
]);
|
|
37
66
|
/** Invoke Pi RPC with Larkin's builtin-only extension factories. */
|
|
38
67
|
export async function invokeBuiltinPiRpc(piMain, rest) {
|
|
68
|
+
if (globalThis.__LARKIN_EMBEDDED_PI_SUBAGENTS_BUNDLE__ && process.env.LARKIN_CONFIG_DIR) {
|
|
69
|
+
const materialized = materializeEmbeddedPiSubagentBundle(process.env.LARKIN_CONFIG_DIR);
|
|
70
|
+
if (!materialized)
|
|
71
|
+
throw new Error("standalone pi-subagents bundle failed to materialize");
|
|
72
|
+
const supervised = path.join(path.dirname(materialized), "pi-supervised-command.bundle.js");
|
|
73
|
+
process.env.LARKIN_PI_SUPERVISED_BUNDLE = fs.realpathSync(supervised);
|
|
74
|
+
}
|
|
39
75
|
await piMain(["--mode", "rpc", ...rest], {
|
|
40
76
|
extensionFactories: [...BUILTIN_PI_EXTENSION_FACTORIES],
|
|
41
77
|
});
|
|
@@ -17,22 +17,113 @@ import { fileURLToPath } from "node:url";
|
|
|
17
17
|
* 把 embedded bundle 落盘到 <configDir>/providers/pi/extensions/pi-subagents.bundle.js(0700/0600)。
|
|
18
18
|
* 无 embedded 资产或 configDir 缺失时返回 null。
|
|
19
19
|
*/
|
|
20
|
+
function rejectSymlinkPath(target) {
|
|
21
|
+
let current = path.resolve(target);
|
|
22
|
+
for (;;) {
|
|
23
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
24
|
+
throw new Error("bundle path must not contain a symlink");
|
|
25
|
+
}
|
|
26
|
+
const parent = path.dirname(current);
|
|
27
|
+
if (parent === current)
|
|
28
|
+
break;
|
|
29
|
+
current = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function backupRegularFile(target) {
|
|
33
|
+
if (!fs.existsSync(target))
|
|
34
|
+
return null;
|
|
35
|
+
const stat = fs.lstatSync(target);
|
|
36
|
+
if (stat.isSymbolicLink())
|
|
37
|
+
throw new Error("bundle target must not be a symlink");
|
|
38
|
+
return { content: fs.readFileSync(target), mode: stat.mode & 0o777 };
|
|
39
|
+
}
|
|
40
|
+
function restoreRegularFile(target, backup) {
|
|
41
|
+
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) {
|
|
42
|
+
throw new Error("bundle target must not be a symlink");
|
|
43
|
+
}
|
|
44
|
+
if (!backup) {
|
|
45
|
+
if (fs.existsSync(target))
|
|
46
|
+
fs.unlinkSync(target);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(target, backup.content, { mode: backup.mode });
|
|
50
|
+
fs.chmodSync(target, backup.mode);
|
|
51
|
+
}
|
|
20
52
|
export function materializeEmbeddedPiSubagentBundle(configDir) {
|
|
21
53
|
const embedded = globalThis.__LARKIN_EMBEDDED_PI_SUBAGENTS_BUNDLE__;
|
|
54
|
+
const supervised = globalThis.__LARKIN_EMBEDDED_PI_SUPERVISED_COMMAND_BUNDLE__;
|
|
22
55
|
if (!embedded || !configDir)
|
|
23
56
|
return null;
|
|
24
|
-
|
|
25
|
-
|
|
57
|
+
if (!supervised)
|
|
58
|
+
throw new Error("supervised command bundle is missing");
|
|
59
|
+
const resolvedConfig = path.resolve(configDir);
|
|
60
|
+
fs.mkdirSync(resolvedConfig, { recursive: true, mode: 0o700 });
|
|
61
|
+
const canonical = fs.realpathSync(resolvedConfig);
|
|
62
|
+
const dir = path.join(canonical, "providers", "pi", "extensions");
|
|
63
|
+
const subTarget = path.join(dir, "pi-subagents.bundle.js");
|
|
64
|
+
const supTarget = path.join(dir, "pi-supervised-command.bundle.js");
|
|
65
|
+
const lockPath = path.join(dir, ".materialize.lock");
|
|
66
|
+
let lock;
|
|
26
67
|
try {
|
|
68
|
+
rejectSymlinkPath(dir);
|
|
27
69
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
70
|
+
rejectSymlinkPath(dir);
|
|
28
71
|
fs.chmodSync(dir, 0o700);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
72
|
+
lock = fs.openSync(lockPath, "wx", 0o600);
|
|
73
|
+
const oldSub = backupRegularFile(subTarget);
|
|
74
|
+
const oldSup = backupRegularFile(supTarget);
|
|
75
|
+
try {
|
|
76
|
+
writePrivateBundle(dir, "pi-subagents.bundle.js", embedded);
|
|
77
|
+
writePrivateBundle(dir, "pi-supervised-command.bundle.js", supervised);
|
|
78
|
+
return subTarget;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
restoreRegularFile(subTarget, oldSub);
|
|
82
|
+
restoreRegularFile(supTarget, oldSup);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof Error && error.message.includes("supervised command bundle is missing"))
|
|
88
|
+
throw error;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (lock !== undefined) {
|
|
93
|
+
try {
|
|
94
|
+
fs.closeSync(lock);
|
|
95
|
+
}
|
|
96
|
+
catch { /* ignore */ }
|
|
97
|
+
try {
|
|
98
|
+
fs.unlinkSync(lockPath);
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
34
101
|
}
|
|
35
|
-
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function writePrivateBundle(dir, name, embedded) {
|
|
105
|
+
const target = path.join(dir, name);
|
|
106
|
+
rejectSymlinkPath(target);
|
|
107
|
+
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) {
|
|
108
|
+
throw new Error("bundle target must not be a symlink");
|
|
109
|
+
}
|
|
110
|
+
if (!fs.existsSync(target) || fs.readFileSync(target, "utf8") !== embedded) {
|
|
111
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
112
|
+
fs.writeFileSync(temporary, embedded, { mode: 0o600, flag: "wx" });
|
|
113
|
+
fs.renameSync(temporary, target);
|
|
114
|
+
}
|
|
115
|
+
fs.chmodSync(target, 0o600);
|
|
116
|
+
return target;
|
|
117
|
+
}
|
|
118
|
+
export function materializeEmbeddedPiSupervisedCommandBundle(dirOrConfig) {
|
|
119
|
+
const embedded = globalThis.__LARKIN_EMBEDDED_PI_SUPERVISED_COMMAND_BUNDLE__;
|
|
120
|
+
if (!embedded || !dirOrConfig)
|
|
121
|
+
return null;
|
|
122
|
+
const dir = dirOrConfig.endsWith("extensions") ? dirOrConfig : path.join(path.resolve(dirOrConfig), "providers", "pi", "extensions");
|
|
123
|
+
try {
|
|
124
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
125
|
+
fs.chmodSync(dir, 0o700);
|
|
126
|
+
return writePrivateBundle(dir, "pi-supervised-command.bundle.js", embedded);
|
|
36
127
|
}
|
|
37
128
|
catch {
|
|
38
129
|
return null;
|
|
@@ -82,6 +173,7 @@ export function probeExternalPiVersion(piCommand, env) {
|
|
|
82
173
|
* 重复注册导致 pi 扩展加载 FATAL(pi 对 duplicate tool registration 是硬失败)。
|
|
83
174
|
*/
|
|
84
175
|
const BOUNDED_WAIT_CAPABILITY = "larkin-pi-subagents-bounded-wait-v1";
|
|
176
|
+
const SUPERVISED_COMMAND_CAPABILITY = "larkin-pi-supervised-command-v1";
|
|
85
177
|
function userPiSubagentsWaitCapability(env) {
|
|
86
178
|
const agentDir = env.PI_CODING_AGENT_DIR || path.join(env.HOME || process.env.HOME || "", ".pi", "agent");
|
|
87
179
|
try {
|
|
@@ -116,7 +208,8 @@ function userPiSubagentsWaitCapability(env) {
|
|
|
116
208
|
const capabilityFiles = extensions.map((entry) => path.resolve(installedRoot, entry));
|
|
117
209
|
const hasCapability = capabilityFiles.every((file) => {
|
|
118
210
|
try {
|
|
119
|
-
|
|
211
|
+
const source = fs.readFileSync(file, "utf8");
|
|
212
|
+
return source.includes(BOUNDED_WAIT_CAPABILITY) && source.includes(SUPERVISED_COMMAND_CAPABILITY);
|
|
120
213
|
}
|
|
121
214
|
catch {
|
|
122
215
|
return false;
|
|
@@ -143,7 +236,7 @@ export function resolvePiSubagentExtensionArg(input, probeVersion = () => probeE
|
|
|
143
236
|
if (!userPiSubagentsHasBoundedWaitCapability(input.env)) {
|
|
144
237
|
throw new Error("[larkin] WARNING: refusing external Pi because the user-installed " +
|
|
145
238
|
"pi-subagents extension is unbounded or unverifiable; remove it or install " +
|
|
146
|
-
"a version advertising larkin-pi-subagents-bounded-wait-v1.");
|
|
239
|
+
"a version advertising larkin-pi-subagents-bounded-wait-v1 and larkin-pi-supervised-command-v1.");
|
|
147
240
|
}
|
|
148
241
|
return null;
|
|
149
242
|
}
|