@shield-agent/kya 0.1.20 → 0.1.22

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.
Files changed (48) hide show
  1. package/.mcp.json +1 -1
  2. package/LIMITATIONS.md +2 -2
  3. package/README.md +8 -5
  4. package/claude/claude_desktop_config.example.json +1 -1
  5. package/dist/commands/dash.d.ts.map +1 -1
  6. package/dist/commands/dash.js +337 -83
  7. package/dist/commands/dash.js.map +1 -1
  8. package/dist/commands/decide.d.ts.map +1 -1
  9. package/dist/commands/decide.js +5 -1
  10. package/dist/commands/decide.js.map +1 -1
  11. package/dist/dash/dash.d.ts +6 -0
  12. package/dist/dash/dash.d.ts.map +1 -1
  13. package/dist/dash/dash.js +22 -5
  14. package/dist/dash/dash.js.map +1 -1
  15. package/dist/dash/entitlement.d.ts +1 -1
  16. package/dist/dash/entitlement.d.ts.map +1 -1
  17. package/dist/dash/entitlement.js +1 -0
  18. package/dist/dash/entitlement.js.map +1 -1
  19. package/dist/dash/input.d.ts +1 -1
  20. package/dist/dash/input.d.ts.map +1 -1
  21. package/dist/dash/input.js +11 -0
  22. package/dist/dash/input.js.map +1 -1
  23. package/dist/dash/policy-cache.d.ts +18 -0
  24. package/dist/dash/policy-cache.d.ts.map +1 -0
  25. package/dist/dash/policy-cache.js +37 -0
  26. package/dist/dash/policy-cache.js.map +1 -0
  27. package/dist/dash/render.d.ts +3 -1
  28. package/dist/dash/render.d.ts.map +1 -1
  29. package/dist/dash/render.js +25 -11
  30. package/dist/dash/render.js.map +1 -1
  31. package/dist/dash/screens/free.d.ts +9 -1
  32. package/dist/dash/screens/free.d.ts.map +1 -1
  33. package/dist/dash/screens/free.js +25 -7
  34. package/dist/dash/screens/free.js.map +1 -1
  35. package/dist/orr/agentshield.d.ts.map +1 -1
  36. package/dist/orr/agentshield.js +2 -1
  37. package/dist/orr/agentshield.js.map +1 -1
  38. package/dist/orr/scorecard.d.ts +2 -0
  39. package/dist/orr/scorecard.d.ts.map +1 -1
  40. package/dist/orr/scorecard.js +20 -1
  41. package/dist/orr/scorecard.js.map +1 -1
  42. package/gemini/settings.example.json +1 -1
  43. package/grok/README.md +1 -1
  44. package/manifest.json +1 -1
  45. package/mcp.json +1 -1
  46. package/openai/codex.config.example.toml +1 -1
  47. package/package.json +1 -1
  48. package/server.json +2 -2
package/.mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.1.20", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.1.22", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
package/LIMITATIONS.md CHANGED
@@ -6,8 +6,8 @@
6
6
  - If a host never wraps tools, session shrink cannot stop it. Spawn without a control plane cannot be gated.
7
7
  - `wrap` evaluates (and may open a pending ticket). It never executes the side effect. Offline wrap does not call the approval API.
8
8
  - `invoke` authorizes on a live plane after Allow or APPROVED. It never runs the customer write in the CLI. There is no `--offline` invoke.
9
- - The TUI (`dash`) is observational. Keys `a` / `x` print the CLI decide hint; they do not APPROVE or REJECT.
10
- - Human decide is `kya approve --id` / `kya reject --id` against `POST /api/v1/kya/approvals/{id}/approve|reject` (`kya.approve` scope).
9
+ - The TUI (`dash`) can `a`/`x` decide only after `y` confirm, and only with a JWT that has `kya.approve`. Machine `sk_*` keys are refused (print the CLI hint instead).
10
+ - Human decide is also `kya approve --id` / `kya reject --id` against `POST /api/v1/kya/approvals/{id}/approve|reject` (`kya.approve` scope).
11
11
  - Shield KYA evaluate/approve is the only policy decision point (`ALLOW` / `DENY` / `REQUIRE_APPROVE`). Scanners, `--scorecard`, `harness.agentshield`, and the ORR CLI write evidence. They never ALLOW a side effect. AgentShield is opt-in (`--producer harness.agentshield` or `--agentshield-json`). The CLI never passes `--fix`, never starts MiniClaw, and never installs `ecc-agentshield`.
