akanjs 3.0.0-beta.13 → 3.0.0-beta.15

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 (74) hide show
  1. package/common/CodeAgentClient.ts +102 -0
  2. package/common/CodeTranscript.ts +337 -0
  3. package/common/codeAgentProfile.ts +175 -0
  4. package/common/codeAgentWire.ts +409 -0
  5. package/common/index.ts +51 -0
  6. package/common/markdownSpans.ts +57 -0
  7. package/dictionary/agent.dictionary.ts +4 -11
  8. package/dictionary/base.dictionary.ts +1 -0
  9. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  10. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  11. package/package.json +3 -1
  12. package/server/akanOption.ts +9 -2
  13. package/server/di/predefinedAdaptor.ts +2 -2
  14. package/service/predefinedAdaptor/anthropicLlm.ts +6 -4
  15. package/service/predefinedAdaptor/index.ts +0 -1
  16. package/service/predefinedAdaptor/llm.adaptor.ts +21 -1
  17. package/service/predefinedAdaptor/openaiLlm.ts +37 -18
  18. package/store/agentic/StToolBuilder.ts +54 -0
  19. package/store/agentic/attachAgentic.ts +2 -1
  20. package/store/agentic/useFormTools.ts +1 -1
  21. package/types/common/CodeAgentClient.d.ts +26 -0
  22. package/types/common/CodeTranscript.d.ts +99 -0
  23. package/types/common/codeAgentProfile.d.ts +111 -0
  24. package/types/common/codeAgentWire.d.ts +388 -0
  25. package/types/common/index.d.ts +7 -0
  26. package/types/common/markdownSpans.d.ts +40 -0
  27. package/types/dictionary/agent.dictionary.d.ts +1 -1
  28. package/types/dictionary/base.dictionary.d.ts +1 -1
  29. package/types/dictionary/dictionary.d.ts +9 -9
  30. package/types/server/akanOption.d.ts +9 -2
  31. package/types/server/di/predefinedAdaptor.d.ts +2 -2
  32. package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
  33. package/types/service/predefinedAdaptor/index.d.ts +0 -1
  34. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +14 -1
  35. package/types/service/predefinedAdaptor/openaiLlm.d.ts +20 -11
  36. package/types/store/agentic/StToolBuilder.d.ts +22 -0
  37. package/types/store/agentic/attachAgentic.d.ts +2 -1
  38. package/types/store/baseSt.d.ts +2 -2
  39. package/types/ui/Agent/Chat.d.ts +7 -1
  40. package/types/ui/Agent/Composer.d.ts +17 -1
  41. package/types/ui/Agent/MentionNode.d.ts +26 -0
  42. package/types/ui/Agent/RichInput.d.ts +22 -0
  43. package/types/ui/Agent/ToolCard.d.ts +16 -0
  44. package/types/ui/Agent/markdownSpans.d.ts +1 -0
  45. package/types/ui/Agent/mentionDraft.d.ts +19 -0
  46. package/types/ui/Agent/useChatReferences.d.ts +3 -2
  47. package/types/ui/UiOverride/context.d.ts +2 -0
  48. package/types/ui/index.d.ts +2 -1
  49. package/types/ui/recipe/badgeRecipe.d.ts +2 -2
  50. package/types/ui/recipe/buttonRecipe.d.ts +2 -2
  51. package/types/vendor/use-agentic/AgentSession.d.ts +15 -1
  52. package/types/vendor/use-agentic/ToolRunner.d.ts +21 -1
  53. package/types/vendor/use-agentic/types.d.ts +31 -1
  54. package/ui/Agent/Chat.tsx +24 -7
  55. package/ui/Agent/Composer.tsx +73 -21
  56. package/ui/Agent/Markdown.tsx +2 -2
  57. package/ui/Agent/MentionNode.ts +62 -0
  58. package/ui/Agent/RichInput.tsx +154 -0
  59. package/ui/Agent/ToolCard.tsx +39 -0
  60. package/ui/Agent/markdownSpans.tsx +26 -36
  61. package/ui/Agent/mentionDraft.ts +101 -0
  62. package/ui/Agent/useChatReferences.ts +5 -8
  63. package/ui/UiOverride/context.ts +2 -0
  64. package/ui/index.ts +2 -1
  65. package/vendor/use-agentic/AgentSession.ts +45 -1
  66. package/vendor/use-agentic/AgenticSurface.ts +2 -0
  67. package/vendor/use-agentic/ToolRunner.ts +55 -1
  68. package/vendor/use-agentic/types.ts +36 -1
  69. package/service/predefinedAdaptor/deepseekLlm.ts +0 -82
  70. package/types/service/predefinedAdaptor/deepseekLlm.d.ts +0 -19
  71. /package/{ui/Agent → common}/markdownBlocks.ts +0 -0
  72. /package/{ui/Agent → common}/markdownTable.ts +0 -0
  73. /package/types/{ui/Agent → common}/markdownBlocks.d.ts +0 -0
  74. /package/types/{ui/Agent → common}/markdownTable.d.ts +0 -0
