larkin 0.4.19 → 0.4.21
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/agent/missed-outbound-scan.cjs +123 -0
- package/dist/agent/missed-outbound-scan.mjs +112 -0
- package/dist/feishu/host-shell.cjs +9 -0
- package/dist/feishu/host-shell.mjs +9 -0
- package/dist/feishu/message-policy.cjs +2 -0
- package/dist/feishu/message-policy.mjs +2 -0
- package/dist/runtime/pi-inline-extensions.mjs +40 -9
- package/dist/runtime/pi-subagent-injection.mjs +103 -10
- 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/setup/bot-register.mjs +53 -27
- package/dist/setup/grant-scopes.mjs +13 -4
- package/dist/setup/tenant-scope-grant.mjs +16 -0
- package/package.json +1 -1
- package/patches/@tintinweb%2Fpi-subagents@0.14.3.patch +712 -16
|
@@ -984,7 +984,7 @@ var init_conversation_viewer = __esm(() => {
|
|
|
984
984
|
});
|
|
985
985
|
|
|
986
986
|
// node_modules/@tintinweb/pi-subagents/src/index.ts
|
|
987
|
-
import { existsSync as
|
|
987
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
988
988
|
import { join as join10 } from "path";
|
|
989
989
|
import { defineTool, getAgentDir as getAgentDir7, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
990
990
|
import { Container, Key as Key2, matchesKey as matchesKey4, SettingsList, Spacer, Text } from "@earendil-works/pi-tui";
|
|
@@ -3548,9 +3548,10 @@ import { isAbsolute as isAbsolute2 } from "path";
|
|
|
3548
3548
|
// node_modules/@tintinweb/pi-subagents/src/agent-runner.ts
|
|
3549
3549
|
init_agent_types();
|
|
3550
3550
|
init_default_agents();
|
|
3551
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
3551
|
+
import { existsSync as existsSync3, lstatSync as lstatSync2, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
3552
3552
|
import { homedir as homedir3 } from "os";
|
|
3553
3553
|
import { basename, dirname, isAbsolute, join as join3, resolve } from "path";
|
|
3554
|
+
import { fileURLToPath } from "url";
|
|
3554
3555
|
import {
|
|
3555
3556
|
createAgentSession,
|
|
3556
3557
|
DefaultResourceLoader,
|
|
@@ -3845,6 +3846,29 @@ var SUBAGENT_TOOL_NAMES = {
|
|
|
3845
3846
|
GET_RESULT: "get_subagent_result",
|
|
3846
3847
|
STEER: "steer_subagent"
|
|
3847
3848
|
};
|
|
3849
|
+
function supervisedSiblingPath() {
|
|
3850
|
+
const fromEnv = process.env.LARKIN_PI_SUPERVISED_BUNDLE;
|
|
3851
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0)
|
|
3852
|
+
return fromEnv;
|
|
3853
|
+
return join3(dirname(fileURLToPath(import.meta.url)), "pi-supervised-command.bundle.js");
|
|
3854
|
+
}
|
|
3855
|
+
function resolveTrustedSupervisedBundle(candidate) {
|
|
3856
|
+
if (candidate === undefined)
|
|
3857
|
+
return;
|
|
3858
|
+
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
3859
|
+
throw new Error("supervised command bundle is missing");
|
|
3860
|
+
}
|
|
3861
|
+
const sibling = supervisedSiblingPath();
|
|
3862
|
+
for (const file of [sibling, candidate]) {
|
|
3863
|
+
if (!existsSync3(file) || lstatSync2(file).isSymbolicLink() || !lstatSync2(file).isFile()) {
|
|
3864
|
+
throw new Error("supervised command bundle is missing");
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
if (realpathSync(candidate) !== realpathSync(sibling)) {
|
|
3868
|
+
throw new Error("supervised command bundle is missing");
|
|
3869
|
+
}
|
|
3870
|
+
return realpathSync(sibling);
|
|
3871
|
+
}
|
|
3848
3872
|
var EXCLUDED_TOOL_NAMES = Object.values(SUBAGENT_TOOL_NAMES);
|
|
3849
3873
|
function extensionCanonicalName(extPath) {
|
|
3850
3874
|
const base = basename(extPath);
|
|
@@ -4120,7 +4144,11 @@ async function runAgent(ctx, type, prompt, options) {
|
|
|
4120
4144
|
const excludeNames = new Set((excludeExtensions ?? []).map((n) => n.toLowerCase()));
|
|
4121
4145
|
const hasExcludes = excludeNames.size > 0;
|
|
4122
4146
|
const loadAll = extensions === true || extensionsSpec?.wildcard === true;
|
|
4123
|
-
const
|
|
4147
|
+
const supervisedPath = resolveTrustedSupervisedBundle(options.allowSupervisedCommands);
|
|
4148
|
+
const additionalExtensionPaths = [
|
|
4149
|
+
...extensionsSpec?.paths ?? [],
|
|
4150
|
+
...supervisedPath ? [supervisedPath] : []
|
|
4151
|
+
];
|
|
4124
4152
|
let discoveredNames;
|
|
4125
4153
|
const extensionsOverride = noExtensions || loadAll && !hasExcludes ? undefined : (base) => {
|
|
4126
4154
|
discoveredNames = new Set(base.extensions.flatMap((e) => extensionCanonicalNames(e.path)));
|
|
@@ -4138,7 +4166,7 @@ async function runAgent(ctx, type, prompt, options) {
|
|
|
4138
4166
|
cwd: configCwd,
|
|
4139
4167
|
agentDir,
|
|
4140
4168
|
noExtensions,
|
|
4141
|
-
additionalExtensionPaths,
|
|
4169
|
+
additionalExtensionPaths: additionalExtensionPaths.length ? additionalExtensionPaths : undefined,
|
|
4142
4170
|
extensionsOverride,
|
|
4143
4171
|
noSkills,
|
|
4144
4172
|
noPromptTemplates: true,
|
|
@@ -4395,7 +4423,7 @@ ${toolCalls.join(`
|
|
|
4395
4423
|
// node_modules/@tintinweb/pi-subagents/src/worktree.ts
|
|
4396
4424
|
import { execFileSync } from "child_process";
|
|
4397
4425
|
import { randomUUID } from "crypto";
|
|
4398
|
-
import { existsSync as
|
|
4426
|
+
import { existsSync as existsSync4, realpathSync as realpathSync2 } from "fs";
|
|
4399
4427
|
import { tmpdir } from "os";
|
|
4400
4428
|
import { join as join4, relative } from "path";
|
|
4401
4429
|
function createWorktree(cwd, agentId) {
|
|
@@ -4405,7 +4433,7 @@ function createWorktree(cwd, agentId) {
|
|
|
4405
4433
|
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
|
|
4406
4434
|
baseSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 }).toString().trim();
|
|
4407
4435
|
const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, stdio: "pipe", timeout: 5000 }).toString().trim();
|
|
4408
|
-
subdir = relative(
|
|
4436
|
+
subdir = relative(realpathSync2(topLevel), realpathSync2(cwd));
|
|
4409
4437
|
} catch {
|
|
4410
4438
|
return;
|
|
4411
4439
|
}
|
|
@@ -4424,7 +4452,7 @@ function createWorktree(cwd, agentId) {
|
|
|
4424
4452
|
}
|
|
4425
4453
|
}
|
|
4426
4454
|
function cleanupWorktree(cwd, worktree, agentDescription) {
|
|
4427
|
-
if (!
|
|
4455
|
+
if (!existsSync4(worktree.path)) {
|
|
4428
4456
|
return { hasChanges: false };
|
|
4429
4457
|
}
|
|
4430
4458
|
try {
|
|
@@ -4530,11 +4558,14 @@ class AgentManager {
|
|
|
4530
4558
|
worktreeRepos = new Set;
|
|
4531
4559
|
queue = [];
|
|
4532
4560
|
runningBackground = 0;
|
|
4533
|
-
|
|
4561
|
+
supervisedKey;
|
|
4562
|
+
supervisedById = new Map;
|
|
4563
|
+
constructor(onComplete, maxConcurrent = DEFAULT_MAX_CONCURRENT, onStart, onCompact, supervisedKey) {
|
|
4534
4564
|
this.onComplete = onComplete;
|
|
4535
4565
|
this.onStart = onStart;
|
|
4536
4566
|
this.onCompact = onCompact;
|
|
4537
4567
|
this.maxConcurrent = maxConcurrent;
|
|
4568
|
+
this.supervisedKey = supervisedKey;
|
|
4538
4569
|
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
|
|
4539
4570
|
this.cleanupInterval.unref();
|
|
4540
4571
|
}
|
|
@@ -4547,7 +4578,18 @@ class AgentManager {
|
|
|
4547
4578
|
}
|
|
4548
4579
|
spawn(pi, ctx, type, prompt, options) {
|
|
4549
4580
|
assertValidSpawnCwd(options.cwd);
|
|
4581
|
+
if (Object.prototype.hasOwnProperty.call(options, "allowSupervisedCommands")) {
|
|
4582
|
+
throw new Error("allowSupervisedCommands is not a public spawn option");
|
|
4583
|
+
}
|
|
4584
|
+
const supervisedPath = this.supervisedKey ? options[this.supervisedKey] : undefined;
|
|
4585
|
+
if (typeof supervisedPath === "string" && supervisedPath.length === 0) {
|
|
4586
|
+
throw new Error("supervised command bundle is missing");
|
|
4587
|
+
}
|
|
4550
4588
|
const id = randomUUID2().slice(0, 17);
|
|
4589
|
+
if (typeof supervisedPath === "string")
|
|
4590
|
+
this.supervisedById.set(id, supervisedPath);
|
|
4591
|
+
else
|
|
4592
|
+
this.supervisedById.delete(id);
|
|
4551
4593
|
const abortController = new AbortController;
|
|
4552
4594
|
const record = {
|
|
4553
4595
|
id,
|
|
@@ -4571,6 +4613,7 @@ class AgentManager {
|
|
|
4571
4613
|
try {
|
|
4572
4614
|
this.startAgent(id, record, args);
|
|
4573
4615
|
} catch (err) {
|
|
4616
|
+
this.supervisedById.delete(id);
|
|
4574
4617
|
this.agents.delete(id);
|
|
4575
4618
|
throw err;
|
|
4576
4619
|
}
|
|
@@ -4613,6 +4656,7 @@ class AgentManager {
|
|
|
4613
4656
|
isolated: options.isolated,
|
|
4614
4657
|
inheritContext: options.inheritContext,
|
|
4615
4658
|
thinkingLevel: options.thinkingLevel,
|
|
4659
|
+
allowSupervisedCommands: this.supervisedById.get(id),
|
|
4616
4660
|
cwd: worktreeCwd ?? customCwd,
|
|
4617
4661
|
configCwd: customCwd !== undefined ? ctx.cwd : undefined,
|
|
4618
4662
|
signal: record.abortController.signal,
|
|
@@ -4642,19 +4686,23 @@ class AgentManager {
|
|
|
4642
4686
|
}
|
|
4643
4687
|
options.onSessionCreated?.(session);
|
|
4644
4688
|
}
|
|
4645
|
-
}).then(({ responseText, session, aborted, steered, failure }) => {
|
|
4689
|
+
}).then(async ({ responseText, session, aborted, steered, failure }) => {
|
|
4690
|
+
let nextStatus = record.status;
|
|
4646
4691
|
if (record.status !== "stopped") {
|
|
4647
4692
|
if (aborted) {
|
|
4648
|
-
|
|
4693
|
+
nextStatus = "aborted";
|
|
4649
4694
|
} else if (failure) {
|
|
4650
|
-
|
|
4695
|
+
nextStatus = "error";
|
|
4651
4696
|
record.error = failure;
|
|
4652
4697
|
} else {
|
|
4653
|
-
|
|
4698
|
+
nextStatus = steered ? "steered" : "completed";
|
|
4654
4699
|
}
|
|
4655
4700
|
}
|
|
4656
4701
|
record.result = responseText;
|
|
4657
4702
|
record.session = session;
|
|
4703
|
+
await this.finalize(id, record, nextStatus === "error" ? "error" : "complete");
|
|
4704
|
+
if (record.status !== "stopped")
|
|
4705
|
+
record.status = nextStatus;
|
|
4658
4706
|
record.completedAt ??= Date.now();
|
|
4659
4707
|
detach();
|
|
4660
4708
|
if (record.outputCleanup) {
|
|
@@ -4687,11 +4735,11 @@ Changes saved to branch \`${wtResult.branch}\`${repoNote}. Merge with: \`git mer
|
|
|
4687
4735
|
this.drainQueue();
|
|
4688
4736
|
}
|
|
4689
4737
|
return responseText;
|
|
4690
|
-
}).catch((err) => {
|
|
4691
|
-
if (record.status !== "stopped") {
|
|
4692
|
-
record.status = "error";
|
|
4693
|
-
}
|
|
4738
|
+
}).catch(async (err) => {
|
|
4694
4739
|
record.error = err instanceof Error ? err.message : String(err);
|
|
4740
|
+
await this.finalize(id, record, "error");
|
|
4741
|
+
if (record.status !== "stopped")
|
|
4742
|
+
record.status = "error";
|
|
4695
4743
|
record.completedAt ??= Date.now();
|
|
4696
4744
|
detach();
|
|
4697
4745
|
if (record.outputCleanup) {
|
|
@@ -4813,16 +4861,38 @@ Changes saved to branch \`${wtResult.branch}\`${repoNote}. Merge with: \`git mer
|
|
|
4813
4861
|
this.queue = this.queue.filter((q) => q.id !== id);
|
|
4814
4862
|
record.status = "stopped";
|
|
4815
4863
|
record.completedAt = Date.now();
|
|
4864
|
+
this.supervisedById.delete(id);
|
|
4816
4865
|
return true;
|
|
4817
4866
|
}
|
|
4818
|
-
if (record.status !== "running")
|
|
4867
|
+
if (record.status !== "running") {
|
|
4868
|
+
this.supervisedById.delete(id);
|
|
4819
4869
|
return false;
|
|
4870
|
+
}
|
|
4820
4871
|
record.abortController?.abort();
|
|
4821
|
-
|
|
4822
|
-
|
|
4872
|
+
const done = this.finalize(id, record, "abort").then(() => {
|
|
4873
|
+
record.status = "stopped";
|
|
4874
|
+
record.completedAt ??= Date.now();
|
|
4875
|
+
});
|
|
4876
|
+
this.reaps.set(id, done);
|
|
4823
4877
|
return true;
|
|
4824
4878
|
}
|
|
4879
|
+
reaps = new Map;
|
|
4880
|
+
finalize(id, record, reason) {
|
|
4881
|
+
const existing = this.reaps.get(id);
|
|
4882
|
+
if (existing)
|
|
4883
|
+
return existing;
|
|
4884
|
+
const work = (async () => {
|
|
4885
|
+
const session = record.session;
|
|
4886
|
+
const runner = session?._extensionRunner;
|
|
4887
|
+
if (runner?.emit)
|
|
4888
|
+
await runner.emit({ type: "session_shutdown", reason });
|
|
4889
|
+
this.supervisedById.delete(id);
|
|
4890
|
+
})();
|
|
4891
|
+
this.reaps.set(id, work);
|
|
4892
|
+
return work;
|
|
4893
|
+
}
|
|
4825
4894
|
removeRecord(id, record) {
|
|
4895
|
+
this.supervisedById.delete(id);
|
|
4826
4896
|
record.session?.dispose?.();
|
|
4827
4897
|
record.session = undefined;
|
|
4828
4898
|
this.agents.delete(id);
|
|
@@ -4849,25 +4919,14 @@ Changes saved to branch \`${wtResult.branch}\`${repoNote}. Merge with: \`git mer
|
|
|
4849
4919
|
hasRunning() {
|
|
4850
4920
|
return [...this.agents.values()].some((r) => r.status === "running" || r.status === "queued");
|
|
4851
4921
|
}
|
|
4852
|
-
abortAll() {
|
|
4922
|
+
async abortAll() {
|
|
4853
4923
|
let count = 0;
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
if (
|
|
4857
|
-
record.status = "stopped";
|
|
4858
|
-
record.completedAt = Date.now();
|
|
4924
|
+
const ids = [...this.agents.keys()];
|
|
4925
|
+
for (const id of ids) {
|
|
4926
|
+
if (this.abort(id))
|
|
4859
4927
|
count++;
|
|
4860
|
-
}
|
|
4861
|
-
}
|
|
4862
|
-
this.queue = [];
|
|
4863
|
-
for (const record of this.agents.values()) {
|
|
4864
|
-
if (record.status === "running") {
|
|
4865
|
-
record.abortController?.abort();
|
|
4866
|
-
record.status = "stopped";
|
|
4867
|
-
record.completedAt = Date.now();
|
|
4868
|
-
count++;
|
|
4869
|
-
}
|
|
4870
4928
|
}
|
|
4929
|
+
await Promise.all([...this.reaps.values()]);
|
|
4871
4930
|
return count;
|
|
4872
4931
|
}
|
|
4873
4932
|
async waitForAll() {
|
|
@@ -4879,13 +4938,17 @@ Changes saved to branch \`${wtResult.branch}\`${repoNote}. Merge with: \`git mer
|
|
|
4879
4938
|
await Promise.allSettled(pending);
|
|
4880
4939
|
}
|
|
4881
4940
|
}
|
|
4882
|
-
dispose() {
|
|
4941
|
+
async dispose() {
|
|
4883
4942
|
clearInterval(this.cleanupInterval);
|
|
4943
|
+
await this.abortAll();
|
|
4884
4944
|
this.queue = [];
|
|
4885
|
-
for (const record of this.agents
|
|
4886
|
-
|
|
4945
|
+
for (const [id, record] of this.agents) {
|
|
4946
|
+
this.supervisedById.delete(id);
|
|
4947
|
+
const session = record.session;
|
|
4948
|
+
session?.dispose?.();
|
|
4887
4949
|
}
|
|
4888
4950
|
this.agents.clear();
|
|
4951
|
+
this.supervisedById.clear();
|
|
4889
4952
|
try {
|
|
4890
4953
|
pruneWorktrees(process.cwd());
|
|
4891
4954
|
} catch {}
|
|
@@ -5006,7 +5069,7 @@ function registerRpcHandlers(deps) {
|
|
|
5006
5069
|
|
|
5007
5070
|
// node_modules/@tintinweb/pi-subagents/src/custom-agents.ts
|
|
5008
5071
|
init_agent_types();
|
|
5009
|
-
import { existsSync as
|
|
5072
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
5010
5073
|
import { basename as basename2, join as join5 } from "path";
|
|
5011
5074
|
import { getAgentDir as getAgentDir4, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
5012
5075
|
function loadCustomAgents(cwd) {
|
|
@@ -5020,7 +5083,7 @@ function loadCustomAgents(cwd) {
|
|
|
5020
5083
|
return agents2;
|
|
5021
5084
|
}
|
|
5022
5085
|
function loadFromDir(dir, agents2, source) {
|
|
5023
|
-
if (!
|
|
5086
|
+
if (!existsSync5(dir))
|
|
5024
5087
|
return;
|
|
5025
5088
|
let files;
|
|
5026
5089
|
try {
|
|
@@ -5115,7 +5178,7 @@ function inheritField(val) {
|
|
|
5115
5178
|
}
|
|
5116
5179
|
|
|
5117
5180
|
// node_modules/@tintinweb/pi-subagents/src/enabled-models.ts
|
|
5118
|
-
import { existsSync as
|
|
5181
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
5119
5182
|
import { join as join6 } from "path";
|
|
5120
5183
|
import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
|
|
5121
5184
|
function settingsPaths(cwd) {
|
|
@@ -5125,7 +5188,7 @@ function settingsPaths(cwd) {
|
|
|
5125
5188
|
];
|
|
5126
5189
|
}
|
|
5127
5190
|
function readField(path) {
|
|
5128
|
-
if (!
|
|
5191
|
+
if (!existsSync6(path))
|
|
5129
5192
|
return;
|
|
5130
5193
|
try {
|
|
5131
5194
|
const raw = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -6456,7 +6519,7 @@ class SubagentScheduler {
|
|
|
6456
6519
|
}
|
|
6457
6520
|
|
|
6458
6521
|
// node_modules/@tintinweb/pi-subagents/src/schedule-store.ts
|
|
6459
|
-
import { existsSync as
|
|
6522
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6460
6523
|
import { dirname as dirname2, join as join8 } from "path";
|
|
6461
6524
|
var LOCK_RETRY_MS = 50;
|
|
6462
6525
|
var LOCK_MAX_RETRIES = 100;
|
|
@@ -6513,7 +6576,7 @@ class ScheduleStore {
|
|
|
6513
6576
|
mkdirSync3(dirname2(this.filePath), { recursive: true });
|
|
6514
6577
|
}
|
|
6515
6578
|
load() {
|
|
6516
|
-
if (!
|
|
6579
|
+
if (!existsSync7(this.filePath))
|
|
6517
6580
|
return;
|
|
6518
6581
|
try {
|
|
6519
6582
|
const data = JSON.parse(readFileSync5(this.filePath, "utf-8"));
|
|
@@ -6576,7 +6639,7 @@ class ScheduleStore {
|
|
|
6576
6639
|
return this.withLock(() => this.jobs.delete(id));
|
|
6577
6640
|
}
|
|
6578
6641
|
deleteFileIfEmpty() {
|
|
6579
|
-
if (this.jobs.size === 0 &&
|
|
6642
|
+
if (this.jobs.size === 0 && existsSync7(this.filePath)) {
|
|
6580
6643
|
try {
|
|
6581
6644
|
unlinkSync(this.filePath);
|
|
6582
6645
|
} catch {}
|
|
@@ -6585,7 +6648,7 @@ class ScheduleStore {
|
|
|
6585
6648
|
}
|
|
6586
6649
|
|
|
6587
6650
|
// node_modules/@tintinweb/pi-subagents/src/settings.ts
|
|
6588
|
-
import { existsSync as
|
|
6651
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
6589
6652
|
import { dirname as dirname3, join as join9 } from "path";
|
|
6590
6653
|
import { getAgentDir as getAgentDir6 } from "@earendil-works/pi-coding-agent";
|
|
6591
6654
|
var VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
|
|
@@ -6641,7 +6704,7 @@ function projectPath(cwd) {
|
|
|
6641
6704
|
return join9(cwd, ".pi", "subagents.json");
|
|
6642
6705
|
}
|
|
6643
6706
|
function readSettingsFile(path) {
|
|
6644
|
-
if (!
|
|
6707
|
+
if (!existsSync8(path))
|
|
6645
6708
|
return {};
|
|
6646
6709
|
try {
|
|
6647
6710
|
return sanitize(JSON.parse(readFileSync6(path, "utf-8")));
|
|
@@ -7141,6 +7204,7 @@ async function waitForSubagentResult(record, timeoutMs, signal) {
|
|
|
7141
7204
|
return (await waitWithDeadline(record.promise, remainingMs, signal)).timedOut;
|
|
7142
7205
|
}
|
|
7143
7206
|
var LARKIN_BOUNDED_WAIT_CAPABILITY = "larkin-pi-subagents-bounded-wait-v1";
|
|
7207
|
+
var LARKIN_SUPERVISED_COMMAND_CAPABILITY = "larkin-pi-supervised-command-v1";
|
|
7144
7208
|
function renderRunningAgentStatus(frame, statsText, activity, theme) {
|
|
7145
7209
|
const container = new Container;
|
|
7146
7210
|
container.addChild(new Text(theme.fg("accent", frame) + (statsText ? " " + statsText : ""), 0, 0));
|
|
@@ -7416,6 +7480,13 @@ Use get_subagent_result for full output.`,
|
|
|
7416
7480
|
tokens
|
|
7417
7481
|
};
|
|
7418
7482
|
}
|
|
7483
|
+
function supervisedCommandBundlePath() {
|
|
7484
|
+
const sibling = supervisedSiblingPath();
|
|
7485
|
+
if (!existsSync9(sibling))
|
|
7486
|
+
throw new Error("supervised command bundle is missing");
|
|
7487
|
+
return sibling;
|
|
7488
|
+
}
|
|
7489
|
+
const supervisedKey = Symbol("larkin-supervised-commands");
|
|
7419
7490
|
const manager = new AgentManager((record) => {
|
|
7420
7491
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
7421
7492
|
const eventData = buildEventData(record);
|
|
@@ -7465,7 +7536,7 @@ Use get_subagent_result for full output.`,
|
|
|
7465
7536
|
tokensBefore: info.tokensBefore,
|
|
7466
7537
|
compactionCount: record.compactionCount
|
|
7467
7538
|
});
|
|
7468
|
-
});
|
|
7539
|
+
}, supervisedKey);
|
|
7469
7540
|
const MANAGER_KEY = Symbol.for("pi-subagents:manager");
|
|
7470
7541
|
const registryEntry = {
|
|
7471
7542
|
waitForAll: () => manager.waitForAll(),
|
|
@@ -7522,12 +7593,12 @@ Use get_subagent_result for full output.`,
|
|
|
7522
7593
|
delete globalThis[MANAGER_KEY];
|
|
7523
7594
|
}
|
|
7524
7595
|
scheduler.stop();
|
|
7525
|
-
manager.abortAll();
|
|
7596
|
+
await manager.abortAll();
|
|
7526
7597
|
for (const timer of pendingNudges.values())
|
|
7527
7598
|
clearTimeout(timer);
|
|
7528
7599
|
pendingNudges.clear();
|
|
7529
7600
|
fleet.dispose();
|
|
7530
|
-
manager.dispose();
|
|
7601
|
+
await manager.dispose();
|
|
7531
7602
|
});
|
|
7532
7603
|
let widgetMode = "background";
|
|
7533
7604
|
function getWidgetMode() {
|
|
@@ -7745,7 +7816,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
7745
7816
|
join10(getAgentDir7(), "agent-tool-description.md")
|
|
7746
7817
|
]) {
|
|
7747
7818
|
try {
|
|
7748
|
-
if (!
|
|
7819
|
+
if (!existsSync9(path))
|
|
7749
7820
|
continue;
|
|
7750
7821
|
const text = readFileSync7(path, "utf-8").trim();
|
|
7751
7822
|
if (text)
|
|
@@ -8041,6 +8112,7 @@ ${list}`);
|
|
|
8041
8112
|
inheritContext,
|
|
8042
8113
|
thinkingLevel: thinking,
|
|
8043
8114
|
isBackground: true,
|
|
8115
|
+
...params.run_in_background === true ? { [supervisedKey]: supervisedCommandBundlePath() } : {},
|
|
8044
8116
|
isolation,
|
|
8045
8117
|
invocation: agentInvocation,
|
|
8046
8118
|
...bgCallbacks
|
|
@@ -8301,13 +8373,13 @@ ${conversation}`;
|
|
|
8301
8373
|
const personalAgentsDir = () => join10(getAgentDir7(), "agents");
|
|
8302
8374
|
function findAgentFile(name) {
|
|
8303
8375
|
const projectPath2 = join10(projectAgentsDir(), `${name}.md`);
|
|
8304
|
-
if (
|
|
8376
|
+
if (existsSync9(projectPath2))
|
|
8305
8377
|
return { path: projectPath2, location: "project" };
|
|
8306
8378
|
const workspacePath = join10(workspaceAgentsDir(), `${name}.md`);
|
|
8307
|
-
if (
|
|
8379
|
+
if (existsSync9(workspacePath))
|
|
8308
8380
|
return { path: workspacePath, location: "workspace" };
|
|
8309
8381
|
const personalPath = join10(personalAgentsDir(), `${name}.md`);
|
|
8310
|
-
if (
|
|
8382
|
+
if (existsSync9(personalPath))
|
|
8311
8383
|
return { path: personalPath, location: "personal" };
|
|
8312
8384
|
return;
|
|
8313
8385
|
}
|
|
@@ -8538,7 +8610,7 @@ ${conversation}`;
|
|
|
8538
8610
|
const targetDir = location.startsWith("Project") ? projectAgentsDir() : personalAgentsDir();
|
|
8539
8611
|
mkdirSync5(targetDir, { recursive: true });
|
|
8540
8612
|
const targetPath = join10(targetDir, `${name}.md`);
|
|
8541
|
-
if (
|
|
8613
|
+
if (existsSync9(targetPath)) {
|
|
8542
8614
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
8543
8615
|
if (!overwrite)
|
|
8544
8616
|
return;
|
|
@@ -8676,7 +8748,7 @@ enabled: false
|
|
|
8676
8748
|
return;
|
|
8677
8749
|
mkdirSync5(targetDir, { recursive: true });
|
|
8678
8750
|
const targetPath = join10(targetDir, `${name}.md`);
|
|
8679
|
-
if (
|
|
8751
|
+
if (existsSync9(targetPath)) {
|
|
8680
8752
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
8681
8753
|
if (!overwrite)
|
|
8682
8754
|
return;
|
|
@@ -8730,7 +8802,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
8730
8802
|
return;
|
|
8731
8803
|
}
|
|
8732
8804
|
reloadCustomAgents();
|
|
8733
|
-
if (
|
|
8805
|
+
if (existsSync9(targetPath)) {
|
|
8734
8806
|
ctx.ui.notify(`Created ${targetPath}`, "info");
|
|
8735
8807
|
} else {
|
|
8736
8808
|
ctx.ui.notify("Agent generation completed but file was not created. Check the agent output.", "warning");
|
|
@@ -8804,7 +8876,7 @@ ${systemPrompt}
|
|
|
8804
8876
|
`;
|
|
8805
8877
|
mkdirSync5(targetDir, { recursive: true });
|
|
8806
8878
|
const targetPath = join10(targetDir, `${name}.md`);
|
|
8807
|
-
if (
|
|
8879
|
+
if (existsSync9(targetPath)) {
|
|
8808
8880
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
8809
8881
|
if (!overwrite)
|
|
8810
8882
|
return;
|
|
@@ -9033,5 +9105,6 @@ export {
|
|
|
9033
9105
|
waitForSubagentResult,
|
|
9034
9106
|
renderRunningAgentStatus,
|
|
9035
9107
|
src_default as default,
|
|
9108
|
+
LARKIN_SUPERVISED_COMMAND_CAPABILITY,
|
|
9036
9109
|
LARKIN_BOUNDED_WAIT_CAPABILITY
|
|
9037
9110
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { cancelSupervisedCommand, LARKIN_SUPERVISED_COMMAND_CAPABILITY, reapSupervisedCommands, resolveSupervisedCwd, startSupervisedCommand, supervisedWaitSeconds, waitSupervisedCommand, } from "./pi-supervised-command.mjs";
|
|
3
|
+
void LARKIN_SUPERVISED_COMMAND_CAPABILITY;
|
|
4
|
+
function ownerOf(ctx) {
|
|
5
|
+
const owner = ctx?.sessionManager;
|
|
6
|
+
if (!owner)
|
|
7
|
+
throw new Error("supervised commands require a session");
|
|
8
|
+
return owner;
|
|
9
|
+
}
|
|
10
|
+
export default function (pi) {
|
|
11
|
+
const waitCap = supervisedWaitSeconds();
|
|
12
|
+
let waitUsedThisTurn = false;
|
|
13
|
+
const resetWaitTurn = () => { waitUsedThisTurn = false; };
|
|
14
|
+
pi.on("turn_end", resetWaitTurn);
|
|
15
|
+
const reapOwner = async (_event, ctx) => {
|
|
16
|
+
const owner = ctx?.sessionManager;
|
|
17
|
+
if (owner)
|
|
18
|
+
await reapSupervisedCommands(owner);
|
|
19
|
+
};
|
|
20
|
+
pi.on("agent_end", reapOwner);
|
|
21
|
+
pi.on("session_shutdown", reapOwner);
|
|
22
|
+
pi.registerTool({
|
|
23
|
+
name: "supervised_start",
|
|
24
|
+
label: "supervised_start",
|
|
25
|
+
description: "Start one supervised process with executable+args (shell:false). Background-agent only. Returns a handle; use supervised_wait in <=60s slices. Do not use bash for work that must outlive 60s.",
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
executable: Type.String({ description: "Executable path or name. No shell." }),
|
|
28
|
+
args: Type.Array(Type.String(), { description: "Argument vector. No shell metacharacters are interpreted." }),
|
|
29
|
+
cwd: Type.Optional(Type.String({ description: "Optional cwd inside the child session root. Symlinks and path escape are rejected." })),
|
|
30
|
+
}),
|
|
31
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
32
|
+
const session = ctx;
|
|
33
|
+
const root = typeof session.cwd === "string" && session.cwd.length > 0 ? session.cwd : process.cwd();
|
|
34
|
+
const started = startSupervisedCommand({
|
|
35
|
+
owner: ownerOf(session),
|
|
36
|
+
executable: params.executable,
|
|
37
|
+
args: params.args ?? [],
|
|
38
|
+
cwd: resolveSupervisedCwd(root, params.cwd),
|
|
39
|
+
});
|
|
40
|
+
return { content: [{ type: "text", text: JSON.stringify(started) }], details: started };
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
pi.registerTool({
|
|
44
|
+
name: "supervised_wait",
|
|
45
|
+
label: "supervised_wait",
|
|
46
|
+
description: `Wait on a supervised handle for at most ${waitCap}s. Timeout returns status=running without killing the process. At most one wait per assistant turn so queued Steer is consumed before the next wait.`,
|
|
47
|
+
parameters: Type.Object({
|
|
48
|
+
handle: Type.String({ description: "Handle from supervised_start" }),
|
|
49
|
+
timeout: Type.Optional(Type.Number({ description: `Wait seconds, max ${waitCap}`, maximum: waitCap, minimum: 1 })),
|
|
50
|
+
}),
|
|
51
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
52
|
+
if (waitUsedThisTurn) {
|
|
53
|
+
throw new Error("supervised_wait is limited to once per turn; return to the model so queued Steer can run first");
|
|
54
|
+
}
|
|
55
|
+
waitUsedThisTurn = true;
|
|
56
|
+
const result = await waitSupervisedCommand(ownerOf(ctx), params.handle, params.timeout);
|
|
57
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
pi.registerTool({
|
|
61
|
+
name: "supervised_cancel",
|
|
62
|
+
label: "supervised_cancel",
|
|
63
|
+
description: "Cancel a supervised handle and reap its process tree.",
|
|
64
|
+
parameters: Type.Object({
|
|
65
|
+
handle: Type.String({ description: "Handle from supervised_start" }),
|
|
66
|
+
}),
|
|
67
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
68
|
+
const result = await cancelSupervisedCommand(ownerOf(ctx), params.handle);
|
|
69
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|