@mafdet/cli 0.4.4 → 0.4.5

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 (43) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +4 -6
  3. package/README.zh-CN.md +4 -3
  4. package/dist/agent/command-policy.d.ts.map +1 -1
  5. package/dist/agent/command-policy.js +10 -1
  6. package/dist/agent/context-budget.d.ts +15 -0
  7. package/dist/agent/context-budget.d.ts.map +1 -1
  8. package/dist/agent/context-budget.js +46 -6
  9. package/dist/agent/loop.d.ts +9 -1
  10. package/dist/agent/loop.d.ts.map +1 -1
  11. package/dist/agent/loop.js +21 -2
  12. package/dist/agent/session.d.ts +2 -0
  13. package/dist/agent/session.d.ts.map +1 -1
  14. package/dist/agent/session.js +7 -2
  15. package/dist/api/sse-parser.d.ts +15 -0
  16. package/dist/api/sse-parser.d.ts.map +1 -1
  17. package/dist/api/sse-parser.js +28 -1
  18. package/dist/commands/agent.d.ts.map +1 -1
  19. package/dist/commands/agent.js +75 -26
  20. package/dist/commands/sessions.d.ts.map +1 -1
  21. package/dist/commands/sessions.js +34 -8
  22. package/dist/sessions/index-file.d.ts +2 -0
  23. package/dist/sessions/index-file.d.ts.map +1 -1
  24. package/dist/sessions/index-file.js +3 -0
  25. package/dist/sessions/open.d.ts.map +1 -1
  26. package/dist/sessions/open.js +3 -1
  27. package/dist/sessions/resume.d.ts +1 -0
  28. package/dist/sessions/resume.d.ts.map +1 -1
  29. package/dist/sessions/resume.js +5 -3
  30. package/dist/tui/agent-surface.d.ts.map +1 -1
  31. package/dist/tui/agent-surface.js +10 -1
  32. package/dist/tui/app.d.ts +14 -0
  33. package/dist/tui/app.d.ts.map +1 -1
  34. package/dist/tui/app.js +45 -11
  35. package/dist/tui/picker.d.ts.map +1 -1
  36. package/dist/tui/picker.js +2 -1
  37. package/dist/tui/session-centre.d.ts +5 -0
  38. package/dist/tui/session-centre.d.ts.map +1 -1
  39. package/dist/tui/session-centre.js +9 -3
  40. package/dist/tui/start-agent.d.ts +28 -0
  41. package/dist/tui/start-agent.d.ts.map +1 -1
  42. package/dist/tui/start-agent.js +39 -9
  43. package/package.json +1 -1
@@ -63,7 +63,13 @@ async function run(argv, io) {
63
63
  return ExitCode.Usage;
64
64
  }