@@ -2,7 +2,6 @@ export * from "./anthropicLlm";
2
2
  export * from "./cache.adaptor";
3
3
  export * from "./compress.adaptor";
4
4
  export * from "./database.adaptor";
5
- export * from "./deepseekLlm";
6
5
  export * from "./insightQuery";
7
6
  export * from "./llm.adaptor";
8
7
  export * from "./logging.adaptor";
@@ -131,7 +131,12 @@ export interface LlmAccepts {
131
131
  /**
132
132
  * Settings for whichever adaptor fills `LlmAdaptorRole`, registered with `option.setLlm(...)` and injected as the
133
133
  * `llmOption` use. It belongs to the role rather than to one provider: swapping the default for another `adapt()`
134
- * class re-reads the same three fields under that provider's own defaults.
134
+ * class re-reads the same fields under that provider's own defaults.
135
+ *
136
+ * It is the floor, not the whole shape. `setLlm` keeps whatever else it is handed, so an adaptor an app or a
137
+ * library wrote declares its own interface extending this one and reads it with `use<MyLlmOption>()` — a region,
138
+ * a project id, a deployment name reach it through the same channel the fields below do, instead of a second
139
+ * `option.use({...})` key beside it.
135
140
  */
136
141
  export interface LlmOption {
137
142
  apiKey?: string;
@@ -156,3 +161,18 @@ export interface LlmOption {
156
161
  */
157
162
  maxTokens?: number;
158
163
  }
164
+
165
+ /**
166
+ * What the chat prints as the party that refused a turn, carried on `agent.error.llmRequestFailed`.
167
+ *
168
+ * It is the host rather than the adaptor's own name because one adaptor speaks one dialect to whatever host it
169
+ * is pointed at — an OpenAI-dialect class aimed at a gateway would otherwise credit OpenAI for that gateway's
170
+ * refusal. A host that is not a URL is printed as written; there is nothing better to say about it.
171
+ */
172
+ export const llmProviderOf = (host: string): string => {
173
+ try {
174
+ return new URL(host).hostname;
175
+ } catch {
176
+ return host;
177
+ }
178
+ };
@@ -1,18 +1,27 @@
1
1
  import { Err } from "akanjs/dictionary";
2
2
  import { adapt } from "../adapt";
3
- import type { LlmAccepts, LlmAdaptor, LlmOption, LlmTurnAnswer, LlmTurnRequest } from "./llm.adaptor";
3
+ import {
4
+ type LlmAccepts,
5
+ type LlmAdaptor,
6
+ type LlmOption,
7
+ type LlmTurnAnswer,
8
+ type LlmTurnRequest,
9
+ llmProviderOf,
10
+ } from "./llm.adaptor";
4
11
  import { type OpenaiAnswer, OpenaiDialect } from "./openaiDialect";
5
12
 
6
13
  /**
7
- * OpenAI's chat-completions endpoint, and every gateway that serves the same dialect — `host` is what points it
8
- * at one. It is `DeepseekLlm`'s sibling rather than its replacement: same wire, and the difference that earns a
9
- * second class is that this one declares `accepts`, so an attached image reaches the model as an image part
10
- * instead of a note saying it could not be read.
14
+ * The OpenAI chat-completions dialect, pointed at a host — and the framework's default fill for `LlmAdaptorRole`.
15
+ *
16
+ * One class rather than one per vendor: DeepSeek, Groq, Together, OpenRouter, Ollama and a self-hosted vLLM all
17
+ * serve this same wire, so what distinguishes them is `option.setLlm({ host, model })` and not a protocol. A
18
+ * provider that speaks its own wire — Anthropic's blocks, Bedrock's signed requests — is a different adaptor
19
+ * class, in this package or in the app's own `srvkit/`, applied with
20
+ * `option.applyAdaptor(LlmAdaptorRole, TheClass)`.
11
21
  *
12
22
  * `model` is required and has no default. A default would be a model name that ages out of the provider's
13
- * catalogue into a 404 at the first turn, and — worse here than for a text-only adaptor — it would decide the
14
- * vision claim below on the app's behalf. Name the model in `option.setLlm({ model })`, and name
15
- * `accepts: { image: false }` beside it when that model is one of the provider's text-only ones.
23
+ * catalogue into a 404 at the first turn, and — worse — it would decide the vision claim below on the app's
24
+ * behalf.
16
25
  */
17
26
  export class OpenaiLlm
18
27
  extends adapt("openaiLlm" as const, ({ use }) => ({
@@ -20,13 +29,22 @@ export class OpenaiLlm
20
29
  }))
21
30
  implements LlmAdaptor
22
31
  {
32
+ static readonly defaultHost = "https://api.openai.com/v1";
33
+
23
34
  get #host() {
24
- return this.llmOption.host ?? "https://api.openai.com/v1";
35
+ return this.llmOption.host ?? OpenaiLlm.defaultHost;
25
36
  }
26
37
 
27
- /** The endpoint takes image parts, so that is the provider's answer; a model that does not takes the override. */
28
- get accepts(): LlmAccepts {
29
- return this.llmOption.accepts ?? { image: true };
38
+ /**
39
+ * OpenAI's own endpoint takes image parts, so that is what is claimed for the default host. A host the app
40
+ * named is a gateway this class knows nothing about, and claiming vision for one is the worst guess available:
41
+ * the bytes reach a model that cannot decode them and the whole turn dies on a 400, where text-only degrades
42
+ * them to a note the model can repeat back. So a named host is text-only until `option.setLlm({ accepts })`
43
+ * says otherwise — as is the OpenAI model that reads no image.
44
+ */
45
+ get accepts(): LlmAccepts | undefined {
46
+ if (this.llmOption.accepts) return this.llmOption.accepts;
47
+ return this.llmOption.host ? undefined : { image: true };
30
48
  }
31
49
 
32
50
  async chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null> {
@@ -52,8 +70,8 @@ export class OpenaiLlm
52
70
  );
53
71
  return await OpenaiDialect.consumeStream(body, onDelta);
54
72
  } catch (error) {
55
-
56
- this.logger.error(`OpenAI turn failed: ${error instanceof Error ? error.message : String(error)}`);
73
+
74
+ this.logger.error(`LLM turn failed: ${error instanceof Error ? error.message : String(error)}`);
57
75
  throw error;
58
76
  }
59
77
  }
@@ -66,7 +84,7 @@ export class OpenaiLlm
66
84
 
67
85
  signal: AbortSignal.timeout(120_000),
68
86
  });
