@agentproto/cli 0.9.0 → 0.10.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.
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { rm, readFile, mkdir, writeFile, readdir, mkdtemp, chmod, stat, cp, unli
6
6
  import { homedir, tmpdir, userInfo, hostname, platform } from 'os';
7
7
  import { isAbsolute, join, dirname, resolve, delimiter, basename, normalize, relative } from 'path';
8
8
  import { promisify, parseArgs } from 'util';
9
- import { createReadStream, promises, constants, readFileSync, existsSync, chmodSync, mkdirSync, writeFileSync, accessSync, readdirSync, renameSync, createWriteStream, statSync, openSync, closeSync } from 'fs';
9
+ import { createReadStream, promises, constants, readFileSync, existsSync, chmodSync, mkdirSync, writeFileSync, accessSync, readdirSync, renameSync, createWriteStream, statSync, realpathSync, openSync, closeSync } from 'fs';
10
10
  import { createRequire as createRequire$1 } from 'module';
11
11
  import { pathToFileURL, fileURLToPath } from 'url';
12
12
  import { collectAgentprotoNamespaceRoots, makeAdapterResolver, makeSetupLedger, makeAdapterLister, makeAdapterWizard, discoverAdapterPackages, makeCredsStore, makeListTool, makeSetupTool, computeStatus } from '@agentproto/provider-kit';
@@ -39,7 +39,7 @@ import { stdout, stdin } from 'process';
39
39
  import { getBrowserAdapter, browserAdapters } from '@agentproto/adapter-browser';
40
40
 
41
41
  /**
42
- * @agentproto/cli v0.9.0
42
+ * @agentproto/cli v0.10.0
43
43
  * The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
44
44
  */
45
45
  // Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
@@ -15035,9 +15035,18 @@ function isWorkflowHandle(v2) {
15035
15035
  }
15036
15036
  async function importEntryHandle(workflowMdPath, entry) {
15037
15037
  const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry);
15038
+ let href = pathToFileURL(abs).href;
15039
+ if (!process.env.VITEST) {
15040
+ try {
15041
+ const url = pathToFileURL(abs);
15042
+ url.searchParams.set("v", String((await stat(abs)).mtimeMs));
15043
+ href = url.href;
15044
+ } catch {
15045
+ }
15046
+ }
15038
15047
  let mod;
15039
15048
  try {
15040
- mod = await import(pathToFileURL(abs).href);
15049
+ mod = await import(href);
15041
15050
  } catch (err) {
15042
15051
  throw new WorkflowLoadError(
15043
15052
  `cannot import entry '${entry}': ${err instanceof Error ? err.message : String(err)}`
@@ -17350,9 +17359,19 @@ function setActiveWorkspace(config, slug) {
17350
17359
  function findWorkspace(config, slug) {
17351
17360
  return config.workspaces.find((w) => w.slug === sanitizeSlug(slug));
17352
17361
  }
17362
+ function canonical(p2) {
17363
+ try {
17364
+ return realpathSync(resolve(p2));
17365
+ } catch {
17366
+ return resolve(p2);
17367
+ }
17368
+ }
17353
17369
  function findWorkspaceByPath(config, dir) {
17354
- const resolved = resolve(dir);
17355
- const candidates2 = config.workspaces.filter((w) => resolved.startsWith(resolve(w.path) + "/") || resolved === resolve(w.path)).sort((a, b2) => b2.path.length - a.path.length);
17370
+ const resolved = canonical(dir);
17371
+ const candidates2 = config.workspaces.filter((w) => {
17372
+ const wPath = canonical(w.path);
17373
+ return resolved.startsWith(wPath + "/") || resolved === wPath;
17374
+ }).sort((a, b2) => b2.path.length - a.path.length);
17356
17375
  return candidates2[0];
17357
17376
  }
17358
17377
  function getActiveWorkspace(config) {
@@ -17438,6 +17457,76 @@ async function loadConfig(path) {
17438
17457
  return {};
17439
17458
  }
17440
17459
  }
17460
+ var markerSchema = z.object({ worktreeId: z.string() });
17461
+ function statOrUndefined(path) {
17462
+ try {
17463
+ return statSync(path);
17464
+ } catch {
17465
+ return void 0;
17466
+ }
17467
+ }
17468
+ function readWorktreeGitDir(dir) {
17469
+ const link = (() => {
17470
+ try {
17471
+ return readFileSync(join(dir, ".git"), "utf8");
17472
+ } catch {
17473
+ return void 0;
17474
+ }
17475
+ })();
17476
+ if (link === void 0) return void 0;
17477
+ const target = /^gitdir:\s*(.+)$/m.exec(link)?.[1]?.trim();
17478
+ if (!target) return void 0;
17479
+ const gitDir = isAbsolute(target) ? target : resolve(dir, target);
17480
+ return statOrUndefined(join(gitDir, "gitdir"))?.isFile() === true ? gitDir : void 0;
17481
+ }
17482
+ function readMainRepoPath(gitDir) {
17483
+ let raw;
17484
+ try {
17485
+ raw = readFileSync(join(gitDir, "commondir"), "utf8").trim();
17486
+ } catch {
17487
+ return void 0;
17488
+ }
17489
+ if (!raw) return void 0;
17490
+ const commonGitDir = isAbsolute(raw) ? raw : resolve(gitDir, raw);
17491
+ return dirname(commonGitDir);
17492
+ }
17493
+ function readWorktreeId(gitDir) {
17494
+ let raw;
17495
+ try {
17496
+ raw = readFileSync(join(gitDir, "agentproto-worktree.json"), "utf8");
17497
+ } catch {
17498
+ return void 0;
17499
+ }
17500
+ let parsed;
17501
+ try {
17502
+ parsed = JSON.parse(raw);
17503
+ } catch {
17504
+ return void 0;
17505
+ }
17506
+ const result = markerSchema.safeParse(parsed);
17507
+ return result.success ? result.data.worktreeId : void 0;
17508
+ }
17509
+ function resolveWorktreeIdentity(cwd) {
17510
+ let dir = resolve(cwd);
17511
+ for (; ; ) {
17512
+ const dotGit = statOrUndefined(join(dir, ".git"));
17513
+ if (dotGit) {
17514
+ if (!dotGit.isFile()) return void 0;
17515
+ const gitDir = readWorktreeGitDir(dir);
17516
+ if (gitDir === void 0) return void 0;
17517
+ const worktreeId = readWorktreeId(gitDir);
17518
+ const mainRepoPath = readMainRepoPath(gitDir);
17519
+ return {
17520
+ worktreePath: dir,
17521
+ ...worktreeId === void 0 ? {} : { worktreeId },
17522
+ ...mainRepoPath === void 0 ? {} : { mainRepoPath }
17523
+ };
17524
+ }
17525
+ const parent = dirname(dir);
17526
+ if (parent === dir) return void 0;
17527
+ dir = parent;
17528
+ }
17529
+ }
17441
17530
  var providers_store_exports = {};
17442
17531
  __reExport(providers_store_exports, dist_exports);
17443
17532
  function resolveSpawnDefaults(defaults, adapterSlug, input2) {
@@ -17988,15 +18077,38 @@ async function spawnAgentSession(deps2, input2) {
17988
18077
  try {
17989
18078
  const config = await loadWorkspacesConfig();
17990
18079
  if (!cwd) {
17991
- const ws = input2.workspaceSlug ? findWorkspace(config, input2.workspaceSlug) : getActiveWorkspace(config);
17992
- if (ws) {
17993
- cwd = ws.path;
17994
- resolvedSlug = ws.slug;
18080
+ if (input2.workspaceSlug) {
18081
+ const ws = findWorkspace(config, input2.workspaceSlug);
18082
+ if (ws) {
18083
+ cwd = ws.path;
18084
+ resolvedSlug = ws.slug;
18085
+ }
18086
+ } else if (callerScope) {
18087
+ const parentCwd = callerScope.ownerSessionId ? registry.get(callerScope.ownerSessionId)?.cwd : void 0;
18088
+ if (parentCwd) {
18089
+ cwd = parentCwd;
18090
+ const ws = findWorkspaceByPath(config, parentCwd);
18091
+ if (ws) {
18092
+ resolvedSlug = ws.slug;
18093
+ }
18094
+ }
18095
+ } else {
18096
+ const ws = getActiveWorkspace(config);
18097
+ if (ws) {
18098
+ cwd = ws.path;
18099
+ resolvedSlug = ws.slug;
18100
+ }
17995
18101
  }
17996
18102
  } else if (!resolvedSlug) {
17997
18103
  const ws = findWorkspaceByPath(config, cwd);
17998
18104
  if (ws) {
17999
18105
  resolvedSlug = ws.slug;
18106
+ } else {
18107
+ const identity = resolveWorktreeIdentity(cwd);
18108
+ if (identity?.mainRepoPath) {
18109
+ const baseWs = findWorkspaceByPath(config, identity.mainRepoPath);
18110
+ if (baseWs) resolvedSlug = baseWs.slug;
18111
+ }
18000
18112
  }
18001
18113
  }
18002
18114
  } catch {
@@ -18202,7 +18314,8 @@ async function spawnAgentSession(deps2, input2) {
18202
18314
  const effectivePrompt = input2.prompt ? `${composeRoleContext(role, input2.promptAppend, roleRegistry)}
18203
18315
 
18204
18316
  ${input2.prompt}` : input2.prompt;
18205
- const initialTitle = input2.prompt ? deriveSessionTitle(input2.prompt) : void 0;
18317
+ const explicitTitle = input2.title?.trim() ? input2.title.trim() : void 0;
18318
+ const initialTitle = explicitTitle ?? (input2.prompt ? deriveSessionTitle(input2.prompt) : void 0);
18206
18319
  let settleClaim;
18207
18320
  if (input2.idempotencyKey) {
18208
18321
  const claims = claimsFor(registry);
@@ -21013,6 +21126,90 @@ function registerSessionTools(rawServer, opts) {
21013
21126
  }
21014
21127
  }
21015
21128
  );
21129
+ server.tool(
21130
+ "session_rename",
21131
+ "Set or clear a session's user-facing name \u2014 the label the sessions tree, transcript header, and tab show. `label` out-ranks `title` in that display chain, so a user rename should write `label` (the default a UI picks) to be sure it shows; `title` is the auto-derived first-sentence fallback. For EACH of `title`/`label`: a non-empty string sets it (trimmed + length-capped), an empty string clears it (reverting to the derived title / a friendly `adapter \xB7 id` fallback), and omitting it leaves that field untouched. Persists across daemon restarts. Does NOT rename the adapter-native session or touch the running agent.",
21132
+ {
21133
+ idOrName: z.string().min(1).describe("Session id or name to rename \u2014 from `session_list`."),
21134
+ label: z.string().optional().describe(
21135
+ "New label (the winning display field). Empty string clears it. Omit to leave the label untouched."
21136
+ ),
21137
+ title: z.string().optional().describe(
21138
+ "New title (the auto-derived fallback slot). Empty string clears it, reverting to the first-sentence derivation. Omit to leave it untouched."
21139
+ )
21140
+ },
21141
+ async (input2) => {
21142
+ const prev = registry.findByIdOrName(input2.idOrName);
21143
+ if (!prev) {
21144
+ return {
21145
+ content: [
21146
+ {
21147
+ type: "text",
21148
+ text: JSON.stringify({ error: `no session "${input2.idOrName}" found` })
21149
+ }
21150
+ ],
21151
+ isError: true
21152
+ };
21153
+ }
21154
+ if (callerScope) {
21155
+ const subtree = collectSubtree(
21156
+ callerScope.ownerSessionId,
21157
+ registry.list({ includeArchived: true })
21158
+ );
21159
+ if (!subtree.has(prev.id)) {
21160
+ return {
21161
+ content: [
21162
+ {
21163
+ type: "text",
21164
+ text: JSON.stringify({
21165
+ error: "orchestrator_session_out_of_scope",
21166
+ message: `session_rename: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only rename sessions it (transitively) spawned.`,
21167
+ ok: false,
21168
+ sessionId: prev.id
21169
+ })
21170
+ }
21171
+ ],
21172
+ isError: true
21173
+ };
21174
+ }
21175
+ }
21176
+ if (input2.title === void 0 && input2.label === void 0) {
21177
+ return {
21178
+ content: [
21179
+ {
21180
+ type: "text",
21181
+ text: JSON.stringify({
21182
+ error: "nothing_to_rename",
21183
+ message: "session_rename: supply at least one of `title` or `label`.",
21184
+ ok: false,
21185
+ sessionId: prev.id
21186
+ })
21187
+ }
21188
+ ],
21189
+ isError: true
21190
+ };
21191
+ }
21192
+ try {
21193
+ const desc = registry.renameSession(prev.id, {
21194
+ ...input2.title !== void 0 ? { title: input2.title } : {},
21195
+ ...input2.label !== void 0 ? { label: input2.label } : {}
21196
+ });
21197
+ return {
21198
+ content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
21199
+ };
21200
+ } catch (err) {
21201
+ return {
21202
+ content: [
21203
+ {
21204
+ type: "text",
21205
+ text: `session_rename: ${err instanceof Error ? err.message : String(err)}`
21206
+ }
21207
+ ],
21208
+ isError: true
21209
+ };
21210
+ }
21211
+ }
21212
+ );
21016
21213
  server.tool(
21017
21214
  "terminal_start",
21018
21215
  "Spawn a process under a real PTY (node-pty) on the host. Bytes (including ANSI escapes, alt-screen sequences) flow through the daemon's byte ring buffer; subscribers attach via the WS at /sessions/:id/pty. Use for interactive TUIs (claude, vim, htop) or to orchestrate shells from another agent. Returns the session descriptor.",
@@ -24221,60 +24418,6 @@ function createTerminalTranscriptWriter(opts) {
24221
24418
  }
24222
24419
  };
24223
24420
  }
24224
- var markerSchema = z.object({ worktreeId: z.string() });
24225
- function statOrUndefined(path) {
24226
- try {
24227
- return statSync(path);
24228
- } catch {
24229
- return void 0;
24230
- }
24231
- }
24232
- function readWorktreeGitDir(dir) {
24233
- const link = (() => {
24234
- try {
24235
- return readFileSync(join(dir, ".git"), "utf8");
24236
- } catch {
24237
- return void 0;
24238
- }
24239
- })();
24240
- if (link === void 0) return void 0;
24241
- const target = /^gitdir:\s*(.+)$/m.exec(link)?.[1]?.trim();
24242
- if (!target) return void 0;
24243
- const gitDir = isAbsolute(target) ? target : resolve(dir, target);
24244
- return statOrUndefined(join(gitDir, "gitdir"))?.isFile() === true ? gitDir : void 0;
24245
- }
24246
- function readWorktreeId(gitDir) {
24247
- let raw;
24248
- try {
24249
- raw = readFileSync(join(gitDir, "agentproto-worktree.json"), "utf8");
24250
- } catch {
24251
- return void 0;
24252
- }
24253
- let parsed;
24254
- try {
24255
- parsed = JSON.parse(raw);
24256
- } catch {
24257
- return void 0;
24258
- }
24259
- const result = markerSchema.safeParse(parsed);
24260
- return result.success ? result.data.worktreeId : void 0;
24261
- }
24262
- function resolveWorktreeIdentity(cwd) {
24263
- let dir = resolve(cwd);
24264
- for (; ; ) {
24265
- const dotGit = statOrUndefined(join(dir, ".git"));
24266
- if (dotGit) {
24267
- if (!dotGit.isFile()) return void 0;
24268
- const gitDir = readWorktreeGitDir(dir);
24269
- if (gitDir === void 0) return void 0;
24270
- const worktreeId = readWorktreeId(gitDir);
24271
- return worktreeId === void 0 ? { worktreePath: dir } : { worktreePath: dir, worktreeId };
24272
- }
24273
- const parent = dirname(dir);
24274
- if (parent === dir) return void 0;
24275
- dir = parent;
24276
- }
24277
- }
24278
24421
  function normalizeAgentPromptOptions(raw) {
24279
24422
  if (!Array.isArray(raw)) return void 0;
24280
24423
  const labels = raw.map((o) => {
@@ -25810,6 +25953,33 @@ function createSessionsRegistry(opts) {
25810
25953
  stampProcessAlive(rt.desc);
25811
25954
  return rt.desc;
25812
25955
  },
25956
+ renameSession(id, patch) {
25957
+ const rt = sessions.get(id);
25958
+ if (!rt) throw new Error(`renameSession: no session "${id}"`);
25959
+ const apply = (field) => {
25960
+ const raw = patch[field];
25961
+ if (raw === void 0) return;
25962
+ const trimmed = raw === null ? "" : raw.trim();
25963
+ if (trimmed === "") {
25964
+ rt.desc[field] = void 0;
25965
+ return;
25966
+ }
25967
+ const points = Array.from(trimmed);
25968
+ rt.desc[field] = points.length > MAX_LENGTH ? points.slice(0, MAX_LENGTH).join("") : trimmed;
25969
+ };
25970
+ apply("title");
25971
+ apply("label");
25972
+ schedulePersist();
25973
+ sessionEvents?.emit({
25974
+ type: "session:renamed",
25975
+ sessionId: id,
25976
+ ...rt.desc.title !== void 0 ? { title: rt.desc.title } : {},
25977
+ ...rt.desc.label !== void 0 ? { label: rt.desc.label } : {},
25978
+ ts: (/* @__PURE__ */ new Date()).toISOString()
25979
+ });
25980
+ stampProcessAlive(rt.desc);
25981
+ return rt.desc;
25982
+ },
25813
25983
  listPendingPermissions(filter) {
25814
25984
  const all = Array.from(pendingPermissions.values());
25815
25985
  const scoped = filter?.sessionId ? all.filter((p2) => p2.sessionId === filter.sessionId) : all;
@@ -28942,6 +29112,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
28942
29112
  })() : {},
28943
29113
  ...typeof b2.prompt === "string" ? { prompt: b2.prompt } : {},
28944
29114
  ...typeof b2.label === "string" ? { label: b2.label } : {},
29115
+ // Explicit title override (SPEC-3 FIX C, `--title`) — wins over the
29116
+ // first-sentence derivation from the prompt (see session-spawn.ts).
29117
+ ...typeof b2.title === "string" ? { title: b2.title } : {},
28945
29118
  ...typeof b2.idempotencyKey === "string" && b2.idempotencyKey.length > 0 ? { idempotencyKey: b2.idempotencyKey } : {},
28946
29119
  ...typeof b2.role === "string" && b2.role.length > 0 ? { role: b2.role } : {},
28947
29120
  ...typeof b2.promptAppend === "string" ? { promptAppend: b2.promptAppend } : {},
@@ -29198,6 +29371,49 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29198
29371
  }
29199
29372
  return true;
29200
29373
  }
29374
+ const terminalInputMatch = path.match(/^\/sessions\/([^/]+)\/terminal\/input$/);
29375
+ if (terminalInputMatch && req.method === "POST") {
29376
+ const id2 = terminalInputMatch[1];
29377
+ if (!id2) return false;
29378
+ if (!ptyEnabled) {
29379
+ json(501, {
29380
+ error: "pty_not_configured",
29381
+ message: "POST /sessions/:id/terminal/input needs the host to inject `spawnPty` into createGateway (node-pty optional dep \u2014 install in @agentproto/cli)."
29382
+ });
29383
+ return true;
29384
+ }
29385
+ const desc = registry.get(id2);
29386
+ if (!desc) {
29387
+ json(404, { error: "no_session", message: `no session "${id2}"` });
29388
+ return true;
29389
+ }
29390
+ const body = await readJsonBody(req);
29391
+ const text6 = body?.text;
29392
+ if (typeof text6 !== "string") {
29393
+ json(400, { error: "missing_text", message: "Body `text` must be a string." });
29394
+ return true;
29395
+ }
29396
+ if (desc.kind !== "terminal" || desc.pty !== true) {
29397
+ json(400, {
29398
+ error: "not_a_pty",
29399
+ message: `session "${id2}" is not a live PTY (kind=${desc.kind})`
29400
+ });
29401
+ return true;
29402
+ }
29403
+ const enter = body?.enter !== false;
29404
+ let ok = true;
29405
+ if (text6.length > 0) ok = registry.writeTerminalInput(id2, text6) && ok;
29406
+ if (enter) ok = registry.writeTerminalInput(id2, "\r") && ok;
29407
+ if (!ok) {
29408
+ json(400, {
29409
+ error: "not_a_pty",
29410
+ message: `session "${id2}" has no live PTY to write to`
29411
+ });
29412
+ return true;
29413
+ }
29414
+ json(200, { ok: true });
29415
+ return true;
29416
+ }
29201
29417
  const modelMatch = path.match(/^\/sessions\/([^/]+)\/model$/);
29202
29418
  if (modelMatch && req.method === "POST") {
29203
29419
  const id2 = modelMatch[1];
@@ -29321,6 +29537,31 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29321
29537
  }
29322
29538
  return true;
29323
29539
  }
29540
+ const renameMatch = path.match(/^\/sessions\/([^/]+)$/);
29541
+ if (renameMatch && req.method === "PATCH") {
29542
+ const rawIdOrName2 = renameMatch[1];
29543
+ if (!rawIdOrName2) return false;
29544
+ const resolved = registry.findByIdOrName(rawIdOrName2);
29545
+ if (!resolved) {
29546
+ json(404, { error: "session_not_found", id: rawIdOrName2 });
29547
+ return true;
29548
+ }
29549
+ const body = await readJsonBody(req);
29550
+ const b2 = body && typeof body === "object" ? body : {};
29551
+ const field = (v2) => typeof v2 === "string" ? v2 : v2 === null ? null : void 0;
29552
+ const patch = {
29553
+ ..."title" in b2 ? { title: field(b2.title) } : {},
29554
+ ..."label" in b2 ? { label: field(b2.label) } : {}
29555
+ };
29556
+ try {
29557
+ const desc = registry.renameSession(resolved.id, patch);
29558
+ json(200, desc);
29559
+ } catch (err) {
29560
+ const msg = err instanceof Error ? err.message : String(err);
29561
+ json(msg.includes("no session") ? 404 : 500, { error: "rename_failed", message: msg });
29562
+ }
29563
+ return true;
29564
+ }
29324
29565
  if (path === "/sessions" && req.method === "POST") {
29325
29566
  const body = await readJsonBody(req);
29326
29567
  if (!body || typeof body !== "object") {
@@ -39173,8 +39414,8 @@ async function runServe(args) {
39173
39414
  values.workspace ?? cfgDaemon.workspace ?? process.cwd()
39174
39415
  );
39175
39416
  try {
39176
- const stat7 = await promises.stat(workspace);
39177
- if (!stat7.isDirectory()) {
39417
+ const stat8 = await promises.stat(workspace);
39418
+ if (!stat8.isDirectory()) {
39178
39419
  process.stderr.write(
39179
39420
  `agentproto serve: --workspace "${workspace}" is not a directory.
39180
39421
  `
@@ -39581,7 +39822,7 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
39581
39822
  async function runOneTunnel(opts, gateway, announcedTools, spawnPty, signal, reconnectState) {
39582
39823
  if (!opts.connect) throw new Error("runOneTunnel: --connect not set");
39583
39824
  const headers = {
39584
- "user-agent": `agentproto/${"0.9.0"}`
39825
+ "user-agent": `agentproto/${"0.10.0"}`
39585
39826
  };
39586
39827
  if (opts.token) headers.authorization = `Bearer ${opts.token}`;
39587
39828
  const ws = new WebSocket2(opts.connect, { headers });