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.
@@ -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 existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
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 additionalExtensionPaths = extensionsSpec?.paths.length ? extensionsSpec.paths : undefined;
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 existsSync3, realpathSync } from "fs";
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(realpathSync(topLevel), realpathSync(cwd));
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 (!existsSync3(worktree.path)) {
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
- constructor(onComplete, maxConcurrent = DEFAULT_MAX_CONCURRENT, onStart, onCompact) {
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
- record.status = "aborted";
4693
+ nextStatus = "aborted";
4649
4694
  } else if (failure) {
4650
- record.status = "error";
4695
+ nextStatus = "error";
4651
4696
  record.error = failure;
4652
4697
  } else {
4653
- record.status = steered ? "steered" : "completed";
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
- record.status = "stopped";
4822
- record.completedAt = Date.now();
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
- for (const queued of this.queue) {
4855
- const record = this.agents.get(queued.id);
4856
- if (record) {
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.values()) {
4886
- record.session?.dispose();
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 existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
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 (!existsSync4(dir))
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 existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
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 (!existsSync5(path))
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 existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
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 (!existsSync6(this.filePath))
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 && existsSync6(this.filePath)) {
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 existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
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 (!existsSync7(path))
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 (!existsSync8(path))
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 (existsSync8(projectPath2))
8376
+ if (existsSync9(projectPath2))
8305
8377
  return { path: projectPath2, location: "project" };
8306
8378
  const workspacePath = join10(workspaceAgentsDir(), `${name}.md`);
8307
- if (existsSync8(workspacePath))
8379
+ if (existsSync9(workspacePath))
8308
8380
  return { path: workspacePath, location: "workspace" };
8309
8381
  const personalPath = join10(personalAgentsDir(), `${name}.md`);
8310
- if (existsSync8(personalPath))
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 (existsSync8(targetPath)) {
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 (existsSync8(targetPath)) {
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 (existsSync8(targetPath)) {
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 (existsSync8(targetPath)) {
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
+ }