howtofixcli 0.7.7 → 0.7.9

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 (152) hide show
  1. package/dist/chunk-27TRKD65.js +2 -0
  2. package/dist/chunk-3FYCFLNK.js +1 -0
  3. package/dist/chunk-56SEAGAQ.js +1 -0
  4. package/dist/chunk-63ZCQDXH.js +6 -0
  5. package/dist/chunk-6FUWMLWO.js +1 -0
  6. package/dist/chunk-7CHDNTH6.js +1 -0
  7. package/dist/chunk-ACOMNDUL.js +185 -0
  8. package/dist/chunk-AEVPET7X.js +2 -0
  9. package/dist/chunk-AGGIYXN7.js +2 -0
  10. package/dist/chunk-BHVBO4SC.js +1 -0
  11. package/dist/chunk-FPXVDP32.js +8 -0
  12. package/dist/chunk-GIUYIKH3.js +109 -0
  13. package/dist/chunk-GQCAJ56I.js +5 -0
  14. package/dist/chunk-GXB7RZHH.js +1 -0
  15. package/dist/chunk-IGEY3AWF.js +1 -0
  16. package/dist/chunk-ILDDDLRC.js +1 -0
  17. package/dist/chunk-JFYVRWJI.js +2 -0
  18. package/dist/chunk-MVLCGU3G.js +1 -0
  19. package/dist/chunk-N345KWWK.js +1 -0
  20. package/dist/chunk-NBQFPCRT.js +1 -0
  21. package/dist/chunk-OIPSLIZE.js +5 -0
  22. package/dist/chunk-ORJ2QZBG.js +3 -0
  23. package/dist/chunk-PCALBZOO.js +2 -0
  24. package/dist/chunk-PDNBGUYP.js +2 -0
  25. package/dist/chunk-S656RE4A.js +1 -0
  26. package/dist/chunk-TSEKPGK4.js +1 -0
  27. package/dist/chunk-U6CI6KXN.js +34 -0
  28. package/dist/chunk-UTYFLLSM.js +1 -0
  29. package/dist/chunk-V3R4FYTJ.js +1 -0
  30. package/dist/chunk-V656XVXV.js +8 -0
  31. package/dist/chunk-WKUJ7CFO.js +1 -0
  32. package/dist/chunk-WOT6VMZA.js +1 -0
  33. package/dist/chunk-WUPU6LTD.js +4 -0
  34. package/dist/chunk-WX3V5P7U.js +5 -0
  35. package/dist/chunk-ZKZ6WCI3.js +2 -0
  36. package/dist/main.js +94 -536
  37. package/package.json +15 -62
  38. package/dist/account-credential.js +0 -136
  39. package/dist/account-credential.test.js +0 -38
  40. package/dist/activity.js +0 -124
  41. package/dist/agent-defs.js +0 -237
  42. package/dist/agent-limiter.js +0 -68
  43. package/dist/agents.js +0 -554
  44. package/dist/anim.js +0 -361
  45. package/dist/args.js +0 -228
  46. package/dist/at-file.js +0 -28
  47. package/dist/brand-extension.js +0 -87
  48. package/dist/brand.js +0 -76
  49. package/dist/browser.js +0 -58
  50. package/dist/cache-report.js +0 -139
  51. package/dist/checkpoint.js +0 -437
  52. package/dist/claims.js +0 -167
  53. package/dist/cli-auth.js +0 -142
  54. package/dist/cli-checkin.js +0 -116
  55. package/dist/cli-credentials.js +0 -254
  56. package/dist/clipboard.js +0 -149
  57. package/dist/collapse.js +0 -129
  58. package/dist/commands.js +0 -301
  59. package/dist/compaction-guard.js +0 -81
  60. package/dist/context-extension.js +0 -17
  61. package/dist/dcp/commands.js +0 -172
  62. package/dist/dcp/compress-state.js +0 -139
  63. package/dist/dcp/compress.js +0 -329
  64. package/dist/dcp/dcp-extension.js +0 -288
  65. package/dist/dcp/dedup.js +0 -93
  66. package/dist/dcp/ids.js +0 -189
  67. package/dist/dcp/index.js +0 -25
  68. package/dist/dcp/prompts.js +0 -68
  69. package/dist/dcp/protected-content.js +0 -221
  70. package/dist/dcp/protected.js +0 -161
  71. package/dist/dcp/prune.js +0 -418
  72. package/dist/dcp/state.js +0 -176
  73. package/dist/device-id.js +0 -90
  74. package/dist/device-id.test.js +0 -23
  75. package/dist/errors.js +0 -63
  76. package/dist/extensions.js +0 -48
  77. package/dist/fatal.js +0 -35
  78. package/dist/feed.js +0 -498
  79. package/dist/fs-atomic.js +0 -54
  80. package/dist/gateway-errors.js +0 -90
  81. package/dist/git.js +0 -230
  82. package/dist/goal-extension.js +0 -88
  83. package/dist/goal.js +0 -222
  84. package/dist/hooks-extension.js +0 -176
  85. package/dist/hooks.js +0 -443
  86. package/dist/howtofix-account.js +0 -715
  87. package/dist/hud-line.js +0 -153
  88. package/dist/hwid.js +0 -130
  89. package/dist/interrupt.js +0 -60
  90. package/dist/json-events.js +0 -50
  91. package/dist/level-style.js +0 -112
  92. package/dist/loop-guard.js +0 -95
  93. package/dist/lsp-tools.js +0 -32
  94. package/dist/lsp.js +0 -347
  95. package/dist/mcp-config.js +0 -85
  96. package/dist/mcp-lifecycle.js +0 -147
  97. package/dist/model-catalog.js +0 -217
  98. package/dist/model-resolve.js +0 -65
  99. package/dist/notes.js +0 -78
  100. package/dist/oauth-login.js +0 -568
  101. package/dist/paths.js +0 -61
  102. package/dist/permissions.js +0 -298
  103. package/dist/plan-mode.js +0 -363
  104. package/dist/preset-command.js +0 -227
  105. package/dist/preset-extension.js +0 -35
  106. package/dist/preset-mcp.js +0 -292
  107. package/dist/presets.js +0 -727
  108. package/dist/prompt-files.js +0 -103
  109. package/dist/prompt-text.js +0 -41
  110. package/dist/proxy.js +0 -137
  111. package/dist/quality/ast.js +0 -196
  112. package/dist/quality/cognitive.js +0 -83
  113. package/dist/quality/cyclomatic.js +0 -30
  114. package/dist/quality/halstead.js +0 -83
  115. package/dist/quality/index.js +0 -18
  116. package/dist/quality/length.js +0 -3
  117. package/dist/quality/maintainability.js +0 -15
  118. package/dist/quality/nesting.js +0 -57
  119. package/dist/quality/report.js +0 -803
  120. package/dist/quality/smells.js +0 -67
  121. package/dist/quality-tool.js +0 -63
  122. package/dist/read-only-commands.js +0 -597
  123. package/dist/repo-map.js +0 -292
  124. package/dist/rtk.js +0 -193
  125. package/dist/sandbox.js +0 -105
  126. package/dist/schema.js +0 -214
  127. package/dist/session-lifecycle.js +0 -19
  128. package/dist/session-retention.js +0 -111
  129. package/dist/session.js +0 -446
  130. package/dist/sessions.js +0 -100
  131. package/dist/settings.js +0 -148
  132. package/dist/skills.js +0 -35
  133. package/dist/slop-tool.js +0 -96
  134. package/dist/spend-cap.js +0 -43
  135. package/dist/subagent.js +0 -171
  136. package/dist/tar.js +0 -146
  137. package/dist/theme.js +0 -79
  138. package/dist/thinking.js +0 -45
  139. package/dist/todo.js +0 -114
  140. package/dist/tool-loadout.js +0 -97
  141. package/dist/tool-render.js +0 -769
  142. package/dist/tools.js +0 -176
  143. package/dist/ui.js +0 -4751
  144. package/dist/update.js +0 -533
  145. package/dist/usage-segment.js +0 -69
  146. package/dist/web-tools.js +0 -46
  147. package/dist/web.js +0 -265
  148. package/dist/welcome.js +0 -179
  149. package/dist/workflow-engine.js +0 -246
  150. package/dist/workflow-progress.js +0 -446
  151. package/dist/workflow-view.js +0 -195
  152. package/dist/workflow.js +0 -349