65
65
  function list(argv, io, root) {
66
- const { format } = parseFormatFlags(argv);
66
+ const { format, rest } = parseFormatFlags(argv);
67
+ // R-7 · `list` was the one subcommand that threw its leftovers away, so
68
+ // `sessions list --dry-run` exited 0 and listed everything.
69
+ if (rest.length > 0) {
70
+ io.stderr(`mafdet sessions list: unexpected argument '${rest[0]}'\n${USAGE}`);
71
+ return ExitCode.Usage;
72
+ }
67
73
  const { sessions } = loadIndex(root);
68
74
  if (format === "json") {
69
75
  io.stdout(renderJson({
@@ -89,7 +95,12 @@ function list(argv, io, root) {
89
95
  { header: "REQUESTS", value: (s) => `${s.model_requests}` },
90
96
  {
91
97
  header: "EST. COST",
92
- value: (s) => formatUsd(s.estimated_charge_micro_usd),
98
+ // R-6 · An unpriced session is not a free one. `$0.00` in the main
99
+ // column is the claim that matters; a note on stderr cannot correct
100
+ // a number the user has already read.
101
+ value: (s) => s.unpriced_requests
102
+ ? "unknown"
103
+ : formatUsd(s.estimated_charge_micro_usd),
93
104
  },
94
105
  { header: "UPDATED", value: (s) => s.updated_at },
95
106
  ]));
@@ -135,12 +146,20 @@ function show(argv, io, root) {
135
146
  ["model", safe(m.model_id, io)],
136
147
  ["workspace", safe(m.workspace, io)],
137
148
  ["mode", safe(m.mode, io)],
138
- ["created", m.created_at],
139
- ["updated", m.updated_at],
149
+ // R-4 · Timestamps come off disk like everything else here; a file that
150
+ // has been edited can carry a sequence in any field, not only the
151
+ // free-text ones.
152
+ ["created", safe(m.created_at, io)],
153
+ ["updated", safe(m.updated_at, io)],
140
154
  ["messages", `${m.message_count}`],
141
155
  ["model requests", `${m.model_requests}`],
142
156
  ["tokens", `${m.prompt_tokens} in / ${m.completion_tokens} out`],
143
- ["estimated cost", formatUsd(m.estimated_charge_micro_usd)],
157
+ [
158
+ "estimated cost",
159
+ m.unpriced_requests
160
+ ? `${formatUsd(m.estimated_charge_micro_usd)} + ${m.unpriced_requests} unpriced`
161
+ : formatUsd(m.estimated_charge_micro_usd),
162
+ ],
144
163
  ]));
145
164
  if (session.truncatedTail) {
146
165
  // Worth saying plainly: the user should know a crash cost them the tail.
@@ -322,6 +341,8 @@ async function usage(argv, io, root) {
322
341
  completion_tokens: totals.completionTokens,
323
342
  // Strings, so no parser turns a charge into a float.
324
343
  estimated_charge_micro_usd: totals.estimatedMicroUsd.toString(),
344
+ // R-6 · Without this a consumer reads the total above as complete.
345
+ unpriced_requests: totals.unpricedRequests,
325
346
  settled_charge_micro_usd: totals.settledMicroUsd.toString(),
326
347
  by_settlement: totals.bySettlement,
327
348
  by_model: totals.byModel.map((m) => ({
@@ -348,7 +369,12 @@ async function usage(argv, io, root) {
348
369
  `${totals.toolRuns}${totals.toolRunsRefused > 0 ? ` (${totals.toolRunsRefused} refused)` : ""}`,
349
370
  ],
350
371
  ["tokens", `${totals.promptTokens} in / ${totals.completionTokens} out`],
351
- ["local estimate", formatUsd(totals.estimatedMicroUsd.toString())],
372
+ [
373
+ "local estimate",
374
+ totals.unpricedRequests > 0
375
+ ? `${formatUsd(totals.estimatedMicroUsd.toString())} + ${totals.unpricedRequests} unpriced`
376
+ : formatUsd(totals.estimatedMicroUsd.toString()),
377
+ ],
352
378
  ];
353
379
  if (totals.bySettlement.settled > 0) {
354
380
  rows.push([
@@ -366,7 +392,7 @@ async function usage(argv, io, root) {
366
392
  if (totals.byModel.length > 1) {
367
393
  io.stdout("\nBy model\n");
368
394
  io.stdout(renderTable(totals.byModel, [
369
- { header: "MODEL", value: (m) => m.modelId },
395
+ { header: "MODEL", value: (m) => safe(m.modelId, io) },
370
396
  { header: "REQUESTS", value: (m) => `${m.modelRequests}` },
371
397
  {
372
398
  header: "ESTIMATE",
@@ -378,7 +404,7 @@ async function usage(argv, io, root) {
378
404
  // Split per key so two accounts' spending is never presented as one.
379
405
  io.stdout("\nBy API key\n");
380
406
  io.stdout(renderTable(totals.byKey, [
381
- { header: "KEY", value: (k) => k.apiKeyRef },
407
+ { header: "KEY", value: (k) => safe(k.apiKeyRef, io) },
382
408
  { header: "REQUESTS", value: (k) => `${k.modelRequests}` },
383
409
  {
384
410
  header: "ESTIMATE",
@@ -7,6 +7,8 @@ export interface IndexEntry {
7
7
  readonly workspace: string;
8
8
  readonly model_requests: number;
9
9
  readonly estimated_charge_micro_usd: string;
10
+ /** R-6 · Requests the estimate above does not include; see F-012. */
11
+ readonly unpriced_requests?: number;
10
12
  }
11
13
  export interface SessionIndex {
12
14
  readonly schema_version: number;
@@ -1 +1 @@
1
- {"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../../src/sessions/index-file.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAmBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAoBvD;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAiBlE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAapD;AAwBD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,eAAe,GACxB,YAAY,CAUd;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,YAAY,CAed;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBnD"}
1
+ {"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../../src/sessions/index-file.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAsBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAoBvD;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAiBlE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAapD;AAwBD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,eAAe,GACxB,YAAY,CAUd;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,YAAY,CAed;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBnD"}
@@ -25,6 +25,9 @@ function entryOf(metadata) {
25
25
  workspace: metadata.workspace,
26
26
  model_requests: metadata.model_requests,
27
27
  estimated_charge_micro_usd: metadata.estimated_charge_micro_usd,
28
+ ...(metadata.unpriced_requests
29
+ ? { unpriced_requests: metadata.unpriced_requests }
30
+ : {}),
28
31
  };
29
32
  }
30
33
  /** Newest first, which is the order a session list wants. */
@@ -1 +1 @@
1
- {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,oBAAoB,EAGpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,aAAa,EAAe,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;CAC/D,CAAC;AAEN,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAqFnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAC9C,MAAM,CAwBR"}
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,oBAAoB,EAGpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,aAAa,EAAe,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;CAC/D,CAAC;AAEN,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAqFnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAC9C,MAAM,CAyBR"}
@@ -6,6 +6,7 @@
6
6
  // and the checks are all re-derived from the key in use *now*, never from
7
7
  // what the session was recorded with.
8
8
  import { existsSync } from "node:fs";
9
+ import { stripAnsi } from "../agent/command-policy.js";
9
10
  import { acquireSessionLock, SessionLockedError, } from "./lock.js";
10
11
  import { describeBlocker, permissionsForResume, rebuildContext, resumeBlockers, } from "./resume.js";
11
12
  import { InvalidSessionIdError, resolveSessionDirectory, } from "./session-id.js";
@@ -102,8 +103,9 @@ export function openForResume(input) {
102
103
  * know that will be surprised by the bill rather than by the feature.
103
104
  */
104
105
  export function describeResume(result) {
106
+ // R-4 · Title and model come off disk and this is drawn on a terminal.
105
107
  const lines = [
106
- `resuming "${result.metadata.title}" (${result.metadata.model_id})`,
108
+ `resuming "${stripAnsi(result.metadata.title)}" (${stripAnsi(result.metadata.model_id)})`,
107
109
  ];
108
110
  if (result.toolRunsSummarised > 0) {
109
111
  lines.push(` ${result.toolRunsSummarised} earlier tool run(s) are summarised, not repeated`);
@@ -50,6 +50,7 @@ export interface ResumeCheckInput {
50
50
  * with a key that could call a model says nothing about whether this key can.
51
51
  */
52
52
  export declare function resumeBlockers(input: ResumeCheckInput): readonly ResumeBlocker[];
53
+ /** R-4 · The values quoted here are recorded ones, and this is shown on a terminal. */
53
54
  export declare function describeBlocker(blocker: ResumeBlocker): string;
54
55
  /**
55
56
  * What a resumed run starts with, stated so the caller cannot forget.
@@ -1 +1 @@
1
- {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/sessions/resume.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CAwClE;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GACtB,SAAS,aAAa,EAAE,CAa1B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAS9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,kBAAkB,CAQpB"}
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/sessions/resume.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CAwClE;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GACtB,SAAS,aAAa,EAAE,CAa1B;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAS9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,kBAAkB,CAQpB"}
@@ -9,6 +9,7 @@
9
9
  // what was said. A replayed `tool_calls` block would invite the model to
10
10
  // continue a plan the user approved yesterday, in a workspace that has since
11
11
  // changed, with permissions this run may not have.
12
+ import { stripAnsi } from "../agent/command-policy.js";
12
13
  const DEFAULT_MAX_MESSAGES = 40;
13
14
  /**
14
15
  * Rebuild the messages to send, from what was stored.
@@ -79,14 +80,15 @@ export function resumeBlockers(input) {
79
80
  }
80
81
  return blockers;
81
82
  }
83
+ /** R-4 · The values quoted here are recorded ones, and this is shown on a terminal. */
82
84
  export function describeBlocker(blocker) {
83
85
  switch (blocker.kind) {
84
86
  case "model-unavailable":
85
- return `${blocker.modelId} is not callable with the key you are using now - choose another model to continue`;
87
+ return `${stripAnsi(blocker.modelId)} is not callable with the key you are using now - choose another model to continue`;
86
88
  case "model-not-agent-capable":
87
- return `${blocker.modelId} has no verified tool calling now, so the agent cannot drive it - choose another model`;
89
+ return `${stripAnsi(blocker.modelId)} has no verified tool calling now, so the agent cannot drive it - choose another model`;
88
90
  case "workspace-missing":
89
- return `${blocker.workspace} no longer exists - you can read this conversation, or continue it in another directory`;
91
+ return `${stripAnsi(blocker.workspace)} no longer exists - you can read this conversation, or continue it in another directory`;
90
92
  }
91
93
  }
92
94
  export function permissionsForResume(declaredThisRun) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-surface.d.ts","sourceRoot":"","sources":["../../src/tui/agent-surface.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,IAAI,CACpB,cAAc,EACd,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAC1D,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;IACnD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,UAAU,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA+B7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CA4EzB;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAgBR"}
1
+ {"version":3,"file":"agent-surface.d.ts","sourceRoot":"","sources":["../../src/tui/agent-surface.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,IAAI,CACpB,cAAc,EACd,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAC1D,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;IACnD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,UAAU,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA+B7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAoFzB;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAgBR"}
@@ -72,8 +72,17 @@ export async function runAgentSurface(options) {
72
72
  // Said out loud: from here the model can no longer see those results,
73
73
  // which is the sort of thing that otherwise looks like it forgot.
74
74
  // The saved transcript still has them - only what we send got shorter.
75
+ // R-2 · Say what was actually dropped. A conversation with no tools in
76
+ // it drops turns and zero exchanges, and this used to report "0 earlier
77
+ // tool exchanges" - a true statement that described nothing.
78
+ const parts = [];
79
+ if (event.exchanges > 0) {
80
+ parts.push(`${event.exchanges} earlier tool exchange(s)`);
81
+ }
82
+ if (event.turns > 0)
83
+ parts.push(`${event.turns} earlier turn(s)`);
75
84
  options.stdout.write(dim(` context near the limit (${event.promptTokens} tokens) - dropped ` +
76
- `${event.exchanges} earlier tool exchange(s) from what is sent\n`));
85
+ `${parts.join(" and ") || "nothing"} from what is sent\n`));
77
86
  }
78
87
  };
79
88
  const sessionOptions = {
package/dist/tui/app.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { ExitCode } from "../output/exit-codes.js";
2
2
  import type { CommandIo } from "../commands/runtime.js";
3
+ import { type TerminalCapabilities } from "./theme.js";
3
4
  import { type GuardHost } from "./terminal.js";
5
+ import { type WidgetIo } from "./widgets.js";
4
6
  export interface TuiOptions {
5
7
  readonly version: string;
6
8
  readonly workspace: string;
@@ -22,4 +24,16 @@ export interface TuiOptions {
22
24
  readonly argv: readonly string[];
23
25
  }
24
26
  export declare function runTui(options: TuiOptions): Promise<ExitCode>;
27
+ interface ChooseOptions {
28
+ readonly io: WidgetIo;
29
+ readonly commandIo: CommandIo;
30
+ readonly apiKey: string;
31
+ readonly capabilities: TerminalCapabilities;
32
+ readonly stdout: {
33
+ write(text: string): unknown;
34
+ };
35
+ }
36
+ /** The model the whole precedence chain settles on: flag > env > project > user. */
37
+ export declare function resolvedDefaultModel(options: ChooseOptions): string | undefined;
38
+ export {};
25
39
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAcxD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAiBrE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgKnE"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAYxD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAerE,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgLnE;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACnD;AAsBD,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,GACrB,MAAM,GAAG,SAAS,CAYpB"}
package/dist/tui/app.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import { existsSync } from "node:fs";
8
8
  import { ExitCode } from "../output/exit-codes.js";
9
9
  import { loadConfig } from "../config/load.js";
10
- import { loadCatalogue, saveDefaultModel } from "./adapters/models.js";
10
+ import { loadCatalogue, saveDefaultModel, } from "./adapters/models.js";
11
11
  import { renderBanner } from "./banner.js";
12
12
  import { keyboard } from "./input.js";
13
13
  import { FORCED_EXIT_NOTICE, InterruptTracker } from "./interrupt.js";
@@ -110,18 +110,21 @@ export async function runTui(options) {
110
110
  options.stdout.write("commands that work without an interactive session.\n");
111
111
  return ExitCode.Cancelled;
112
112
  }
113
- const chosen = await chooseModel({
113
+ const chooseOptions = {
114
114
  io,
115
115
  commandIo: options.commandIo,
116
116
  apiKey: auth.apiKey,
117
117
  capabilities,
118
118
  stdout: options.stdout,
119
- });
120
- if (chosen === undefined)
119
+ };
120
+ const catalogue = await loadModels(chooseOptions);
121
+ if (catalogue === undefined)
121
122
  return ExitCode.Cancelled;
122
123
  options.stdout.write(renderWorkspaceSummary(inspectWorkspace(options.workspace, existsSync), capabilities));
123
- // Offered only when there is something to resume, and only when the user
124
- // did not already say what they wanted on the command line.
124
+ // R-9 · The session comes first. Resuming runs on the model the session
125
+ // recorded, so asking for one before knowing whether we are resuming made
126
+ // the user choose - and possibly save as their default - a model the run
127
+ // then ignored, after printing it in the banner as though it were in use.
125
128
  const sessionOptions = parseSessionOptions(options.argv);
126
129
  let resumeId = sessionOptions.resumeId;
127
130
  if (resumeId === undefined && !sessionOptions.noHistory) {
@@ -131,6 +134,17 @@ export async function runTui(options) {
131
134
  if (choice.kind === "resume")
132
135
  resumeId = choice.sessionId;
133
136
  }
137
+ // Only a new conversation needs a model chosen for it.
138
+ const chosen = resumeId === undefined
139
+ ? await chooseModel(chooseOptions, catalogue)
140
+ : {
141
+ model: catalogue.agentReady[0] ?? catalogue.chat[0],
142
+ catalogue: catalogue.chat,
143
+ agentCapable: catalogue.agentReady.map((m) => m.modelId),
144
+ };
145
+ if (chosen === undefined || chosen.model === undefined) {
146
+ return ExitCode.Cancelled;
147
+ }
134
148
  return await startAgent({
135
149
  io,
136
150
  capabilities,
@@ -160,7 +174,7 @@ export async function runTui(options) {
160
174
  }
161
175
  }
162
176
  /** The model the whole precedence chain settles on: flag > env > project > user. */
163
- function resolvedDefaultModel(options) {
177
+ export function resolvedDefaultModel(options) {
164
178
  try {
165
179
  return loadConfig({
166
180
  env: options.commandIo.env,
@@ -174,7 +188,15 @@ function resolvedDefaultModel(options) {
174
188
  return options.commandIo.env?.MAFDET_MODEL;
175
189
  }
176
190
  }
177
- async function chooseModel(options) {
191
+ /**
192
+ * R-9 · Load and vet the catalogue, without choosing anything yet.
193
+ *
194
+ * Split from the picker because resuming does not need a choice: the session
195
+ * runs on the model it recorded. Asking for one first made the user pick - and
196
+ * possibly "remember" - a model that the run then ignored, and printed it in
197
+ * the banner as though it were in use.
198
+ */
199
+ async function loadModels(options) {
178
200
  const { dim, bold } = palette(options.capabilities);
179
201
  options.stdout.write(dim(" loading the model catalogue...\n"));
180
202
  let catalogue;
@@ -202,13 +224,25 @@ async function chooseModel(options) {
202
224
  return undefined;
203
225
  }
204
226
  if (catalogue.agentReady.length === 0) {
227
+ // R-1 · Refuse, rather than warn and open the gate wider.
228
+ //
229
+ // This printed a notice and then offered *every* chat model, because
230
+ // `agentOnly` was `agentReady.length > 0` - so the one case with nothing
231
+ // safe to offer was the case that offered everything. The whole of this
232
+ // surface is an agent session, and `mafdet agent` already refuses an
233
+ // unverified model outright; the two must not disagree about admission.
205
234
  options.stdout.write(noAgentModelsNotice(catalogue.chat.length, options.capabilities));
235
+ return undefined;
206
236
  }
237
+ return catalogue;
238
+ }
239
+ async function chooseModel(options, catalogue) {
240
+ const { dim } = palette(options.capabilities);
207
241
  const picked = await pickModel(options.io, {
208
242
  models: catalogue.chat,
209
- // Fail closed: offer only agent-capable models when any exist, so a
210
- // model the agent cannot drive never becomes the session's default.
211
- agentOnly: catalogue.agentReady.length > 0,
243
+ // Fail closed: only agent-capable models are ever offered here. The
244
+ // empty case is refused in loadModels, so this is never a way in.
245
+ agentOnly: true,
212
246
  // F-005 · The resolved model, not just the environment variable. After
213
247
  // "Remember X as your default?" wrote X to the user config, the next
214
248
  // launch still highlighted whatever MAFDET_MODEL said - usually nothing -
@@ -1 +1 @@
1
- {"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/tui/picker.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAW7C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,QAAQ,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,UAAU,CAAC,CAiHrB;AA6BD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAYR;AAED,iEAAiE;AACjE,YAAY,EAAE,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/tui/picker.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAW7C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,QAAQ,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,UAAU,CAAC,CAiHrB;AA6BD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAaR;AAED,iEAAiE;AACjE,YAAY,EAAE,GAAG,EAAE,CAAC"}
@@ -138,7 +138,8 @@ export function noAgentModelsNotice(chatCount, capabilities) {
138
138
  return ([
139
139
  "",
140
140
  bold(" No model on this key has verified tool calling."),
141
- dim(` ${chatCount} chat model(s) are available, so chat still works.`),
141
+ dim(" The agent needs one, so there is no session to open."),
142
+ dim(` ${chatCount} chat model(s) still work: try 'mafdet chat'.`),
142
143
  dim(" Run 'mafdet models list --agent' to see which models qualify,"),
143
144
  dim(" or check the key's model permissions in the console."),
144
145
  "",
@@ -10,6 +10,11 @@ export type SessionChoice = {
10
10
  readonly kind: "cancelled";
11
11
  };
12
12
  export declare function recentSessions(root: string): readonly IndexEntry[];
13
+ /**
14
+ * R-4 · Every string here came off disk, and this list is drawn on a terminal
15
+ * by definition. A title carrying an OSC sequence would retitle the window of
16
+ * anyone who merely opened the picker - before choosing anything.
17
+ */
13
18
  export declare function describeSession(entry: IndexEntry, capabilities: TerminalCapabilities): string;
14
19
  export declare function chooseSession(io: WidgetIo, root: string): Promise<SessionChoice>;
15
20
  //# sourceMappingURL=session-centre.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-centre.d.ts","sourceRoot":"","sources":["../../src/tui/session-centre.ts"],"names":[],"mappings":"AAOA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKrD,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAEnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,CAQlE;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAQR;AAOD,wBAAsB,aAAa,CACjC,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAoBxB"}
1
+ {"version":3,"file":"session-centre.d.ts","sourceRoot":"","sources":["../../src/tui/session-centre.ts"],"names":[],"mappings":"AAQA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKrD,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAEnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,CAQlE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAQR;AAOD,wBAAsB,aAAa,CACjC,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAoBxB"}
@@ -3,6 +3,7 @@
3
3
  // Offered only when there is something to resume: a menu whose only entry is
4
4
  // "New conversation" is a keystroke charged for nothing. Everything here reads
5
5
  // the local index and sends no request, so browsing costs nothing (plan 20.2).
6
+ import { stripAnsi } from "../agent/command-policy.js";
6
7
  import { formatUsd } from "../output/money.js";
7
8
  import { loadIndex } from "../sessions/index-file.js";
8
9
  import { palette } from "./theme.js";
@@ -19,10 +20,15 @@ export function recentSessions(root) {
19
20
  return [];
20
21
  }
21
22
  }
23
+ /**
24
+ * R-4 · Every string here came off disk, and this list is drawn on a terminal
25
+ * by definition. A title carrying an OSC sequence would retitle the window of
26
+ * anyone who merely opened the picker - before choosing anything.
27
+ */
22
28
  export function describeSession(entry, capabilities) {
23
29
  const parts = [
24
- entry.model_id,
25
- workspaceName(entry.workspace),
30
+ stripAnsi(entry.model_id),
31
+ stripAnsi(workspaceName(entry.workspace)),
26
32
  `${entry.model_requests} request(s)`,
27
33
  `~${formatUsd(entry.estimated_charge_micro_usd)}`,
28
34
  ];
@@ -42,7 +48,7 @@ export async function chooseSession(io, root) {
42
48
  { value: "", label: "New conversation" },
43
49
  ...recent.map((entry) => ({
44
50
  value: entry.session_id,
45
- label: entry.title,
51
+ label: stripAnsi(entry.title),
46
52
  detail: stripStyles(describeSession(entry, io.capabilities)),
47
53
  })),
48
54
  ],
@@ -73,5 +73,33 @@ export interface StartAgentOptions {
73
73
  */
74
74
  readonly getSignal?: () => AbortSignal | undefined;
75
75
  }
76
+ export interface RunContextInput {
77
+ /** The metadata of a resumed session, or undefined for a new one. */
78
+ readonly resumed?: {
79
+ readonly workspace: string;
80
+ readonly model_id: string;
81
+ };
82
+ readonly currentWorkspace: string;
83
+ readonly currentModel: ResolvedModel;
84
+ readonly catalogue: readonly ResolvedModel[];
85
+ }
86
+ /**
87
+ * F-003 · Where a run happens and which model it uses.
88
+ *
89
+ * A resumed session runs where it was recorded, on the model it was recorded
90
+ * with. It used to run in the current directory on the model picked moments
91
+ * earlier - so continuing project A's conversation from project B pointed the
92
+ * agent at B's files, and with `--allow-write` that meant editing the wrong
93
+ * repository.
94
+ *
95
+ * Falls back to the current model only when the catalogue has no entry for the
96
+ * recorded one. That cannot happen on a run that got this far - an uncallable
97
+ * model is a blocker and blockers now stop the run - but the fallback keeps
98
+ * this total rather than throwing on a case the type system allows.
99
+ */
100
+ export declare function effectiveRunContext(input: RunContextInput): {
101
+ workspace: string;
102
+ model: ResolvedModel;
103
+ };
76
104
  export declare function startAgent(options: StartAgentOptions): Promise<ExitCode>;
77
105
  //# sourceMappingURL=start-agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"start-agent.d.ts","sourceRoot":"","sources":["../../src/tui/start-agent.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAenD,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;GAKG;AACH,kEAAkE;AAClE,2DAA2D;AAC3D,eAAO,MAAM,SAAS,iHAQZ,CAAC;AASX,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAgB/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAiB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAgB1E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;CACpD;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,CA8JnB"}
1
+ {"version":3,"file":"start-agent.d.ts","sourceRoot":"","sources":["../../src/tui/start-agent.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAgBnD,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;GAKG;AACH,kEAAkE;AAClE,2DAA2D;AAC3D,eAAO,MAAM,SAAS,iHAQZ,CAAC;AASX,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAgB/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAiB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAgB1E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,CAAC;CACtB,CAUA;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,CAgKnB"}
@@ -15,6 +15,7 @@ import { keyDigest } from "../security/redact.js";
15
15
  import { SessionRecorder } from "../sessions/recorder.js";
16
16
  import { SessionWriter } from "../sessions/store.js";
17
17
  import { NOTICE_FILE, decideRecording, firstSaveNotice, } from "../sessions/policy.js";
18
+ import { stripAnsi } from "../agent/command-policy.js";
18
19
  import { describeResume, openForResume } from "../sessions/open.js";
19
20
  import { describeBlocker } from "../sessions/resume.js";
20
21
  import { runAgentSurface } from "./agent-surface.js";
@@ -118,6 +119,30 @@ export function parsePermissions(argv) {
118
119
  }
119
120
  return { allowWrite, allowedCommands };
120
121
  }
122
+ /**
123
+ * F-003 · Where a run happens and which model it uses.
124
+ *
125
+ * A resumed session runs where it was recorded, on the model it was recorded
126
+ * with. It used to run in the current directory on the model picked moments
127
+ * earlier - so continuing project A's conversation from project B pointed the
128
+ * agent at B's files, and with `--allow-write` that meant editing the wrong
129
+ * repository.
130
+ *
131
+ * Falls back to the current model only when the catalogue has no entry for the
132
+ * recorded one. That cannot happen on a run that got this far - an uncallable
133
+ * model is a blocker and blockers now stop the run - but the fallback keeps
134
+ * this total rather than throwing on a case the type system allows.
135
+ */
136
+ export function effectiveRunContext(input) {
137
+ if (input.resumed === undefined) {
138
+ return { workspace: input.currentWorkspace, model: input.currentModel };
139
+ }
140
+ return {
141
+ workspace: input.resumed.workspace,
142
+ model: input.catalogue.find((m) => m.modelId === input.resumed?.model_id) ??
143
+ input.currentModel,
144
+ };
145
+ }
121
146
  export async function startAgent(options) {
122
147
  const { dim, bold } = palette(options.capabilities);
123
148
  // The TUI is interactive by construction, so `--allow-write` can be honoured
@@ -128,11 +153,6 @@ export async function startAgent(options) {
128
153
  ...options.commandIo,
129
154
  env: { ...options.commandIo.env, MAFDET_API_KEY: options.apiKey },
130
155
  });
131
- options.stdout.write(dim(`\n ${options.model.modelId} - ${mode}` +
132
- (hasCommands
133
- ? ` - commands: ${options.permissions.allowedCommands.join(", ")}`
134
- : "") +
135
- "\n type a goal, or /help for the session commands\n\n"));
136
156
  const opened = options.session?.resumeId
137
157
  ? openForResume({
138
158
  root: sessionsDir(options.commandIo.env ?? process.env),
@@ -174,10 +194,12 @@ export async function startAgent(options) {
174
194
  // was recorded with. Continuing project A's conversation against project B's
175
195
  // files is how an agent with write access edits the wrong repository - and
176
196
  // it used to be the default, because the workspace came from the cwd.
177
- const effectiveWorkspace = opened === undefined ? options.workspace : opened.metadata.workspace;
178
- const effectiveModel = opened === undefined
179
- ? options.model
180
- : ((options.catalogue ?? []).find((m) => m.modelId === opened.metadata.model_id) ?? options.model);
197
+ const { workspace: effectiveWorkspace, model: effectiveModel } = effectiveRunContext({
198
+ resumed: opened?.metadata,
199
+ currentWorkspace: options.workspace,
200
+ currentModel: options.model,
201
+ catalogue: options.catalogue ?? [],
202
+ });
181
203
  let workspace;
182
204
  try {
183
205
  workspace = Workspace.open(effectiveWorkspace);
@@ -194,6 +216,14 @@ export async function startAgent(options) {
194
216
  if (opened !== undefined && effectiveWorkspace !== options.workspace) {
195
217
  options.stdout.write(dim(` continuing in ${effectiveWorkspace}, where it was recorded\n`));
196
218
  }
219
+ // R-9 · Printed here, not before the session was resolved: on a resume the
220
+ // model in use is the one the session recorded, and announcing the model
221
+ // picked beforehand named something that was never going to be used.
222
+ options.stdout.write(dim(`\n ${stripAnsi(effectiveModel.modelId)} - ${mode}` +
223
+ (hasCommands
224
+ ? ` - commands: ${options.permissions.allowedCommands.join(", ")}`
225
+ : "") +
226
+ "\n type a goal, or /help for the session commands\n\n"));
197
227
  const recorder = opened !== undefined
198
228
  ? new SessionRecorder({
199
229
  writer: opened.writer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mafdet/cli",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Mafdet AI command line — model calls, account queries, and a permissioned local agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",