12
12
  - Dialog safety belongs in a guardrails product.
13
13
  - Multi-language in-process runtimes are out of scope.
package/README.md CHANGED
@@ -33,10 +33,13 @@ npx @shield-agent/kya register-agent --name solo-builder --version-hash dev-loca
33
33
  npx @shield-agent/kya eval-tool --tool-id org.sample.never.event --irreversible
34
34
  npx @shield-agent/kya serve-mcp --stdio
35
35
 
36
- # Terminal dashboard (free panes; --offline works without a key)
36
+ # Terminal desk (FREE personal panes; --offline works without a key)
37
37
  npx @shield-agent/kya dash --once --offline
38
+ # Interactive TTY: 1-8 panes, e force-eval, p auto-refresh, y confirms kill/shrink/decide
39
+ npx @shield-agent/kya dash
38
40
  ```
39
41
 
42
+
40
43
  Install hub: [https://shield-agent.com/install](https://shield-agent.com/install)
41
44
 
42
45
  ## Dual plane
@@ -79,7 +82,7 @@ MCP Registry entry: `server.json` plus package `mcpName` `io.github.The-Pixel-Bo
79
82
  "mcpServers": {
80
83
  "shield-kya": {
81
84
  "command": "npx",
82
- "args": ["--no-install", "@shield-agent/kya@0.1.20", "serve-mcp", "--stdio"],
85
+ "args": ["--no-install", "@shield-agent/kya@0.1.22", "serve-mcp", "--stdio"],
83
86
  "env": {
84
87
  "KYA_BASE_URL": "http://127.0.0.1:8090",
85
88
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -98,7 +101,7 @@ npx @shield-agent/kya approve --id <approval-id>
98
101
  npx @shield-agent/kya reject --id <approval-id>
99
102
  ```
100
103
 
101
- `wrap` evaluates and may open a pending ticket. It never executes the side effect. `invoke` asks the live plane to authorize after Allow or APPROVED. It does not run the write on this machine. The TUI (`dash`) is observational: keys `a` / `x` print CLI hints and do not decide.
104
+ `wrap` evaluates and may open a pending ticket. It never executes the side effect. `invoke` asks the live plane to authorize after Allow or APPROVED. It does not run the write on this machine. The TUI (`dash`) can `a`/`x` decide only after `y` confirm with a JWT (`sk_*` refused).
102
105
 
103
106
  ## Claude connector
104
107
 
@@ -106,7 +109,7 @@ npx @shield-agent/kya reject --id <approval-id>
106
109
 
107
110
  ```bash
108
111
  # Prefer a preinstalled package (no registry auto-install):
109
- npx --no-install @shield-agent/kya@0.1.20 serve-mcp --stdio
112
+ npx --no-install @shield-agent/kya@0.1.22 serve-mcp --stdio
110
113
  # Or after npm i -g / local install:
111
114
  kya serve-mcp --stdio
112
115
  ```
@@ -117,7 +120,7 @@ Copy `claude/claude_desktop_config.example.json` into Claude Desktop MCP setting
117
120
 
118
121
  ## OpenAI (Codex / Responses)
119
122
 
120
- **Codex CLI / IDE:** copy `openai/codex.config.example.toml` into `~/.codex/config.toml`. Local stdio uses `npx --no-install @shield-agent/kya@0.1.20 serve-mcp --stdio`. Hosted Codex uses `url = "https://shield-agent.com/mcp"` with `bearer_token_env_var = "KYA_API_KEY"`.
123
+ **Codex CLI / IDE:** copy `openai/codex.config.example.toml` into `~/.codex/config.toml`. Local stdio uses `npx --no-install @shield-agent/kya@0.1.22 serve-mcp --stdio`. Hosted Codex uses `url = "https://shield-agent.com/mcp"` with `bearer_token_env_var = "KYA_API_KEY"`.
121
124
 
122
125
  **Responses API:** see `openai/responses-mcp.example.json` (`server_url` + `Authorization: Bearer <KYA_API_KEY>`).
123
126
 
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "shield-kya": {
4
4
  "command": "npx",
5
- "args": ["--no-install", "@shield-agent/kya@0.1.20", "serve-mcp", "--stdio"],
5
+ "args": ["--no-install", "@shield-agent/kya@0.1.22", "serve-mcp", "--stdio"],
6
6
  "env": {
7
7
  "KYA_BASE_URL": "https://shield-agent.com",
8
8
  "KYA_API_KEY": "${KYA_API_KEY}",
@@ -1 +1 @@
1
- {"version":3,"file":"dash.d.ts","sourceRoot":"","sources":["../../src/commands/dash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAoC1D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;CAChB,CAkBA;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAiLjB"}
1
+ {"version":3,"file":"dash.d.ts","sourceRoot":"","sources":["../../src/commands/dash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AA0C1D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;CAChB,CAkBA;AASD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAsZjB"}
@@ -1,14 +1,18 @@
1
1
  import { stdin as stdinFd, stdout as stdoutFd } from "node:process";
2
2
  import { emitKeypressEvents } from "node:readline";
3
- import { KyaHttpClient } from "../client.js";
3
+ import { isMachineApiKey, KyaHttpClient } from "../client.js";
4
4
  import { AuthRequiredError } from "../errors.js";
5
5
  import { flagBool, flagString } from "../parse-args.js";
6
6
  import { renderDash } from "../dash/dash.js";
7
7
  import { mapKey } from "../dash/input.js";
8
+ import { PolicySampleCache } from "../dash/policy-cache.js";
9
+ import { assertNoSecrets } from "../dash/render.js";
8
10
  import { runWrap, wrapExitCode } from "./wrap.js";
9
11
  import { runInvoke } from "./invoke.js";
10
12
  import { runKillAgent, runShrinkSession } from "./ops.js";
11
13
  import { runOrr } from "./orr.js";
14
+ const AUTO_REFRESH_MS = 5_000;
15
+ /** TUI ORR stays first_party only — Scorecard spawn is CLI-explicit (no env leak / stdin race). */
12
16
  function deskOrr(cwd) {
13
17
  return runOrr({
14
18
  path: cwd,
@@ -30,6 +34,7 @@ const PANES = [
30
34
  "sessions",
31
35
  "orr",
32
36
  "mcp",
37
+ "sandbox",
33
38
  "dashboard",
34
39
  "cases",
35
40
  "metrics",
@@ -57,21 +62,14 @@ export async function runDash(config, parsed, io, env) {
57
62
  const opts = dashOptionsFromArgs(parsed);
58
63
  const tty = io.isTty ?? Boolean(stdinFd.isTTY && stdoutFd.isTTY);
59
64
  const once = opts.once || !tty;
60
- let client;
61
- if (!opts.offline && config.apiKey) {
62
- client = new KyaHttpClient({
63
- baseUrl: config.baseUrl,
64
- apiKey: config.apiKey,
65
- host: config.host,
66
- agentId: config.agentId,
67
- requireApiKey: true,
68
- });
69
- }
70
- const fetchPlane = client
71
- ? async () => client.request("/api/v1/kya/entitlement", { signal: AbortSignal.timeout(5000) })
72
- : undefined;
73
- let pane = opts.pane;
74
65
  let offline = opts.offline;
66
+ let client = makeClient(config, offline);
67
+ const policyCache = new PolicySampleCache();
68
+ let forcePolicyEval = false;
69
+ let autoRefresh = false;
70
+ let pending;
71
+ let busy = false;
72
+ let pane = opts.pane;
75
73
  let cursor = 0;
76
74
  let orrSummary;
77
75
  let last = {
@@ -79,48 +77,222 @@ export async function runDash(config, parsed, io, env) {
79
77
  approvals: [],
80
78
  sessions: [],
81
79
  };
82
- const paint = async () => {
80
+ const confirmLine = () => pending?.prompt;
81
+ const paint = async (optsPaint) => {
83
82
  const snap = await renderDash({ ...config, offline }, {
84
83
  once: true,
85
84
  offline,
86
85
  pane,
87
86
  client,
88
- fetchPlaneEntitlement: fetchPlane,
87
+ fetchPlaneEntitlement: client
88
+ ? async () => client.request("/api/v1/kya/entitlement", { signal: AbortSignal.timeout(5000) })
89
+ : undefined,
89
90
  cursor,
90
91
  orrSummary,
92
+ policyCache,
93
+ forcePolicyEval,
94
+ confirmLine: confirmLine(),
91
95
  }, env);
96
+ forcePolicyEval = false;
92
97
  last = {
93
98
  agents: [...snap.agents],
94
99
  approvals: [...snap.approvals],
95
100
  sessions: [...snap.sessions],
96
101
  };
102
+ // Never ANSI-clear while a confirm is armed (keeps the prompt visible).
103
+ if (!once && tty && optsPaint?.clear !== false && !pending) {
104
+ try {
105
+ stdoutFd.write("\x1b[2J\x1b[H");
106
+ }
107
+ catch {
108
+ /* ignore */
109
+ }
110
+ }
97
111
  io.log(snap.frame);
98
112
  return snap;
99
113
  };
100
114
  const selected = (rows) => rows.length === 0 ? undefined : rows[Math.min(cursor, rows.length - 1)];
101
115
  if (once) {
102
- await paint();
116
+ await paint({ clear: false });
103
117
  return 0;
104
118
  }
105
- await paint();
119
+ await paint({ clear: false });
106
120
  emitKeypressEvents(stdinFd);
107
121
  stdinFd.setRawMode?.(true);
108
122
  stdinFd.resume();
123
+ // Bracketed paste: ignore paste chunks so `ay`/`ky` cannot one-shot confirm.
124
+ try {
125
+ stdoutFd.write("\x1b[?2004h");
126
+ }
127
+ catch {
128
+ /* ignore */
129
+ }
109
130
  return await new Promise((resolve) => {
110
- let painting = false;
131
+ let refreshTimer;
132
+ let pasteBuf = "";
133
+ let inPaste = false;
134
+ const stopRefresh = () => {
135
+ if (refreshTimer) {
136
+ clearInterval(refreshTimer);
137
+ refreshTimer = undefined;
138
+ }
139
+ };
140
+ const startRefresh = () => {
141
+ stopRefresh();
142
+ if (!autoRefresh || offline || !client)
143
+ return;
144
+ refreshTimer = setInterval(() => {
145
+ if (busy || pending)
146
+ return;
147
+ busy = true;
148
+ void paint()
149
+ .catch((err) => {
150
+ if (err instanceof AuthRequiredError)
151
+ io.error(err.message);
152
+ else
153
+ io.error(err instanceof Error ? err.message : String(err));
154
+ })
155
+ .finally(() => {
156
+ busy = false;
157
+ });
158
+ }, AUTO_REFRESH_MS);
159
+ };
160
+ const syncClient = () => {
161
+ client = makeClient(config, offline);
162
+ if (offline)
163
+ policyCache.invalidate();
164
+ if (!offline && client && autoRefresh)
165
+ startRefresh();
166
+ else
167
+ stopRefresh();
168
+ };
169
+ const stillLive = () => !offline && Boolean(client);
170
+ const arm = (job) => {
171
+ pending = job;
172
+ void paint({ clear: false });
173
+ };
174
+ const runPaint = () => {
175
+ if (busy)
176
+ return;
177
+ busy = true;
178
+ void paint()
179
+ .catch((err) => {
180
+ if (err instanceof AuthRequiredError)
181
+ io.error(err.message);
182
+ else
183
+ io.error(err instanceof Error ? err.message : String(err));
184
+ })
185
+ .finally(() => {
186
+ busy = false;
187
+ });
188
+ };
111
189
  const onKey = (str, key) => {
190
+ const seq = key?.sequence ?? str ?? "";
191
+ // Bracketed paste begin/end
192
+ if (seq.includes("\x1b[200~") || str === "\x1b[200~") {
193
+ inPaste = true;
194
+ pasteBuf = "";
195
+ return;
196
+ }
197
+ if (inPaste) {
198
+ if (seq.includes("\x1b[201~") || str === "\x1b[201~") {
199
+ inPaste = false;
200
+ pasteBuf = "";
201
+ if (pending) {
202
+ pending = undefined;
203
+ io.log("paste ignored — confirm cancelled");
204
+ void paint({ clear: false });
205
+ }
206
+ }
207
+ else {
208
+ pasteBuf += str ?? "";
209
+ }
210
+ return;
211
+ }
112
212
  const raw = key?.ctrl && key.name === "c" ? "\u0003" : (str ?? key?.name ?? "");
113
213
  const action = mapKey(raw);
214
+ if (pending) {
215
+ if (action.type === "confirm-yes") {
216
+ const job = pending;
217
+ pending = undefined;
218
+ if (!stillLive()) {
219
+ io.log("Need a live plane (drop offline / set KYA_API_KEY).");
220
+ void paint({ clear: false });
221
+ return;
222
+ }
223
+ const liveClient = client;
224
+ const cfg = { ...config, offline: false };
225
+ busy = true;
226
+ void (async () => {
227
+ try {
228
+ if (!stillLive()) {
229
+ io.log("offline mid-flight — aborted");
230
+ return;
231
+ }
232
+ if (job.kind === "kill") {
233
+ const killed = await runKillAgent(cfg, job.id, liveClient);
234
+ if (!stillLive())
235
+ return;
236
+ io.log(`${killed.status ?? "PAUSED"} ${killed.id}`);
237
+ }
238
+ else if (job.kind === "shrink") {
239
+ const sh = await runShrinkSession(cfg, job.id, job.to, liveClient);
240
+ if (!stillLive())
241
+ return;
242
+ io.log(`clearance ${sh.from} → ${sh.to}`);
243
+ }
244
+ else if (job.kind === "decide") {
245
+ // Machine sk_* keys must not decide — JWT / kya.approve only.
246
+ if (isMachineApiKey(config.apiKey ?? "")) {
247
+ io.log(`Machine API keys cannot ${job.decision} from the TUI. Run: kya ${job.decision} --id ${job.id}`);
248
+ return;
249
+ }
250
+ const res = await liveClient.decideApproval(job.id, job.decision);
251
+ if (!stillLive())
252
+ return;
253
+ io.log(`${job.decision} → ${res.status} ${res.id}`);
254
+ }
255
+ else if (job.kind === "invoke") {
256
+ const result = await runInvoke(cfg, { toolId: job.toolId, irreversible: true }, liveClient);
257
+ if (!stillLive())
258
+ return;
259
+ io.log(`invoke ${result.verdict} dispatched=${result.dispatched} ${result.sideEffect} (does not run the write)`);
260
+ }
261
+ else if (job.kind === "wrap") {
262
+ const result = await runWrap(cfg, { toolId: "org.sample.data.write", irreversible: true }, liveClient);
263
+ if (!stillLive())
264
+ return;
265
+ io.log(`wrap exit ${wrapExitCode(result)} ${result.eval.response.verdict}`);
266
+ }
267
+ }
268
+ catch (err) {
269
+ if (err instanceof AuthRequiredError)
270
+ io.error(err.message);
271
+ else
272
+ io.error(err instanceof Error ? err.message : String(err));
273
+ }
274
+ finally {
275
+ busy = false;
276
+ await paint();
277
+ }
278
+ })();
279
+ return;
280
+ }
281
+ pending = undefined;
282
+ io.log("cancelled");
283
+ void paint({ clear: false });
284
+ return;
285
+ }
114
286
  if (action.type === "quit") {
115
287
  cleanup();
116
288
  resolve(0);
117
289
  return;
118
290
  }
119
291
  if (action.type === "help") {
120
- io.log("1-7 panes · j/k move · w wrap · k kill · i invoke · b/R shrink · o orr · a/x approve/reject (JWT) · O offline · q");
292
+ io.log("1-8 panes · j/k · e eval · p auto-refresh · w wrap · k kill · i invoke · b/R shrink · o orr · t passport · a/x decide (y confirm; sk_* refused) · O offline · q");
121
293
  return;
122
294
  }
123
- if (action.type === "noop" || painting)
295
+ if (action.type === "noop" || busy)
124
296
  return;
125
297
  if (action.type === "up") {
126
298
  cursor = Math.max(0, cursor - 1);
@@ -130,6 +302,22 @@ export async function runDash(config, parsed, io, env) {
130
302
  }
131
303
  else if (action.type === "offline") {
132
304
  offline = !offline;
305
+ syncClient();
306
+ io.log(offline ? "offline (sample PEP only)" : "live plane");
307
+ }
308
+ else if (action.type === "force-policy-eval") {
309
+ forcePolicyEval = true;
310
+ policyCache.invalidate();
311
+ pane = "policy";
312
+ }
313
+ else if (action.type === "toggle-auto-refresh") {
314
+ autoRefresh = !autoRefresh;
315
+ io.log(autoRefresh ? `auto-refresh ${AUTO_REFRESH_MS / 1000}s` : "auto-refresh off");
316
+ if (autoRefresh)
317
+ startRefresh();
318
+ else
319
+ stopRefresh();
320
+ return;
133
321
  }
134
322
  else if (action.type === "pane" && action.pane) {
135
323
  pane = action.pane;
@@ -139,62 +327,108 @@ export async function runDash(config, parsed, io, env) {
139
327
  io.log("Register: kya register-agent --name solo --version-hash dev-local");
140
328
  return;
141
329
  }
142
- else if (!opts.offline && client) {
143
- const cfg = { ...config, offline: false };
144
- void (async () => {
330
+ else if (action.type === "refresh") {
331
+ // fall through
332
+ }
333
+ else if (!offline && client) {
334
+ if (action.type === "wrap") {
335
+ arm({ kind: "wrap", prompt: "wrap sample org.sample.data.write (ticket only)?" });
336
+ return;
337
+ }
338
+ if (action.type === "kill") {
339
+ const row = selected(last.agents);
340
+ if (!row)
341
+ return;
342
+ arm({ kind: "kill", id: row.id, prompt: `kill agent ${row.id}?` });
343
+ return;
344
+ }
345
+ if (action.type === "invoke") {
346
+ const row = selected(last.approvals);
347
+ if (!row)
348
+ return;
349
+ const toolId = String(row.action ?? "org.sample.data.write");
350
+ arm({
351
+ kind: "invoke",
352
+ id: row.id,
353
+ toolId,
354
+ prompt: `invoke ${row.id} tool=${toolId}? (does not run the write)`,
355
+ });
356
+ return;
357
+ }
358
+ if (action.type === "shrink-build" || action.type === "shrink-read") {
359
+ const row = selected(last.sessions);
360
+ if (!row)
361
+ return;
362
+ const to = action.type === "shrink-build" ? "BUILD" : "READ";
363
+ arm({ kind: "shrink", id: row.id, to, prompt: `shrink ${row.id} → ${to}?` });
364
+ return;
365
+ }
366
+ if (action.type === "orr-run") {
367
+ busy = true;
145
368
  try {
146
- if (action.type === "wrap") {
147
- const result = await runWrap(cfg, { toolId: "org.sample.data.write", irreversible: true }, client);
148
- io.log(`wrap exit ${wrapExitCode(result)} ${result.eval.response.verdict}`);
149
- }
150
- else if (action.type === "kill") {
151
- const row = selected(last.agents);
152
- if (!row)
153
- return;
154
- const killed = await runKillAgent(cfg, row.id, client);
155
- io.log(`${killed.status ?? "PAUSED"} ${killed.id}`);
156
- }
157
- else if (action.type === "invoke") {
158
- const row = selected(last.approvals);
159
- if (!row)
160
- return;
161
- const result = await runInvoke(cfg, { toolId: String(row.action ?? "org.sample.data.write"), irreversible: true }, client);
162
- io.log(`invoke ${result.verdict} dispatched=${result.dispatched} ${result.sideEffect}`);
163
- }
164
- else if (action.type === "shrink-build" || action.type === "shrink-read") {
165
- const row = selected(last.sessions);
166
- if (!row)
369
+ const result = deskOrr(config.cwd);
370
+ orrSummary = {
371
+ overall: result.report.overall,
372
+ disposition: result.report.disposition,
373
+ path: result.reportJsonPath ?? result.reportMdPath,
374
+ };
375
+ io.log("orr producer=sa.first_party (Scorecard: use CLI --producer openssf.scorecard)");
376
+ }
377
+ finally {
378
+ busy = false;
379
+ }
380
+ }
381
+ else if (action.type === "passport") {
382
+ const row = selected(last.agents);
383
+ if (!row) {
384
+ io.log("Select an agent (pane 3) then press t");
385
+ return;
386
+ }
387
+ busy = true;
388
+ void (async () => {
389
+ try {
390
+ if (!stillLive())
167
391
  return;
168
- const to = action.type === "shrink-build" ? "BUILD" : "READ";
169
- const sh = await runShrinkSession(cfg, row.id, to, client);
170
- io.log(`clearance ${sh.from} → ${sh.to}`);
392
+ const passport = await client.getPassport(row.id);
393
+ const dump = JSON.stringify(passport, null, 2);
394
+ assertNoSecrets("", dump);
395
+ io.log(dump);
171
396
  }
172
- else if (action.type === "orr-run") {
173
- const result = deskOrr(config.cwd);
174
- orrSummary = {
175
- overall: result.report.overall,
176
- disposition: result.report.disposition,
177
- path: result.reportJsonPath ?? result.reportMdPath,
178
- };
397
+ catch (err) {
398
+ if (err instanceof AuthRequiredError)
399
+ io.error(err.message);
400
+ else
401
+ io.error(err instanceof Error ? err.message : String(err));
179
402
  }
180
- else if (action.type === "decide-approve" || action.type === "decide-reject") {
181
- const row = selected(last.approvals);
182
- if (!row)
183
- return;
184
- const verb = action.type === "decide-approve" ? "approve" : "reject";
185
- io.log(`kya ${verb} --id ${row.id} (TUI does not decide)`);
403
+ finally {
404
+ busy = false;
405
+ await paint();
186
406
  }
407
+ })();
408
+ return;
409
+ }
410
+ else if (action.type === "decide-approve" || action.type === "decide-reject") {
411
+ const row = selected(last.approvals);
412
+ if (!row)
413
+ return;
414
+ const decision = action.type === "decide-approve" ? "approve" : "reject";
415
+ if (isMachineApiKey(config.apiKey ?? "")) {
416
+ io.log(`Machine API keys cannot ${decision} from the TUI. Run: kya ${decision} --id ${row.id}`);
417
+ return;
187
418
  }
188
- catch (err) {
189
- if (err instanceof AuthRequiredError)
190
- io.error(err.message);
191
- else
192
- io.error(err instanceof Error ? err.message : String(err));
193
- }
194
- finally {
195
- await paint();
196
- }
197
- })();
419
+ arm({
420
+ kind: "decide",
421
+ id: row.id,
422
+ decision,
423
+ prompt: `${decision} ${row.id}? (needs kya.approve JWT scope)`,
424
+ });
425
+ return;
426
+ }
427
+ else {
428
+ runPaint();
429
+ return;
430
+ }
431
+ runPaint();
198
432
  return;
199
433
  }
200
434
  else if (action.type === "orr-run") {
@@ -204,20 +438,29 @@ export async function runDash(config, parsed, io, env) {
204
438
  disposition: result.report.disposition,
205
439
  path: result.reportJsonPath ?? result.reportMdPath,
206
440
  };
441
+ io.log("orr producer=sa.first_party");
207
442
  }
208
- painting = true;
209
- void paint()
210
- .catch((err) => {
211
- if (err instanceof AuthRequiredError)
212
- io.error(err.message);
213
- else
214
- io.error(err instanceof Error ? err.message : String(err));
215
- })
216
- .finally(() => {
217
- painting = false;
218
- });
443
+ else if (action.type === "wrap" ||
444
+ action.type === "kill" ||
445
+ action.type === "invoke" ||
446
+ action.type === "shrink-build" ||
447
+ action.type === "shrink-read" ||
448
+ action.type === "decide-approve" ||
449
+ action.type === "decide-reject" ||
450
+ action.type === "passport") {
451
+ io.log("Need a live plane (O toggles offline; set KYA_API_KEY).");
452
+ return;
453
+ }
454
+ runPaint();
219
455
  };
220
456
  const cleanup = () => {
457
+ stopRefresh();
458
+ try {
459
+ stdoutFd.write("\x1b[?2004l");
460
+ }
461
+ catch {
462
+ /* ignore */
463
+ }
221
464
  stdinFd.setRawMode?.(false);
222
465
  stdinFd.off("keypress", onKey);
223
466
  stdinFd.pause();
@@ -225,4 +468,15 @@ export async function runDash(config, parsed, io, env) {
225
468
  stdinFd.on("keypress", onKey);
226
469
  });
227
470
  }
471
+ function makeClient(config, offline) {
472
+ if (offline || !config.apiKey)
473
+ return undefined;
474
+ return new KyaHttpClient({
475
+ baseUrl: config.baseUrl,
476
+ apiKey: config.apiKey,
477
+ host: config.host,
478
+ agentId: config.agentId,
479
+ requireApiKey: true,
480
+ });
481
+ }
228
482
  //# sourceMappingURL=dash.js.map