@@ -1,217 +0,0 @@
1
- /**
2
- * Real context windows and reasoning support, from models.dev.
3
- *
4
- * `GET /v1/models` on the gateway returns ids and nothing else — verified against
5
- * the live service, whose response carries only `id`, `object`, `created`,
6
- * `owned_by` and `supported_endpoint_types`. So a synced model has no declared
7
- * window and no declared capability, and `/connect` filled the gap with two
8
- * guesses that were both wrong in the same direction:
9
- *
10
- * - `contextWindow: 128_000` for every model. The gateway serves models with
11
- * 1,000,000-token windows (deepseek-v4.1-flash, kimi-k3, mimo-v2.5), so the
12
- * context meter read ~8x too full and the CLI compacted a conversation that
13
- * had barely started.
14
- * - no `reasoning` field at all. The SDK gates thinking on it — the schema is
15
- * `reasoning: boolean`, and `getSupportedThinkingLevels` returns `["off"]`
16
- * the moment it is falsy — so every synced model lost its thinking levels
17
- * even though the model reasons. That is the bug a user reports as "thinking
18
- * is missing on a model that has it".
19
- *
20
- * models.dev publishes exactly these facts per model (`limit.context`,
21
- * `limit.output`, `reasoning`). It is a public catalogue, so the CLI can read it
22
- * instead of guessing. Nothing here is bundled: the catalogue is fetched once,
23
- * slimmed to the three fields that are used, and cached next to the agent state,
24
- * so a later start can repair models without a network call.
25
- *
26
- * The pure half (index, lookup) is separated from the network half so the
27
- * matching rules are testable without a fetch.
28
- */
29
- /** Where the catalogue lives. One document, every provider. */
30
- export const MODELS_DEV_URL = "https://models.dev/api.json";
31
- /** How long to wait for the catalogue. It is ~5 MB, so this is generous. */
32
- export const CATALOG_TIMEOUT_MS = 20_000;
33
- /**
34
- * Providers whose entry wins when the same model is listed by several.
35
- *
36
- * The catalogue is community-maintained and mirrors every reseller, so one model
37
- * id resolves to a dozen near-identical entries. The first-party listing is the
38
- * one whose numbers describe the model rather than a middleman's limits, so the
39
- * search prefers it. Order matters only among the preferred set; everything else
40
- * is ordered alphabetically so the result never depends on object iteration.
41
- */
42
- const PREFERRED_PROVIDERS = ["anthropic", "openai", "google", "deepseek", "zai", "xai", "moonshotai", "minimax", "alibaba", "xiaomi", "meta", "tencent", "nvidia", "venice", "openrouter"];
43
- /** The comparison form of a model id: case and punctuation are not meaningful. */
44
- export function normalizeModelId(id) {
45
- return id.toLowerCase().replace(/[^a-z0-9]+/g, "");
46
- }
47
- /** The last path segment of an id, normalized: `z-ai/glm-5.3-fast` -> `glm53fast`. */
48
- function lastSegment(id) {
49
- const parts = id.split("/");
50
- return normalizeModelId(parts[parts.length - 1] ?? id);
51
- }
52
- /**
53
- * Index every model in the catalogue document.
54
- *
55
- * One entry per normalized id; when several providers list the same model, the
56
- * preferred provider wins (see {@link PREFERRED_PROVIDERS}). A model whose entry
57
- * carries no `limit.context` is skipped: without a window there is nothing to
58
- * correct, and indexing it would let a bad entry shadow a good one.
59
- */
60
- export function buildCatalogIndex(raw) {
61
- const index = new Map();
62
- const owners = new Map();
63
- if (!raw || typeof raw !== "object")
64
- return index;
65
- const providers = raw;
66
- const priority = (provider) => {
67
- const at = PREFERRED_PROVIDERS.indexOf(provider);
68
- return at === -1 ? PREFERRED_PROVIDERS.length : at;
69
- };
70
- // Sorted so the winner among equally-preferred providers is decided by name,
71
- // not by the insertion order of a parsed JSON object — this number feeds a
72
- // token budget, so it must not depend on how the document happened to be built.
73
- for (const provider of Object.keys(providers).sort()) {
74
- const models = providers[provider]?.models;
75
- if (!models || typeof models !== "object")
76
- continue;
77
- for (const id of Object.keys(models)) {
78
- const model = models[id];
79
- const context = Number(model?.limit?.context);
80
- if (!Number.isFinite(context) || context <= 0)
81
- continue;
82
- const key = normalizeModelId(id);
83
- const rank = priority(provider);
84
- const held = owners.get(key);
85
- // A preferred provider always wins; otherwise the first (alphabetical) one
86
- // keeps the slot. Never let a later, less-preferred listing overwrite it.
87
- if (held !== undefined && held <= rank)
88
- continue;
89
- const output = Number(model?.limit?.output);
90
- index.set(key, {
91
- contextWindow: Math.floor(context),
92
- maxTokens: Number.isFinite(output) && output > 0 ? Math.floor(output) : undefined,
93
- reasoning: model?.reasoning === true,
94
- vision: Array.isArray(model?.modalities?.input) && model.modalities.input.includes("image"),
95
- });
96
- owners.set(key, rank);
97
- }
98
- }
99
- return index;
100
- }
101
- /**
102
- * The catalogue entry for a gateway model id, or undefined when it is unknown.
103
- *
104
- * Three passes, in order of how literal the match is: the whole id, the last path
105
- * segment, then a suffix. The gateway renames models (`deepseek-v4.1-flash` is
106
- * `DeepSeek-V4.1-Flash` in the catalogue, `glm-5.3-flash` is `zai/glm-5.3-flash`)
107
- * and drops vendor prefixes (`anthropic/claude-sonnet-5` keeps its own), so a
108
- * single exact match finds less than half. A miss is not a failure: the caller
109
- * keeps its conservative default, which is the behaviour before this module.
110
- */
111
- export function lookupModel(index, id) {
112
- const wanted = normalizeModelId(id);
113
- if (!wanted)
114
- return undefined;
115
- const exact = index.get(wanted);
116
- if (exact)
117
- return exact;
118
- const wantedTail = lastSegment(id);
119
- for (const [key, entry] of index) {
120
- if (lastSegment(key) === wantedTail)
121
- return entry;
122
- }
123
- for (const [key, entry] of index) {
124
- if (key.endsWith(wanted) || wanted.endsWith(key))
125
- return entry;
126
- }
127
- return undefined;
128
- }
129
- /** Where the slimmed catalogue is cached, so a later start needs no network. */
130
- export function catalogCachePath(agentDir) {
131
- return `${agentDir}/models-dev-cache.json`;
132
- }
133
- /**
134
- * The serialized form of an index: `{ id: [context, output, reasoning, vision] }`.
135
- *
136
- * An array rather than an object per entry because the catalogue is 8,000+ rows
137
- * and repeating the keys eight thousand times triples the file for no reader's
138
- * benefit. The cache is internal — nothing outside this module parses it.
139
- *
140
- * `vision` was added after the first release, so a cache written by the older
141
- * build is a 3-tuple. {@link deserializeCatalog} reads the fourth slot as
142
- * optional and defaults it to `false`, which is the behaviour that build had;
143
- * the next fetch fills it in. Bumping a format number would instead throw the
144
- * whole cache away and cost every user a 5 MB download on the next start.
145
- */
146
- export function serializeCatalog(index) {
147
- const out = {};
148
- for (const [key, entry] of index) {
149
- out[key] = [entry.contextWindow, entry.maxTokens ?? 0, entry.reasoning ? 1 : 0, entry.vision ? 1 : 0];
150
- }
151
- return JSON.stringify(out);
152
- }
153
- /** Read back {@link serializeCatalog}. Returns an empty index for anything unusable. */
154
- export function deserializeCatalog(text) {
155
- const index = new Map();
156
- let parsed;
157
- try {
158
- parsed = JSON.parse(text);
159
- }
160
- catch {
161
- return index;
162
- }
163
- if (!parsed || typeof parsed !== "object")
164
- return index;
165
- for (const [key, value] of Object.entries(parsed)) {
166
- if (!Array.isArray(value) || value.length < 3)
167
- continue;
168
- const context = Number(value[0]);
169
- const output = Number(value[1]);
170
- if (!Number.isFinite(context) || context <= 0)
171
- continue;
172
- index.set(key, {
173
- contextWindow: Math.floor(context),
174
- maxTokens: Number.isFinite(output) && output > 0 ? Math.floor(output) : undefined,
175
- reasoning: value[2] === 1 || value[2] === true,
176
- // Absent in a cache written before this field existed: read it as "no
177
- // vision", which is what that build assumed, and let the next fetch correct it.
178
- vision: value[3] === 1 || value[3] === true,
179
- });
180
- }
181
- return index;
182
- }
183
- /**
184
- * Fetch the catalogue and index it.
185
- *
186
- * Never throws: a catalogue that cannot be fetched is a reason to keep the
187
- * conservative defaults, not a reason for `/connect` to fail. The caller reports
188
- * which of the two happened.
189
- */
190
- export async function fetchCatalog(options) {
191
- const url = options?.url ?? MODELS_DEV_URL;
192
- const doFetch = options?.fetchImpl ?? fetch;
193
- const timeoutMs = options?.timeoutMs ?? CATALOG_TIMEOUT_MS;
194
- const controller = new AbortController();
195
- const timer = setTimeout(() => controller.abort(), timeoutMs);
196
- try {
197
- const response = await doFetch(url, { signal: controller.signal, headers: { Accept: "application/json" } });
198
- if (!response.ok) {
199
- return { ok: false, message: `the model catalogue answered ${response.status}` };
200
- }
201
- const index = buildCatalogIndex(await response.json());
202
- if (index.size === 0) {
203
- return { ok: false, message: "the model catalogue was empty or in an unexpected shape" };
204
- }
205
- return { ok: true, index };
206
- }
207
- catch (cause) {
208
- const aborted = cause instanceof Error && cause.name === "AbortError";
209
- return {
210
- ok: false,
211
- message: aborted ? `the model catalogue did not answer within ${Math.round(timeoutMs / 1000)}s` : "could not reach the model catalogue",
212
- };
213
- }
214
- finally {
215
- clearTimeout(timer);
216
- }
217
- }
@@ -1,65 +0,0 @@
1
- /**
2
- * Resolve a model spec the way the CLI's own `--model` flag does.
3
- *
4
- * Exact `provider/id` or a bare `id` first, then a case-insensitive substring
5
- * match, so both the name `/model` prints and a shorthand work. Lifted out of
6
- * `session.ts` when subagents gained a model of their own: two resolvers would
7
- * drift, and a subagent picking a different model than the parent asked for
8
- * would read as the option being broken.
9
- */
10
- export function resolveModel(models, spec) {
11
- const exact = models.find((model) => `${model.provider}/${model.id}` === spec || model.id === spec);
12
- if (exact)
13
- return exact;
14
- const needle = spec.toLowerCase();
15
- return models.find((model) => model.id.toLowerCase().includes(needle) || `${model.provider}/${model.id}`.toLowerCase().includes(needle));
16
- }
17
- /**
18
- * Order a catalogue for choosing a default model, best first.
19
- *
20
- * A gateway lists its catalogue in no useful order — `anthropic/claude-opus-4.8`
21
- * can come first while the fast general-purpose models are far down the list — so
22
- * "use whatever is first" hands a new user the most expensive thing on the
23
- * account, or an image model that cannot hold a conversation.
24
- *
25
- * Image, embedding, audio and video models are pushed to the end: they cannot
26
- * answer a coding prompt, so selecting one would look like a broken install. The
27
- * caller then probes the first few and takes the first that answers.
28
- */
29
- const PREFERRED_FAMILIES = ["deepseek", "qwen", "claude", "gpt-5", "gpt-4", "gemini", "glm", "mimo", "kimi", "llama"];
30
- const NOT_CHAT = /(image|embedding|embed|whisper|tts|audio|video|rerank|moderation|dall-e|stable-diffusion|sora)/i;
31
- export function orderModelsForDefault(ids) {
32
- const rank = (id) => {
33
- if (NOT_CHAT.test(id))
34
- return 100;
35
- const family = PREFERRED_FAMILIES.findIndex((name) => id.toLowerCase().includes(name));
36
- return family === -1 ? 50 : family;
37
- };
38
- // Stable within a rank, so the gateway's own order decides ties.
39
- return ids
40
- .map((id, index) => ({ id, index }))
41
- .sort((a, b) => rank(a.id) - rank(b.id) || a.index - b.index)
42
- .map((entry) => entry.id);
43
- }
44
- /**
45
- * The model a session should start on when nothing was asked for.
46
- *
47
- * The model runtime knows every model in the catalog it ships, including
48
- * providers this account holds no credential for, and its own default is the
49
- * catalog's — `opencode/kimi-k2.6` — not the account's. Starting there is not a
50
- * cosmetic problem: the first prompt goes to a provider that cannot answer and
51
- * comes back as an empty assistant message, with nothing in the transcript to
52
- * explain why it looks hung.
53
- *
54
- * So the default comes from the models that are actually available, and a
55
- * HowToFix provider wins over any other reachable one: an install that signed in
56
- * to HowToFix should run HowToFix's models.
57
- */
58
- export function defaultModelFor(available, ownProviders = []) {
59
- if (available.length === 0)
60
- return undefined;
61
- const own = available.filter((model) => ownProviders.includes(model.provider));
62
- const pool = own.length > 0 ? own : available;
63
- const first = orderModelsForDefault(pool.map((model) => model.id))[0];
64
- return pool.find((model) => model.id === first) ?? pool[0];
65
- }
package/dist/notes.js DELETED
@@ -1,78 +0,0 @@
1
- /**
2
- * A shared scratchpad for facts that must survive context compaction and reach
3
- * every agent working on the task.
4
- *
5
- * A shared notes store for the session.
6
- * Why it matters: the `task` tool spawns subagents with their own context
7
- * window, so anything the main agent learned while scouting is invisible to them,
8
- * and anything they discover dies with their run. A note written once is visible
9
- * to the parent and to every sibling, and it survives a compaction that would
10
- * otherwise drop the discovery from the transcript.
11
- *
12
- * The store is process-wide on purpose: `agents.ts` opens subagent sessions in
13
- * this same process, so they share it. It is in-memory only — notes are working
14
- * state for one session, not project documentation (that is `AGENTS.md`).
15
- */
16
- import { StringEnum } from "@netrodev/hxf-ai";
17
- import { defineTool } from "@netrodev/hxf-coding-agent";
18
- import { Type } from "typebox";
19
- const notes = new Map();
20
- /** Longest note accepted, so one call cannot flood every later request. */
21
- const MAX_VALUE = 2000;
22
- /** Longest key accepted; keys are the lookup handle, so they stay short. */
23
- const MAX_KEY = 80;
24
- export function clearNotes() {
25
- notes.clear();
26
- }
27
- export function noteCount() {
28
- return notes.size;
29
- }
30
- /** The block injected into the prompt, or an empty string when there is nothing. */
31
- export function renderNotes() {
32
- if (notes.size === 0)
33
- return "";
34
- const lines = [...notes].map(([key, value]) => `- ${key}: ${value}`);
35
- return ["<shared-notes>", "Working notes recorded with the `context` tool. They survive compaction and are visible to every agent on this task.", ...lines, "</shared-notes>"].join("\n");
36
- }
37
- export function applyNote(op, key, value) {
38
- if (op === "list") {
39
- if (notes.size === 0)
40
- return { text: "No notes." };
41
- return { text: [...notes].map(([k, v]) => `${k} = ${v}`).join("\n") };
42
- }
43
- if (!key)
44
- return { text: "Error: key is required", error: "key required" };
45
- if (key.length > MAX_KEY)
46
- return { text: `Error: key longer than ${MAX_KEY} characters`, error: "key too long" };
47
- if (op === "forget") {
48
- const existed = notes.delete(key);
49
- return { text: existed ? `Removed ${key}` : `No note named ${key}` };
50
- }
51
- if (op === "set") {
52
- if (!value)
53
- return { text: "Error: value is required for set", error: "value required" };
54
- const clipped = value.length > MAX_VALUE ? `${value.slice(0, MAX_VALUE)}…` : value;
55
- notes.set(key, clipped);
56
- return { text: `Recorded ${key} (${notes.size} note${notes.size === 1 ? "" : "s"} held)` };
57
- }
58
- return { text: `Error: unknown op ${JSON.stringify(op)}`, error: "unknown op" };
59
- }
60
- export const contextTool = defineTool({
61
- name: "context",
62
- label: "context",
63
- description: "Record a durable working note (a decision, a file's role, a fact you would otherwise re-derive). Notes survive context compaction and are visible to every agent on this task, including subagents. Use `set` to write, `list` to read, `forget` to remove.",
64
- promptSnippet: "context: durable notes shared with every agent on this task",
65
- parameters: Type.Object({
66
- op: StringEnum(["set", "list", "forget"], { description: "set writes a note, list reads all, forget removes one" }),
67
- key: Type.Optional(Type.String({ description: "Short lookup key, e.g. 'auth-entrypoint'" })),
68
- value: Type.Optional(Type.String({ description: "The fact to keep (for set)" })),
69
- }),
70
- async execute(_toolCallId, params) {
71
- const { text, error } = applyNote(params.op, params.key, params.value);
72
- return {
73
- content: [{ type: "text", text }],
74
- details: { op: params.op, notes: Object.fromEntries(notes) },
75
- ...(error ? { isError: true } : {}),
76
- };
77
- },
78
- });