@join-pantheon/cli 0.26.4 → 0.27.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/install.js CHANGED
@@ -4,11 +4,12 @@ import { homedir, platform } from "node:os";
4
4
  import { join, dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { grantToolPermissions, claudeSettingsFile, cursorCliConfigFile } from "./grants.js";
7
- import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
7
+ import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES, toolsAllowed } from "./tools.js";
8
8
  import { load } from "./config.js";
9
9
  import { loadProfile } from "./profile.js";
10
10
  import { agentSpawn, onPathNamed, which } from "./which.js";
11
11
  import { FEEDBACK_ADDRESS } from "./feedback.js";
12
+ import { kiroDir, kiroAgentFile, refreshKiroAgentAllowed } from "./kiro-files.js";
12
13
  const HERE = dirname(fileURLToPath(import.meta.url));
13
14
  export function onPath() {
14
15
  return onPathNamed("pantheon");
@@ -40,7 +41,7 @@ function mergeMcpJson(file, entry, shape = {}) {
40
41
  const existed = existsSync(file);
41
42
  doc.mcpServers ??= {};
42
43
  const before = JSON.stringify(doc.mcpServers["pantheon"] ?? null);
43
- doc.mcpServers["pantheon"] = { ...shape, ...entry, env: {} };
44
+ doc.mcpServers["pantheon"] = { ...shape, ...entry, env: shape.env ?? {} };
44
45
  if (before === JSON.stringify(doc.mcpServers["pantheon"]))
45
46
  return "unchanged";
46
47
  mkdirSync(dirname(file), { recursive: true });
@@ -61,6 +62,12 @@ export function mergeVsCodeMcpJson(file, entry) {
61
62
  return existed ? "updated" : "created";
62
63
  }
63
64
  export const copilotMcpConfigFile = (home = homedir(), env = process.env) => join(env.COPILOT_HOME || join(home, ".copilot"), "mcp-config.json");
65
+ export { kiroDir };
66
+ export const kiroMcpConfigFile = (home = homedir(), env = process.env) => join(kiroDir(home, env), "settings", "mcp.json");
67
+ export const kiroEntryShape = (approval) => ({
68
+ autoApprove: toolsAllowed(approval),
69
+ env: { PANTHEON_AGENT: "kiro" },
70
+ });
64
71
  export function vsCodeUserDir(home = homedir(), os = platform(), env = process.env) {
65
72
  const dir = os === "darwin" ? join(home, "Library", "Application Support", "Code", "User")
66
73
  : os === "win32" ? join(env.APPDATA || join(home, "AppData", "Roaming"), "Code", "User")
@@ -131,6 +138,9 @@ export function install(projectDir, cfg, force, os = platform(), claudeLocal = c
131
138
  if (existsSync(p) || f === "AGENTS.md")
132
139
  out.push(` ${f.padEnd(25)} ${upsertBrief(p, cfg)} (the collaboration brief)`);
133
140
  }
141
+ if (existsSync(kiroDir()) || onPathNamed("kiro-cli")) {
142
+ out.push(` .kiro/settings/mcp.json ${"not used".padEnd(9)} Kiro reads ~/.kiro/settings/mcp.json, which pantheon join wrote — a repo copy would override each teammate's own (kiro)`);
143
+ }
134
144
  out.push("");
135
145
  out.push(" Copilot asks once to trust this folder before it reads the repo's MCP servers and hooks.");
136
146
  out.push(" Codex reads ~/.codex/config.toml, which `pantheon join` already wrote at user scope.");
@@ -251,6 +261,21 @@ function copilotUserScope(entry) {
251
261
  return { tool, where: file, status: `failed: ${e.message}` };
252
262
  }
253
263
  }
264
+ function kiroUserScope(entry, approval) {
265
+ const tool = "kiro";
266
+ const file = kiroMcpConfigFile();
267
+ if (!existsSync(kiroDir()) && !onPathNamed("kiro-cli"))
268
+ return { tool, where: "-", status: "skipped (kiro-cli not on PATH)" };
269
+ try {
270
+ const status = mergeMcpJson(file, entry, kiroEntryShape(approval));
271
+ const agent = refreshKiroAgentAllowed(kiroAgentFile(kiroDir()), toolsAllowed(approval));
272
+ const which = approval === "ask-before-sending" ? "reads run without asking; send, open_goal and claim prompt" : `the ${PANTHEON_TOOL_NAMES.length} pantheon tools run without asking`;
273
+ return { tool, where: file, status: `${status} — in the Kiro IDE, ${which}${agent === "absent" ? "; the CLI asks per call unless you use the pantheon agent (pantheon hook install)" : `; in the CLI's pantheon agent too (${agent})`}` };
274
+ }
275
+ catch (e) {
276
+ return { tool, where: file, status: `failed: ${e.message}` };
277
+ }
278
+ }
254
279
  function vsCodeUserScope(entry) {
255
280
  const tool = "vs code";
256
281
  const dir = vsCodeUserDir();
@@ -268,7 +293,7 @@ export const CODEX_APPROVAL_LINE = `default_tools_approval_mode = "approve"`;
268
293
  export function approvalSummary(approval) {
269
294
  return approval === "ask-before-sending"
270
295
  ? `your agent asks you before each send, goal or claim in your name; reading and waiting run without a prompt. Unattended runs keep their own per-run approvals — this governs the agent you sit in front of`
271
- : `pantheon's ${PANTHEON_TOOL_NAMES.length} tools run without a per-call prompt in Claude Code, Cursor and Codex — your agent coordinates for you without stopping to ask. Copilot has no allow-list file: it asks once per tool per folder and remembers your answer`;
296
+ : `pantheon's ${PANTHEON_TOOL_NAMES.length} tools run without a per-call prompt in Claude Code, Cursor, Codex, the Kiro IDE and Kiro's pantheon agent — your agent coordinates for you without stopping to ask. Copilot has no allow-list file: it asks once per tool per folder and remembers your answer; Kiro's default CLI agent asks per call`;
272
297
  }
273
298
  export function codexTomlBlock(entry, env = { PANTHEON_AGENT: "codex" }, approval = "approve") {
274
299
  const envToml = Object.entries(env)
@@ -466,7 +491,7 @@ export function installForUser(approval = chosenApproval()) {
466
491
  const entry = userEntry();
467
492
  return [
468
493
  claudeUserScope(entry), cursorUserScope(entry), codexUserScope(entry, approval),
469
- copilotUserScope(entry), vsCodeUserScope(entry),
494
+ copilotUserScope(entry), vsCodeUserScope(entry), kiroUserScope(entry, approval),
470
495
  ...grantToolPermissions(approval).map((g) => ({
471
496
  tool: g.tool,
472
497
  where: g.tool.startsWith("claude") ? claudeSettingsFile() : cursorCliConfigFile(),
@@ -0,0 +1,56 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ export const kiroDir = (home = homedir(), env = process.env) => env.KIRO_HOME || join(home, ".kiro");
5
+ export const KIRO_AGENT_NAME = "pantheon";
6
+ export const kiroAgentFile = (kiroBase) => join(kiroBase, "agents", `${KIRO_AGENT_NAME}.json`);
7
+ export const isOurKiroHook = (cmd) => !!cmd && /\bhook\s+kiro-(?:prompt|stop)\b/.test(cmd);
8
+ export const kiroAllowed = (tools) => tools.map((t) => `@pantheon/${t}`);
9
+ export function kiroAgentDoc(bin, allowed) {
10
+ return {
11
+ name: KIRO_AGENT_NAME,
12
+ description: "Kiro's default agent, plus Pantheon: its tools run without asking, and a turn is held open while a teammate is waiting on you",
13
+ tools: ["*"],
14
+ allowedTools: kiroAllowed(allowed),
15
+ includeMcpJson: true,
16
+ hooks: {
17
+ userPromptSubmit: [{ command: `${bin} hook kiro-prompt >/dev/null 2>&1` }],
18
+ stop: [{ command: `${bin} hook kiro-stop` }],
19
+ },
20
+ };
21
+ }
22
+ export function readKiroAgent(file) {
23
+ if (!existsSync(file))
24
+ return null;
25
+ try {
26
+ const doc = JSON.parse(readFileSync(file, "utf8"));
27
+ const events = Object.entries(doc.hooks ?? {}).filter(([, es]) => (es ?? []).some((h) => isOurKiroHook(h.command))).map(([e]) => e);
28
+ return { doc, ours: events.length > 0, events };
29
+ }
30
+ catch {
31
+ return { doc: {}, ours: false, events: [] };
32
+ }
33
+ }
34
+ export function writeKiroAgent(file, doc) {
35
+ mkdirSync(dirname(file), { recursive: true });
36
+ writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
37
+ }
38
+ export function refreshKiroAgentAllowed(file, allowed) {
39
+ const a = readKiroAgent(file);
40
+ if (!a?.ours)
41
+ return "absent";
42
+ const want = kiroAllowed(allowed);
43
+ if (JSON.stringify(a.doc.allowedTools) === JSON.stringify(want))
44
+ return "unchanged";
45
+ writeKiroAgent(file, { ...a.doc, allowedTools: want });
46
+ return "updated";
47
+ }
48
+ export function kiroDefaultIsPantheon(base = kiroDir()) {
49
+ try {
50
+ return JSON.parse(readFileSync(join(base, "settings", "cli.json"), "utf8"))["chat.defaultAgent"] === KIRO_AGENT_NAME;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ //# sourceMappingURL=kiro-files.js.map
package/dist/ledger.js CHANGED
@@ -35,6 +35,9 @@ export class Ledger {
35
35
  since(seq, limit = 500) {
36
36
  return this.records.filter((r) => r.seq > seq).slice(0, limit);
37
37
  }
38
+ latest(limit = 500) {
39
+ return this.records.slice(Math.max(0, this.records.length - limit));
40
+ }
38
41
  byGoal(goal, limit = 200) {
39
42
  const hits = this.records.filter((r) => r.goal === goal);
40
43
  return hits.slice(Math.max(0, hits.length - limit));
package/dist/machine.js CHANGED
@@ -4,7 +4,7 @@ import { homedir, platform } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { CONFIG_DIR } from "./config.js";
6
6
  import { plistPath } from "./service.js";
7
- import { copilotMcpConfigFile, vsCodeUserDir } from "./install.js";
7
+ import { copilotMcpConfigFile, vsCodeUserDir, kiroMcpConfigFile, kiroDir } from "./install.js";
8
8
  const sh = (cmd) => {
9
9
  try {
10
10
  return execFileSync("sh", ["-lc", cmd], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
@@ -120,6 +120,10 @@ export function wiring() {
120
120
  const f = join(vscodeDir, "mcp.json");
121
121
  out.push({ tool: "vs code", state: existsSync(f) && /"pantheon"/.test(readFileSync(f, "utf8")) ? "wired" : "not wired" });
122
122
  }
123
+ if (existsSync(kiroDir())) {
124
+ const f = kiroMcpConfigFile();
125
+ out.push({ tool: "kiro", state: existsSync(f) && /"pantheon"/.test(readFileSync(f, "utf8")) ? "wired" : "not wired" });
126
+ }
123
127
  return out;
124
128
  }
125
129
  export function logFileFor(kind, proc) {
package/dist/mcp.js CHANGED
@@ -473,6 +473,7 @@ export async function runMcp() {
473
473
  url.pathname = "/history";
474
474
  url.searchParams.set("space", cfg.space);
475
475
  url.searchParams.set("token", cfg.token);
476
+ url.searchParams.set("goal", goal);
476
477
  return fetch(url).then(async (res) => {
477
478
  const { acts } = (await res.json());
478
479
  const hits = acts.filter((a) => a.goal === goal).slice(-(limit ?? 60));
package/dist/measure.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
3
3
  import { resolve, dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { claudeGrantedEffective, cursorGrantedEffective, claudeAllow, cursorAllow, isServerWide } from "./grants.js";
6
- import { codexApprovalState, copilotMcpConfigFile } from "./install.js";
6
+ import { codexApprovalState, copilotMcpConfigFile, kiroMcpConfigFile } from "./install.js";
7
7
  import { existsSync as fileExists, readFileSync } from "node:fs";
8
8
  import { dirname as dirOf } from "node:path";
9
9
  import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
@@ -161,13 +161,41 @@ export function toolsReport(registered, seen, approval, opts = {}) {
161
161
  }
162
162
  const copilot = seen.copilot === undefined || seen.copilot === null ? null
163
163
  : seen.copilot ? "Copilot wired (it asks once per tool per folder and remembers)" : "Copilot has no pantheon entry";
164
- const detail = `${head}; ${claude.text}, ${cursor.text}, ${codex}${copilot ? `, ${copilot}` : ""}`;
164
+ let kiro = null;
165
+ if (seen.kiro === "absent")
166
+ kiro = "Kiro has no pantheon entry";
167
+ else if (Array.isArray(seen.kiro)) {
168
+ const want = [...(approval === "ask-before-sending" ? PANTHEON_TOOL_NAMES.filter((t) => !WRITE_TOOL_NAMES.includes(t)) : PANTHEON_TOOL_NAMES)].sort();
169
+ const have = [...new Set(seen.kiro)].filter((t) => t.startsWith("pantheon_")).sort();
170
+ const extra = have.filter((t) => !want.includes(t)), missing = want.filter((t) => !have.includes(t));
171
+ if (!extra.length && !missing.length)
172
+ kiro = `Kiro IDE allows ${have.length}`;
173
+ else {
174
+ kiro = `Kiro IDE allows ${have.length}${missing.length ? `, missing ${missing.map((t) => t.replace(/^pantheon_/, "")).join(", ")}` : ""}${extra.length ? `, and allows ${extra.map((t) => t.replace(/^pantheon_/, "")).join(", ")} you chose to be asked about` : ""}`;
175
+ problems.push("Kiro's autoApprove does not match");
176
+ }
177
+ }
178
+ const detail = `${head}; ${claude.text}, ${cursor.text}, ${codex}${copilot ? `, ${copilot}` : ""}${kiro ? `, ${kiro}` : ""}`;
165
179
  if (!problems.length)
166
180
  return { level: "pass", detail };
167
181
  return { level: "warn", detail, fix: `${problems.join("; ")} — pantheon install --user rewrites every grant in your own settings to match this build and your approval choice; a rule in this repo's .claude/settings*.json or .cursor/cli.json is yours to edit by hand` };
168
182
  }
169
183
  export function grantsSeen(projectDir = null) {
170
- return { claude: claudeGrantedEffective(projectDir), cursor: cursorGrantedEffective(projectDir), codex: codexApprovalState(), copilot: copilotWired() };
184
+ return { claude: claudeGrantedEffective(projectDir), cursor: cursorGrantedEffective(projectDir), codex: codexApprovalState(), copilot: copilotWired(), kiro: kiroApproved() };
185
+ }
186
+ export function kiroApproved(file = kiroMcpConfigFile()) {
187
+ if (!fileExists(dirOf(dirOf(file))))
188
+ return null;
189
+ try {
190
+ const doc = JSON.parse(readFileSync(file, "utf8"));
191
+ const entry = doc.mcpServers?.["pantheon"];
192
+ if (!entry)
193
+ return "absent";
194
+ return Array.isArray(entry.autoApprove) ? entry.autoApprove.filter((t) => typeof t === "string") : [];
195
+ }
196
+ catch {
197
+ return "absent";
198
+ }
171
199
  }
172
200
  export function copilotWired(file = copilotMcpConfigFile()) {
173
201
  if (!fileExists(dirOf(file)))
package/dist/notice.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { decisionPrompt, decisionTitle } from "./decision-ui.js";
2
- export const TOOL_LABELS = { "claude-code": "Claude Code", codex: "Codex", cursor: "Cursor", copilot: "Copilot", grok: "Grok" };
2
+ export const TOOL_LABELS = { "claude-code": "Claude Code", codex: "Codex", cursor: "Cursor", copilot: "Copilot", kiro: "Kiro", grok: "Grok" };
3
3
  export const APP_TOOLS = [
4
4
  { id: "cursor-app", label: "Cursor app", kind: "app", bundle: "com.todesktop.230313mzl4w4u92", deeplink: "cursor://anysphere.cursor-deeplink/prompt?text={prompt}" },
5
5
  { id: "claude-app", label: "Claude app · question copied", kind: "app", bundle: "com.anthropic.claudefordesktop" },
@@ -67,6 +67,8 @@ const agentLabel = (agent) => {
67
67
  return "Cursor";
68
68
  if (a.includes("copilot"))
69
69
  return "Copilot";
70
+ if (a.includes("kiro"))
71
+ return "Kiro";
70
72
  return null;
71
73
  };
72
74
  function whoLine(from, agent, ts, now) {
package/dist/notify.js CHANGED
@@ -1,12 +1,14 @@
1
+ import { chosenApproval } from "./install.js";
2
+ import { toolsAllowed } from "./tools.js";
1
3
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
2
4
  import { homedir, platform } from "node:os";
3
5
  import { dirname, join, resolve } from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
  import { whichPantheon } from "./service.js";
6
8
  import { detectForHooks, agentById } from "./agents.js";
7
- import { codexHookState, cursorHookState, copilotHookState, installCodexHooks, installCursorHooks, installCopilotHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, } from "./hooks-install.js";
9
+ import { codexHookState, cursorHookState, copilotHookState, kiroHookState, installCodexHooks, installCursorHooks, installCopilotHooks, installKiroHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, removeKiroHooks, } from "./hooks-install.js";
8
10
  export const HOOK_MARKER = "pantheon hook";
9
- export const HOOK_RE = /(?:pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s|cli\.js"?\s+hook\s+(?:stop|prompt|end|codex-stop|copilot-stop|cursor-stop)\b)/i;
11
+ export const HOOK_RE = /(?:pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s|cli\.js"?\s+hook\s+(?:stop|prompt|end|codex-stop|copilot-stop|cursor-stop|kiro-stop|kiro-prompt)\b)/i;
10
12
  export const isOurHookCommand = (cmd) => !!cmd && (cmd.includes(HOOK_MARKER) || HOOK_RE.test(cmd));
11
13
  export const hookBin = (bin) => (/\s/.test(bin) ? `"${bin}"` : bin);
12
14
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -108,6 +110,9 @@ export function agentHookStates(projectDir = process.cwd(), only) {
108
110
  const copilot = copilotHookState(scope, projectDir);
109
111
  if (copilot.installed)
110
112
  out.push(copilot);
113
+ const kiro = kiroHookState(scope, projectDir);
114
+ if (kiro.installed)
115
+ out.push(kiro);
111
116
  }
112
117
  const byAgent = new Map();
113
118
  for (const s of out) {
@@ -133,6 +138,8 @@ export function repairHook(home) {
133
138
  missing.push(id);
134
139
  if (id === "copilot" && !copilotHookState(intent.scope, projectDir).installed)
135
140
  missing.push(id);
141
+ if (id === "kiro" && !kiroHookState(intent.scope, projectDir).installed)
142
+ missing.push(id);
136
143
  }
137
144
  if (!missing.length)
138
145
  return null;
@@ -184,7 +191,7 @@ export function installHooks(scope, projectDir = process.cwd(), only) {
184
191
  ? only.map((id) => agentById(id)).filter((b) => !!b)
185
192
  : available;
186
193
  if (!pick.length) {
187
- throw new Error("no agent found — install claude, codex, cursor-agent, or copilot, or open a project that already uses one");
194
+ throw new Error("no agent found — install claude, codex, cursor-agent, copilot, or kiro-cli, or open a project that already uses one");
188
195
  }
189
196
  const lines = [];
190
197
  const agents = [];
@@ -206,6 +213,10 @@ export function installHooks(scope, projectDir = process.cwd(), only) {
206
213
  lines.push(...installCopilotHooks(bin, scope, projectDir));
207
214
  agents.push("copilot");
208
215
  }
216
+ else if (backend.id === "kiro") {
217
+ lines.push(...installKiroHooks(bin, scope, projectDir, toolsAllowed(chosenApproval())));
218
+ agents.push("kiro");
219
+ }
209
220
  }
210
221
  lines.push(` effect when your agent tries to finish, it checks whether a peer is waiting`, ` on you — and keeps going instead of leaving them hanging.`, ` it also reports when a turn starts and ends, so a worker can tell`, ` an idle editor from one you are actually using.`);
211
222
  return { lines, agents };
@@ -219,6 +230,7 @@ export function removeHook(projectDir = process.cwd()) {
219
230
  out.push(...removeCodexHooks(projectDir));
220
231
  out.push(...removeCursorHooks(projectDir));
221
232
  out.push(...removeCopilotHooks(projectDir));
233
+ out.push(...removeKiroHooks(projectDir));
222
234
  return out.length ? out : [" nothing installed"];
223
235
  }
224
236
  function removeClaudeHook(projectDir = process.cwd()) {
package/dist/persona.js CHANGED
@@ -170,7 +170,7 @@ export const PERSONA_TEMPLATE = `---
170
170
  # wake_by: alice, bob # only these peers' requests are handled by this persona
171
171
  # kinds: ANSWER, ACCEPT # ANSWER (asks), ACCEPT (verdicts), REVISE (objections), CONTINUE (replies/deliveries)
172
172
  # goals: launch-* # only goals whose id or title matches
173
- # agent: auto # claude-code | codex | cursor | copilot | auto
173
+ # agent: auto # claude-code | codex | cursor | copilot | kiro | auto
174
174
  ---
175
175
  Say how this worker should behave when it is woken while you are away — what it
176
176
  should take on, what it should refuse, and where its judgment ends and yours
package/dist/profile.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { CONFIG_DIR } from "./config.js";
4
- export const AGENT_SURFACES = ["codex", "claude-code", "cursor", "copilot"];
4
+ export const AGENT_SURFACES = ["codex", "claude-code", "cursor", "copilot", "kiro"];
5
5
  export const SURFACE_BUNDLE_IDS = {
6
6
  codex: "com.openai.codex",
7
7
  "claude-code": "com.anthropic.claudefordesktop",
8
8
  cursor: "com.todesktop.230313mzl4w4u92",
9
9
  copilot: "com.microsoft.VSCode",
10
+ kiro: "com.amazon.codewhisperer",
10
11
  };
11
12
  export const profilePath = (home = CONFIG_DIR) => join(home, "profile.json");
12
13
  export function isAgentSurface(value) {
@@ -17,6 +18,8 @@ export function surfaceFromAgent(agent) {
17
18
  return "cursor";
18
19
  if (agent && /copilot/i.test(agent))
19
20
  return "copilot";
21
+ if (agent && /kiro/i.test(agent))
22
+ return "kiro";
20
23
  if (agent && /claude/i.test(agent))
21
24
  return "claude-code";
22
25
  return "codex";
package/dist/relay.js CHANGED
@@ -288,7 +288,10 @@ export class Relay {
288
288
  const token = url.searchParams.get("token") ?? "";
289
289
  if (!space || space.token !== token)
290
290
  return this.json(res, 404, { error: "no such space" });
291
- return this.json(res, 200, { acts: space.ledger.since(Number(url.searchParams.get("since") ?? 0)) });
291
+ const goal = url.searchParams.get("goal");
292
+ const since = Number(url.searchParams.get("since") ?? 0);
293
+ const acts = goal ? space.ledger.byGoal(goal) : since > 0 ? space.ledger.since(since) : space.ledger.latest();
294
+ return this.json(res, 200, { acts, head: space.ledger.head });
292
295
  }
293
296
  if (url.pathname === "/obligations") {
294
297
  const space = this.spaces.get(url.searchParams.get("space") ?? "");
@@ -2231,11 +2234,13 @@ pantheon signup --email you@work.com --username you</code></pre>
2231
2234
  return { code: "GOAL_CLOSED", message: `goal "${goal.title}" is closed` };
2232
2235
  if (!goal.members.includes(me.id))
2233
2236
  return { code: "NOT_IN_GOAL", message: `you are not a member of "${goal.title}"` };
2234
- if (goal.acts >= goal.budget) {
2237
+ const settlesOwnHuman = act === "ANSWER" && !!ref
2238
+ && [...sp.obligations.values()].some((o) => o.kind === "HUMAN" && o.ref === ref && o.owner === `human:${me.id}`);
2239
+ if (goal.acts >= goal.budget && !settlesOwnHuman) {
2235
2240
  return { code: "BUDGET_SPENT", message: `goal "${goal.title}" spent its ${goal.budget}-act budget and needs a human` };
2236
2241
  }
2237
2242
  const run = sp.lastActor.get(goalId);
2238
- if (goal.kind !== "worklist" && run && run.member === me.id && run.run >= LIMITS.consecutive && act !== "ESCALATE" && act !== "CLOSE") {
2243
+ if (!settlesOwnHuman && goal.kind !== "worklist" && run && run.member === me.id && run.run >= LIMITS.consecutive && act !== "ESCALATE" && act !== "CLOSE") {
2239
2244
  return {
2240
2245
  code: "PEER_TURN",
2241
2246
  message: `you have sent ${run.run} acts in a row in "${goal.title}". Wait for a peer (pantheon_wait), or ESCALATE if nobody can move it.`,
@@ -5,6 +5,7 @@ export const SIGN_IN_FIX = {
5
5
  codex: "run `codex login`",
6
6
  cursor: "run `cursor-agent login`",
7
7
  copilot: "run `copilot login` (or `gh auth login` — copilot uses gh's sign-in too)",
8
+ kiro: "run `kiro-cli login` (or set KIRO_API_KEY)",
8
9
  };
9
10
  const sh = (dir, ...args) => execFileSync("git", ["-C", dir, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000 }).trim();
10
11
  export const gitProbe = {
@@ -195,7 +195,7 @@ export async function runGuide(io, ctx, deps = {}, preset = {}) {
195
195
  : "only people you approve by hand (pantheon admit <name> --may-wake)"}`);
196
196
  io.say(` • Alerts: ${shortAlert(a.notifications).toLowerCase()}`);
197
197
  io.say(` • Approval: ${a.approval === "approve"
198
- ? "your agent sends to teammates in your name without asking each time (Claude Code, Cursor and Codex; Copilot asks once per tool per folder)"
198
+ ? "your agent sends to teammates in your name without asking each time (Claude Code, Cursor, Codex, and Kiro's IDE and pantheon agent; Copilot asks once per tool per folder)"
199
199
  : "your agent asks you before each send, goal and claim; reading and waiting never prompt"}`);
200
200
  io.say(` • Decisions: ${a.decides.length ? `you personally decide ${a.decides.join(", ")}` : "none reserved for you — your agent may decide; add an area when one comes up"}`);
201
201
  if (a.wake !== "no" && ctx.project)
package/dist/tray.js CHANGED
@@ -131,20 +131,20 @@ async function trayStatusOne(cfg) {
131
131
  const items = [
132
132
  ...mineHuman.map((o) => {
133
133
  const from = senderOf(o);
134
- return row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id, decisionTitle(o.body)));
134
+ return { ...row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id, decisionTitle(o.body))), decider: cfg.member };
135
135
  }),
136
136
  ...owed.map((o) => row(o, "owed", "you", owedPrompt(decisionTitle(o.body), o.goal))),
137
137
  ...waiting.map((o) => {
138
138
  const who = o.owner.replace(/^human:/, "");
139
139
  const r = row(o, "waiting", who, waitingPrompt(who, decisionTitle(o.body), o.goal));
140
140
  return o.owner.startsWith("human:")
141
- ? { ...r, title: `${who} decides: ${r.title}`, headline: `${who} decides: ${r.headline}` }
141
+ ? { ...r, title: `${who} decides: ${r.title}`, headline: `${who} decides: ${r.headline}`, decider: who }
142
142
  : r;
143
143
  }),
144
144
  ];
145
145
  const profile = loadProfile(cfg);
146
146
  const surface = profile.agent.primary_surface;
147
- const surfaceName = surface === "claude-code" ? "Claude" : surface === "cursor" ? "Cursor" : surface === "copilot" ? "Copilot" : "Codex";
147
+ const surfaceName = surface === "claude-code" ? "Claude" : surface === "cursor" ? "Cursor" : surface === "copilot" ? "Copilot" : surface === "kiro" ? "Kiro" : "Codex";
148
148
  const rawWorkerState = workerState();
149
149
  return {
150
150
  configured: true,
@@ -0,0 +1,30 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ export function gitActivity(path) {
4
+ for (const f of ["index", "FETCH_HEAD", "HEAD"]) {
5
+ try {
6
+ return statSync(join(path, ".git", f)).mtimeMs;
7
+ }
8
+ catch { }
9
+ }
10
+ return 0;
11
+ }
12
+ export function pickWorkdir(bindings, hints, team, activity = gitActivity, exists = existsSync) {
13
+ const live = bindings.filter((b) => exists(b.path));
14
+ if (!live.length) {
15
+ return {
16
+ path: null,
17
+ note: `no folder on this machine is bound to "${team}", so this opens in your home folder. To fix it, run pantheon use ${team} --here inside the repo.`,
18
+ };
19
+ }
20
+ const named = live.filter((b) => hints.some((h) => (b.origin ? h.includes(b.origin) : false) || h.includes(b.path)));
21
+ const pool = named.length ? named : live;
22
+ const chosen = [...pool].sort((x, y) => (activity(y.path) - activity(x.path)) || (y.bound_at - x.bound_at))[0];
23
+ const others = live.filter((b) => b.path !== chosen.path).map((b) => basename(b.path));
24
+ const why = named.length ? "named in this goal" : live.length > 1 ? "your most recent git activity" : "";
25
+ return {
26
+ path: chosen.path,
27
+ note: `in ${basename(chosen.path)}${why ? ` (${why})` : ""}${others.length ? ` — "${team}" is also bound to ${others.join(", ")}` : ""}`,
28
+ };
29
+ }
30
+ //# sourceMappingURL=workdir.js.map
package/dist/worker.js CHANGED
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
6
  import { PantheonClient } from "./client.js";
7
- import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice } from "./agents.js";
7
+ import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice, unattendedRefusal } from "./agents.js";
8
8
  import { agentSpawn } from "./which.js";
9
9
  import { VERSION } from "./version.js";
10
10
  import { applyAnswer, loadPolicy, mayAsk, needsSelfUpgrade, recordAsk, savePolicy, useGrant, } from "./worker-policy.js";
@@ -604,6 +604,9 @@ export class Worker {
604
604
  this.opts.log(` agent ${said}`);
605
605
  this.lastAgentSaid = said;
606
606
  }
607
+ const refused = unattendedRefusal(choice);
608
+ if (refused)
609
+ throw new Error(refused);
607
610
  const plan = headlessSpawn({ backend: choice.backend, binary: choice.binary, prompt: brief, worktree, cfg: this.cfg, branch, allowBash, runHome });
608
611
  materializeSpawnFiles(worktree, plan);
609
612
  ({ command, args, env } = { command: plan.command, args: plan.args, env: { ...env, ...plan.env } });