69
- if (!response.ok) throw await OpenaiLlm.refusal(response);
87
+ if (!response.ok) throw await OpenaiLlm.refusal(this.#host, response);
70
88
  return (await response.json()) as T;
71
89
  }
72
90
 
@@ -77,13 +95,14 @@ export class OpenaiLlm
77
95
  body: JSON.stringify(body),
78
96
  signal: AbortSignal.timeout(120_000),
79
97
  });
80
- if (!response.ok || !response.body) throw await OpenaiLlm.refusal(response);
98
+ if (!response.ok || !response.body) throw await OpenaiLlm.refusal(this.#host, response);
81
99
  return response.body;
82
100
  }
83
101
 
84
102
  /** Carried on the `Err` so the chat prints the provider's own sentence rather than a status number. */
85
- static async refusal(response: Response): Promise<Error> {
86
- return new Err("agent.error.openaiRequestFailed", {
103
+ static async refusal(host: string, response: Response): Promise<Error> {
104
+ return new Err("agent.error.llmRequestFailed", {
105
+ provider: llmProviderOf(host),
87
106
  status: String(response.status),
88
107
  reason: await OpenaiDialect.reasonOf(response),
89
108
  });
@@ -9,6 +9,7 @@ import {
9
9
  type PrimitiveScalar,
10
10
  } from "akanjs/base";
11
11
  import type { ParamFieldType } from "akanjs/constant";
12
+ import type { ReactNode } from "react";
12
13
  import {
13
14
  AgenticSurface,
14
15
  type JsonSchema,
@@ -21,6 +22,15 @@ import { tagAction } from "../actionTag";
21
22
 
22
23
  import { useEffect, useRef } from "../hooks";
23
24
 
25
+ /**
26
+ * The two ways a card ends the call it is parked on. `submit`'s value is what the model reads back, `cancel` is
27
+ * why there is none; the first one settles it and the card leaves the screen.
28
+ */
29
+ export interface StToolCardControl {
30
+ submit: (value: unknown) => void;
31
+ cancel: (reason?: string) => void;
32
+ }
33
+
24
34
  export interface StToolMeta {
25
35
  /**
26
36
  * Whether the call has to be waited out before what it did to the screen is reported back to the model. `false`
@@ -184,6 +194,50 @@ export class StToolBuilder<Args extends unknown[] = []> {
184
194
  return callable.current.fn;
185
195
  }
186
196
 
197
+ /**
198
+ * The other way a chain ends, for a tool whose answer belongs to the **user**: the call parks in the chat, the
199
+ * card renders there, and what it submits is what the model reads back. `.exec()` runs a function; this one runs
200
+ * a person, which is the whole difference — a name and a phone number are theirs to type, and a model that
201
+ * invents them has answered its own question.
202
+ *
203
+ * The arguments arrive positional like `.exec()`'s, and they are checked **before** the card is parked rather
204
+ * than while it renders: a bad argument has to reach the model as a refusal it can correct, and a throw inside
205
+ * the render would take the chat panel down with it instead.
206
+ *
207
+ * `confirm` is not read here. The card in front of the user is already the asking.
208
+ */
209
+ card(render: (control: StToolCardControl, ...args: Args) => ReactNode): void {
210
+ const surface = useSurface();
211
+ const scope = useScopePath();
212
+ const live = useRef({ render, meta: this.#meta });
213
+ live.current = { render, meta: this.#meta };
214
+ const declared = useRef<{ name: string | null; desc: string; meta: StToolMeta; args: StToolArg[] } | null>(null);
215
+ if (declared.current?.name !== this.#name)
216
+ declared.current = { name: this.#name, desc: this.#desc, meta: this.#meta, args: this.#args };
217
+ const scopeKey = scope.join(".");
218
+ useEffect(() => {
219
+ const spec = declared.current;
220
+ const name = spec?.name;
221
+ if (!spec || !name) return;
222
+ return surface.registerTool(scope, {
223
+ name,
224
+ description: spec.desc,
225
+ settle: spec.meta.settle,
226
+ parameters: StToolBuilder.parametersOf(spec.args),
227
+ guard: (args) => {
228
+ try {
229
+ StToolBuilder.positionalOf(name, spec.args, args);
230
+ } catch (error) {
231
+ return error instanceof Error ? error.message : String(error);
232
+ }
233
+ return live.current.meta.guard?.(args) ?? true;
234
+ },
235
+ card: ({ args, submit, cancel }) =>
236
+ live.current.render({ submit, cancel }, ...(StToolBuilder.positionalOf(name, spec.args, args) as Args)),
237
+ });
238
+ }, [surface, scopeKey, this.#name]);
239
+ }
240
+
187
241
  static parametersOf(args: StToolArg[]): JsonSchema | undefined {
188
242
  if (!args.length) return undefined;
189
243
  const required = args.filter((arg) => !arg.optional).map((arg) => arg.name);
@@ -15,7 +15,8 @@ export interface StAgentic {
15
15
  /** A read-only derived value the agent can read while the component is mounted: `.desc()` then `.value()`. */
16
16
  expose: <T extends AgentFieldType>(name: string | null, type: T, meta?: StExposeMeta) => StExposeDraft<T>;
17
17
  /**
18
- * A component tool: `.desc()`, then `.arg()` / `.opt()`, chained onto one `.exec()` hook.
18
+ * A component tool: `.desc()`, then `.arg()` / `.opt()`, chained onto one terminal hook — `.exec()` for a tool
19
+ * a function answers, `.card()` for one the user answers in the chat.
19
20
  *
20
21
  * A falsy name declares the tool without publishing it — the callable still drives the click a person makes.
21
22
  * Every one of these ends in a hook, so a conditional surface withholds the name rather than skipping the chain.
@@ -68,7 +68,7 @@ export const useFormTools = (refName: string | null, write: (action: string, val
68
68
 
69
69
  for (const { entry, value } of patch) {
70
70
  const control = surface.tool(AgenticSurface.fullName(scope, entry.action), scope);
71
- if (control) void control.run({ value });
71
+ if (control?.run) void control.run({ value });
72
72
  else live.current(entry.action, value);
73
73
  }
74
74
  },
@@ -0,0 +1,26 @@
1
+ import { type CodeAgentCommand, type CodeAgentEvent } from "./codeAgentWire.d.ts";
2
+ export interface CodeAgentTransport {
3
+ send(line: string): void | Promise<void>;
4
+ /** Called once with a handler that receives whole lines. */
5
+ onLine(handler: (line: string) => void): void;
6
+ close?(): void | Promise<void>;
7
+ }
8
+ /**
9
+ * Speaks the code-agent wire over any line transport — a child process's stdio, a websocket, an in-process pair.
10
+ *
11
+ * It has no dependencies because the browser holds one too. Everything host-specific (spawning, reconnecting,
12
+ * authenticating) belongs to whoever builds the transport.
13
+ */
14
+ export declare class CodeAgentClient {
15
+ #private;
16
+ constructor(transport: CodeAgentTransport);
17
+ /** The highest sequence accepted. A reconnecting host replays from here. */
18
+ get lastSeq(): number;
19
+ /** A new session restarts the sequence, so the watermark has to go with it or every frame reads as a duplicate. */
20
+ resetSeq(): void;
21
+ on(listener: (event: CodeAgentEvent) => void): () => boolean;
22
+ send(command: CodeAgentCommand): Promise<unknown>;
23
+ close(): Promise<void>;
24
+ /** Feed raw chunks when the transport is byte-oriented rather than line-oriented. */
25
+ push(chunk: string): void;
26
+ }
@@ -0,0 +1,99 @@
1
+ import type { CodeAgentApprovalRequest, CodeAgentEvent, CodeAgentQuestion, CodeAgentSessionInfo, CodeAgentStopReason, CodeAgentToolOutcome, CodeAgentToolSummary } from "akanjs/common";
2
+ export type CodeTranscriptPart = {
3
+ kind: "user";
4
+ id: string;
5
+ text: string;
6
+ images?: number;
7
+ } | {
8
+ kind: "assistant";
9
+ id: string;
10
+ text: string;
11
+ streaming: boolean;
12
+ truncated: boolean;
13
+ } | {
14
+ kind: "thinking";
15
+ id: string;
16
+ text: string;
17
+ } | {
18
+ kind: "tool";
19
+ id: string;
20
+ tool: CodeAgentToolSummary;
21
+ outcome?: CodeAgentToolOutcome;
22
+ output?: string;
23
+ progress?: string;
24
+ } | {
25
+ kind: "notice";
26
+ id: string;
27
+ level: "info" | "warning" | "error";
28
+ text: string;
29
+ } | {
30
+ kind: "question";
31
+ id: string;
32
+ question: CodeAgentQuestion;
33
+ rendered?: string;
34
+ } | {
35
+ kind: "approval";
36
+ id: string;
37
+ request: CodeAgentApprovalRequest;
38
+ approved?: boolean;
39
+ } | {
40
+ kind: "host";
41
+ id: string;
42
+ hostKind: string;
43
+ text: string;
44
+ };
45
+ /**
46
+ * Folds the akan wire into what a screen shows.
47
+ *
48
+ * It reads the contract and nothing else, so the terminal and a browser can share it — and so anything it
49
+ * cannot render is a hole in the contract rather than a missing feature of one host.
50
+ *
51
+ * ⚠️ **Tool and host rows are upserted by id, and the first row of a turn is index `0`.** A truthy check on the
52
+ * looked-up index sends that first `tool_end` down the append path, and the same call ends up on screen twice.
53
+ * `undefined` is the only absence here; `== null` would be wrong for the same reason.
54
+ */
55
+ export declare class CodeTranscript {
56
+ #private;
57
+ get parts(): readonly CodeTranscriptPart[];
58
+ get info(): CodeAgentSessionInfo | undefined;
59
+ /**
60
+ * The one line that says what this session is, derived here rather than by each host.
61
+ *
62
+ * The declared context window is on it deliberately: a model descriptor that is wrong but self-consistent —
63
+ * a 65k window declared for a provider that serves 1M — passes every programmatic check there is, and the
64
+ * only thing that catches it is a person reading the number.
65
+ */
66
+ get headline(): string;
67
+ get context(): {
68
+ used: number;
69
+ max: number | undefined;
70
+ } | undefined;
71
+ get streaming(): boolean;
72
+ get compacting(): boolean;
73
+ get question(): CodeAgentQuestion | undefined;
74
+ get approval(): CodeAgentApprovalRequest | undefined;
75
+ get stopReason(): CodeAgentStopReason | undefined;
76
+ get queue(): {
77
+ steering: number;
78
+ followUp: number;
79
+ };
80
+ /** Bumped on every applied event, so a view can tell "changed" from "same" without comparing arrays. */
81
+ get revision(): number;
82
+ /**
83
+ * Drops every row, keeping what the session *is*.
84
+ *
85
+ * Only the screen: the model's own window is untouched, so a cleared transcript and a fresh conversation are
86
+ * two different things and a host that offers this has to say which one it did.
87
+ */
88
+ clear(): void;
89
+ /**
90
+ * A locally typed prompt, shown before the engine echoes it back as a `message`.
91
+ *
92
+ * Attachments ride beside the text rather than in it: the engine's echo carries the words only, and the
93
+ * two rows are matched by text to keep one bubble.
94
+ */
95
+ echo(text: string, images?: number): void;
96
+ /** Something the host has to say — a slash-command answer, a key hint — in the same column as the rest. */
97
+ note(level: "info" | "warning" | "error", text: string): void;
98
+ apply(event: CodeAgentEvent): void;
99
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * What a code agent is allowed to be, as one value.
3
+ *
4
+ * The profile is read by both the host and the core, so it lives here rather than in the CLI. Most of it is
5
+ * applied at **assembly time** — a tool outside `tools` is never constructed, so the model cannot call it and
6
+ * it costs no prompt tokens either. The runtime hooks (`approval`, `paths`, `limits`) are the second gate, for
7
+ * the things assembly cannot decide in advance.
8
+ */
9
+ export type CodeAgentBuiltinTool = "read" | "write" | "edit" | "ls" | "grep" | "find" | "bash";
10
+ /** `never` trusts the environment, `all` trusts nothing. The middle two are the useful ones. */
11
+ export type CodeAgentApprovalPolicy = "never" | "writes" | "commands" | "all";
12
+ /**
13
+ * How the core behaves while a human is being asked something.
14
+ *
15
+ * `await` holds the turn open until the answer arrives. `suspend` resolves the request with a sentinel, ends the
16
+ * turn, and expects the host to reopen one carrying the answer — which is the only form that survives a process
17
+ * restart and does not hold a shared workspace for the minutes or days a person may take to answer.
18
+ */
19
+ export type CodeAgentInteractionMode = "await" | "suspend";
20
+ export interface CodeAgentMcpServerRef {
21
+ name: string;
22
+ transport: "stdio" | "http";
23
+ /** stdio: the executable and its argv. http: the endpoint. */
24
+ command?: string;
25
+ args?: string[];
26
+ url?: string;
27
+ env?: Record<string, string>;
28
+ }
29
+ export interface CodeAgentSubagentBudget {
30
+ maxDepth: number;
31
+ maxConcurrent: number;
32
+ /** Tokens a whole subagent tree may spend before the `task` tool refuses to open another. */
33
+ budget: number;
34
+ }
35
+ export interface CodeAgentProfile {
36
+ name: string;
37
+ tools: {
38
+ builtin: CodeAgentBuiltinTool[];
39
+ /** Workflow, context and self-verification tools built on devkit. */
40
+ akan: boolean;
41
+ /**
42
+ * `"off"` reaches no server at all. An array turns discovery on: the workspace's `.akan/code/mcp.json`
43
+ * plus whatever the array names, so the common case is an empty array.
44
+ */
45
+ mcp: "off" | CodeAgentMcpServerRef[];
46
+ subagent: false | CodeAgentSubagentBudget;
47
+ web: {
48
+ fetch: boolean;
49
+ search: boolean;
50
+ };
51
+ };
52
+ approval: CodeAgentApprovalPolicy;
53
+ paths: {
54
+ root: string;
55
+ allow?: string[];
56
+ deny?: string[];
57
+ };
58
+ session: {
59
+ store: "file" | "memory" | "remote";
60
+ crossSession: boolean;
61
+ };
62
+ interaction: {
63
+ question: CodeAgentInteractionMode;
64
+ approval: CodeAgentInteractionMode;
65
+ };
66
+ network: {
67
+ allowHosts?: string[];
68
+ proxyBaseUrl?: string;
69
+ };
70
+ /** Whether the host can answer a question at all. A pod has nobody in front of it. */
71
+ ui: {
72
+ canPrompt: boolean;
73
+ };
74
+ limits: {
75
+ turnMs: number;
76
+ toolOutputBytes: number;
77
+ contextTokens: number;
78
+ /** How many times one turn-end plugin may reopen a turn with the same finding before it gives up. */
79
+ feedback: number;
80
+ };
81
+ context: {
82
+ /** The repo's `AGENTS.md` is ~26k tokens; a read-only reviewer does not need it. */
83
+ projectFiles: boolean;
84
+ /**
85
+ * The akan skill set — the scaffolding chain, the store surface, the validation loop.
86
+ *
87
+ * Only each skill's one-line description sits in the window; the body is read when a task matches. That
88
+ * makes it the cheapest context of the three, and the most valuable to a profile carrying no `AGENTS.md`.
89
+ */
90
+ skills: boolean;
91
+ };
92
+ }
93
+ /** The builtins that cannot change anything, which is what a profile is narrowed to when it must not. */
94
+ export declare const codeAgentReadOnlyBuiltins: CodeAgentBuiltinTool[];
95
+ /** Paths no profile may read, whatever its allowlist says. */
96
+ export declare const codeAgentDeniedPaths: string[];
97
+ export declare const codeAgentPresets: {
98
+ readonly local: (root: string) => CodeAgentProfile;
99
+ /**
100
+ * An isolated container. Everything is on because the container is the boundary, but nobody is watching it:
101
+ * a question that waits for an answer would hang the pod, so both interactions suspend. MCP is off because
102
+ * a pod's egress goes through a proxy and a stdio server started inside it is a process nobody vetted.
103
+ */
104
+ readonly pod: (root: string) => CodeAgentProfile;
105
+ /** A reviewer reads the repo and nothing else, so it reaches no external service. */
106
+ readonly review: (root: string) => CodeAgentProfile;
107
+ /** Isolation and approval are different axes: a pod is safe and its user may still want to be asked. */
108
+ readonly web: (root: string) => CodeAgentProfile;
109
+ };
110
+ export type CodeAgentPresetName = keyof typeof codeAgentPresets;
111
+ export declare const isCodeAgentPresetName: (name: string) => name is CodeAgentPresetName;