@akanjs/cli 3.0.0-beta.17 → 3.0.0-beta.19

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 (149) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-gjqnc2mg.js → CodeAgent-6cm97kay.js} +2 -2
  3. package/{CodeAgentRpcHost-qvwfv0n9.js → CodeAgentRpcHost-p7h6n2hp.js} +1 -1
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/{CodeTui-nd9hqvrw.js → CodeTui-za4jsm41.js} +205 -34
  6. package/{TunnelShare-r5tc2qk5.js → TunnelShare-2j6s511d.js} +2 -2
  7. package/{agent.command-n664dtcv.js → agent.command-e0wpbbyg.js} +6 -6
  8. package/akanCode.js +7 -5
  9. package/{application.command-t5542jte.js → application.command-8wyxqc8q.js} +28 -15
  10. package/{applicationBuildRunner-x4gbfn7y.js → applicationBuildRunner-n9gzaxd0.js} +35 -10
  11. package/{applicationReleasePackager-z61ep017.js → applicationReleasePackager-vpggfqgq.js} +1 -1
  12. package/buildBatch.proc.js +3 -3
  13. package/{capacitorApp-shv3ajh5.js → capacitorApp-q3z0qht0.js} +2 -2
  14. package/{cloud.command-dk5tj39k.js → cloud.command-4zwa5dsx.js} +34 -12
  15. package/cloud.script-fwx3vb34.js +20 -0
  16. package/{code.command-88sebgnn.js → code.command-468c9d7s.js} +41 -19
  17. package/commandManifest.json +1 -1
  18. package/{context.command-pqsm5jrk.js → context.command-dsnw0jby.js} +14 -14
  19. package/context.runner-rs4rj15d.js +22 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-0z2h2tks.js} +1 -1
  21. package/{devTui-gy6mendg.js → devTui-ffjsarqa.js} +3 -3
  22. package/{devTuiView-y1548wy6.js → devTuiView-1q1mdgrv.js} +1 -1
  23. package/{guideline.command-rpc2emw8.js → guideline.command-vhw13pqr.js} +8 -13
  24. package/guidelines/agentRule/agentRule.instruction.md +27 -10
  25. package/guidelines/conventions/conventions.instruction.md +53 -26
  26. package/guidelines/queryRule/queryRule.instruction.md +17 -7
  27. package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
  28. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  29. package/guidelines/transportRule/transportRule.instruction.md +10 -8
  30. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +4 -3
  31. package/incrementalBuilder.proc.js +3 -2
  32. package/{index-4n4821gp.js → index-10dkrrww.js} +4 -4
  33. package/{index-bzakk0c2.js → index-14zrtgfa.js} +5 -3
  34. package/{index-v17gj504.js → index-18zybgkc.js} +2 -2
  35. package/{index-qh7brn20.js → index-22xtgmhn.js} +6 -2
  36. package/{index-qhtj9y6j.js → index-2446k7b4.js} +74 -21
  37. package/{index-g9zghs4h.js → index-3jrp517k.js} +12 -12
  38. package/{index-vpx68e4x.js → index-4m929vvn.js} +2 -2
  39. package/{index-4cvpzkq6.js → index-53176s7j.js} +3 -3
  40. package/{index-jwrpsrg3.js → index-667w0yah.js} +1 -1
  41. package/{index-3mg4h788.js → index-7ep8qtvp.js} +2 -2
  42. package/{index-w8kmx1b6.js → index-89hy31kf.js} +30 -7
  43. package/index-9d6nvkz2.js +656 -0
  44. package/index-9dp031by.js +12 -0
  45. package/{index-zn8qgeqx.js → index-a1w6934z.js} +3 -3
  46. package/{index-p0tew3fq.js → index-ab0c38ea.js} +10 -5
  47. package/index-bc88j1ry.js +1166 -0
  48. package/{index-ns128c4q.js → index-bftkz6cp.js} +280 -73
  49. package/{index-kcxmv48c.js → index-bhtmj233.js} +4 -1
  50. package/{index-kvzjda3v.js → index-bwf965h6.js} +5 -5
  51. package/{index-p4p9t45x.js → index-crmeqnmf.js} +56 -44
  52. package/{index-8k16atqy.js → index-d2242ksy.js} +23 -6
  53. package/{index-2sq06vv0.js → index-e4v6vhxq.js} +417 -105
  54. package/{index-xjc7gg65.js → index-esk82rvk.js} +23 -33
  55. package/{index-sn7dnzq4.js → index-kwaq3n9n.js} +1 -1
  56. package/{index-gm76f0qb.js → index-n3cv0tm4.js} +1 -1
  57. package/{index-6na0sd2b.js → index-qdgzsnhg.js} +5 -2
  58. package/{index-twq9p2sp.js → index-s5rhm04y.js} +0 -6
  59. package/{index-n8kt2wh6.js → index-sq2dzkzm.js} +35 -10
  60. package/{index-myj01w9r.js → index-srmay5jw.js} +15 -2
  61. package/{index-2pm1j01n.js → index-txr7dj2g.js} +4 -4
  62. package/{index-3cymrzsb.js → index-vqx8bdxz.js} +17 -3
  63. package/{index-te0y1c0m.js → index-zqgj2rt2.js} +12 -7
  64. package/index.js +21 -21
  65. package/{library.command-m8p2r1dh.js → library.command-cqr175dy.js} +3 -3
  66. package/{localRegistry.command-yppdqrxv.js → localRegistry.command-49z55qqz.js} +9 -9
  67. package/{module.command-mfzvqt0n.js → module.command-9p8mm3b0.js} +5 -5
  68. package/{package.command-cgp2p2v5.js → package.command-wxyz909t.js} +5 -5
  69. package/package.json +2 -2
  70. package/{page.command-taenb02b.js → page.command-gsrsfqqs.js} +3 -3
  71. package/{primitive.command-7kpncmkk.js → primitive.command-ns1mptje.js} +6 -6
  72. package/{prompter-9mn3kp36.js → prompter-1f8d9pcc.js} +1 -1
  73. package/{quality.command-1ar94hrw.js → quality.command-e76k2q9k.js} +18 -17
  74. package/{repair.command-n08t6j8p.js → repair.command-pkrsm3cw.js} +5 -5
  75. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  76. package/{scalar.command-mq8qfp4t.js → scalar.command-0cpt3fc8.js} +4 -4
  77. package/skills/akan-code/SKILL.md +122 -0
  78. package/skills/akan-module/SKILL.md +2 -2
  79. package/skills/akan-store/SKILL.md +6 -2
  80. package/{subspace.command-jqv5n79w.js → subspace.command-fktpwp0g.js} +20 -10
  81. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  82. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  83. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  84. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  85. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  86. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  87. package/templates/appSample/lib/task/task.signal.ts +1 -1
  88. package/templates/appSample/srvkit/AuthGuard.ts +4 -1
  89. package/templates/appSample/srvkit/SessionInternalArg.ts +3 -1
  90. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  91. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  92. package/templates/crudPages/new/page.tsx +2 -1
  93. package/templates/crudPages/page.tsx +2 -1
  94. package/templates/crudSinglePage/page.tsx +2 -1
  95. package/templates/localDev/docker-compose.yaml.template +3 -14
  96. package/templates/module/__model__.signal.ts +12 -4
  97. package/templates/pkgRoot/tsconfig.json.template +2 -7
  98. package/templates/service/__model__.abstract.md.template +8 -0
  99. package/{tunnel.command-ht2es78c.js → tunnel.command-d5jy9gk8.js} +4 -4
  100. package/{workflow.command-tvz4nm9n.js → workflow.command-6tkcysmx.js} +9 -9
  101. package/{workspace.command-txv1yv5e.js → workspace.command-gv84eh1p.js} +25 -24
  102. package/cloud.script-ch3xrw3s.js +0 -20
  103. package/context.runner-bys6ykcp.js +0 -22
  104. package/guidelines/agentRule/agentRule.generate.json +0 -31
  105. package/guidelines/componentRule/componentRule.generate.json +0 -31
  106. package/guidelines/conventions/conventions.generate.json +0 -25
  107. package/guidelines/cssRule/cssRule.generate.json +0 -31
  108. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  109. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  110. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  111. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  112. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  113. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  114. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  115. package/guidelines/framework/framework.generate.json +0 -32
  116. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  117. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  118. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  119. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  120. package/guidelines/modelService/modelService.generate.json +0 -31
  121. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  122. package/guidelines/modelStore/modelStore.generate.json +0 -31
  123. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  124. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  125. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  126. package/guidelines/modelView/modelView.generate.json +0 -31
  127. package/guidelines/modelZone/modelZone.generate.json +0 -31
  128. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  129. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  130. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  131. package/guidelines/queryRule/queryRule.generate.json +0 -31
  132. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  133. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  134. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  135. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  136. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  137. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  138. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  139. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  140. package/guidelines/transportRule/transportRule.generate.json +0 -31
  141. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  142. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  143. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  144. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -34
  145. package/index-57p9p179.js +0 -461
  146. package/index-qt8dqgnf.js +0 -250
  147. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  148. package/templates/module/__model__.abstract.md +0 -28
  149. package/templates/service/__model__.abstract.md +0 -28
@@ -2,21 +2,22 @@
2
2
  import {
3
3
  SubagentPool2,
4
4
  akanCodePaths2,
5
- McpServerConfig
6
- } from "./index-qt8dqgnf.js";
5
+ McpServerConfig,
6
+ McpSignIn
7
+ } from "./index-9d6nvkz2.js";
7
8
  import {
8
9
  stripAnsi
9
10
  } from "./index-7md2g9vw.js";
10
11
  import {
12
+ akanCodeDefaultModel2,
11
13
  akanCodeModel2,
12
14
  akanCodeModelSupportsImages2,
13
15
  akanCodeModelWarnings2
14
- } from "./index-jwrpsrg3.js";
16
+ } from "./index-667w0yah.js";
15
17
 
16
18
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgent.ts
17
19
  import {
18
20
  createAgentSession,
19
- DEFAULT_COMPACTION_SETTINGS,
20
21
  DefaultResourceLoader
21
22
  } from "@earendil-works/pi-coding-agent";
22
23
  import {
@@ -513,7 +514,7 @@ class AkanToolPack2 {
513
514
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
514
515
  }
515
516
  async names() {
516
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
517
+ const { ContextRunner } = await import("./context.runner-rs4rj15d.js");
517
518
  return [
518
519
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
519
520
  AkanToolPack2.verifyToolName
@@ -521,7 +522,7 @@ class AkanToolPack2 {
521
522
  }
522
523
  static verifyToolName = "akan_verify";
523
524
  async#register(pi) {
524
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
525
+ const { ContextRunner } = await import("./context.runner-rs4rj15d.js");
525
526
  const runner = new ContextRunner;
526
527
  const guidelineNames = await AkanToolPack2.#guidelineNames();
527
528
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -563,7 +564,7 @@ class AkanToolPack2 {
563
564
  }
564
565
  static async#guidelineNames() {
565
566
  try {
566
- const { Prompter } = await import("./prompter-9mn3kp36.js");
567
+ const { Prompter } = await import("./prompter-1f8d9pcc.js");
567
568
  return await Prompter.listGuidelines();
568
569
  } catch {
569
570
  return [];
@@ -576,9 +577,78 @@ class AkanToolPack2 {
576
577
  }
577
578
  }
578
579
 
579
- // pkgs/@akanjs/devkit/codeAgent/tools/MailToolPack.ts
580
+ // pkgs/@akanjs/devkit/codeAgent/tools/AskToolPack.ts
580
581
  import { Type as Type2 } from "typebox";
581
582
 
583
+ class AskToolPack {
584
+ static toolName = "ask_user";
585
+ static maxQuestions = 4;
586
+ #options;
587
+ constructor(options) {
588
+ this.#options = options;
589
+ }
590
+ names() {
591
+ return this.#options.profile.ui.canPrompt ? [AskToolPack.toolName] : [];
592
+ }
593
+ extension() {
594
+ if (!this.#options.profile.ui.canPrompt)
595
+ return;
596
+ return { name: "akan-ask", factory: (pi) => this.#register(pi) };
597
+ }
598
+ #register(pi) {
599
+ pi.registerTool({
600
+ name: AskToolPack.toolName,
601
+ label: "Ask",
602
+ description: "Ask the user to decide something you cannot decide from the code or the request. Use it when two readings of the task would lead to materially different work, not for choices with an obvious default \u2014 and never to ask permission for work you were already asked to do. Offer concrete options whenever you can name them; mark one recommended when you have a view.",
603
+ promptSnippet: "ask_user: put a decision the user owns back to them, with options",
604
+ parameters: Type2.Object({
605
+ questions: Type2.Array(Type2.Object({
606
+ question: Type2.String({ description: "The whole question, as one sentence ending in a question mark." }),
607
+ header: Type2.Optional(Type2.String({ description: "Two or three words naming what is being chosen, e.g. 'Auth method'." })),
608
+ options: Type2.Optional(Type2.Array(Type2.Object({
609
+ label: Type2.String({ description: "The choice, in one to five words." }),
610
+ detail: Type2.Optional(Type2.String({ description: "What picking it means, or the trade-off it carries." })),
611
+ recommended: Type2.Optional(Type2.Boolean({ description: "Mark at most one. It is offered first and labelled as such." }))
612
+ }), { description: "Two to four distinct choices. Leave it out to ask for an answer in prose." })),
613
+ multiSelect: Type2.Optional(Type2.Boolean({ description: "Several options may be chosen together, rather than exactly one." })),
614
+ freeText: Type2.Optional(Type2.Boolean({
615
+ description: "Offer a further choice that lets the user type an answer of their own. Default true."
616
+ }))
617
+ }), { description: `One to ${AskToolPack.maxQuestions} questions, asked in order.` })
618
+ }),
619
+ execute: async (_id, params) => {
620
+ const questions = (params.questions ?? []).slice(0, AskToolPack.maxQuestions);
621
+ if (!questions.length)
622
+ return { content: [{ type: "text", text: "No question was asked." }], details: undefined, isError: true };
623
+ const answers = [];
624
+ for (const entry of questions) {
625
+ const options = (entry.options ?? []).map((option, at) => ({
626
+ key: `${at}`,
627
+ label: option.label,
628
+ ...option.detail ? { detail: option.detail } : {},
629
+ ...option.recommended ? { recommended: true } : {}
630
+ }));
631
+ const answer = await this.#options.ask({
632
+ prompt: entry.header ? `${entry.header} \u2014 ${entry.question}` : entry.question,
633
+ kind: options.length ? "select" : "text",
634
+ ...options.length ? { options } : {},
635
+ ...entry.multiSelect ? { multiSelect: true } : {},
636
+ freeText: entry.freeText ?? true
637
+ });
638
+ answers.push(`${entry.question}
639
+ \u2192 ${answer ?? "(skipped \u2014 decide this yourself and say what you chose)"}`);
640
+ }
641
+ return { content: [{ type: "text", text: answers.join(`
642
+
643
+ `) }], details: undefined };
644
+ }
645
+ });
646
+ }
647
+ }
648
+
649
+ // pkgs/@akanjs/devkit/codeAgent/tools/MailToolPack.ts
650
+ import { Type as Type3 } from "typebox";
651
+
582
652
  class MailToolPack2 {
583
653
  static toolNames = ["list_peers", "send_peer_message"];
584
654
  #options;
@@ -599,7 +669,7 @@ class MailToolPack2 {
599
669
  label: "List peer sessions",
600
670
  description: "List the other akan code sessions running in this workspace right now, with their name and working directory. Call it before send_peer_message so you name a session that is actually there.",
601
671
  promptSnippet: "list_peers: the other akan code sessions running here",
602
- parameters: Type2.Object({}),
672
+ parameters: Type3.Object({}),
603
673
  execute: async () => ({
604
674
  content: [{ type: "text", text: MailToolPack2.#render(this.#options.mailbox()?.peers() ?? []) }],
605
675
  details: undefined
@@ -610,9 +680,9 @@ class MailToolPack2 {
610
680
  label: "Message a peer session",
611
681
  description: "Hand a message to another akan code session in this workspace. It arrives there as a prompt, so it costs that session a turn \u2014 use it to hand over a piece of work or to warn it about a change you made, not to chat. Say what you want done and what you already did.",
612
682
  promptSnippet: "send_peer_message: hand work to another akan code session",
613
- parameters: Type2.Object({
614
- to: Type2.String({ description: "The peer's name, or the front of its id, from list_peers." }),
615
- message: Type2.String({ description: "What that session should know or do." })
683
+ parameters: Type3.Object({
684
+ to: Type3.String({ description: "The peer's name, or the front of its id, from list_peers." }),
685
+ message: Type3.String({ description: "What that session should know or do." })
616
686
  }),
617
687
  execute: async (_id, params) => {
618
688
  const mailbox = this.#options.mailbox();
@@ -645,20 +715,32 @@ import {
645
715
  codeAgentClip,
646
716
  codeAgentOutputChars
647
717
  } from "akanjs/common";
648
- import { Type as Type3 } from "typebox";
718
+ import { Type as Type4 } from "typebox";
649
719
 
650
720
  // pkgs/@akanjs/devkit/codeAgent/tools/McpClient.ts
651
721
  var protocolVersion = "2025-06-18";
652
722
 
723
+ class McpUnauthorized2 extends Error {
724
+ challenge;
725
+ constructor(name, challenge) {
726
+ super(`MCP server "${name}" requires authentication`);
727
+ this.name = "McpUnauthorized";
728
+ this.challenge = challenge;
729
+ }
730
+ }
731
+
653
732
  class McpClient2 {
733
+ static maxToolPages = 50;
654
734
  ref;
655
735
  #proc;
656
736
  #nextId = 0;
657
737
  #buffer = "";
658
738
  #pending = new Map;
659
739
  #sessionId;
660
- constructor(ref) {
740
+ #token;
741
+ constructor(ref, token) {
661
742
  this.ref = ref;
743
+ this.#token = token;
662
744
  }
663
745
  async connect() {
664
746
  if (this.ref.transport === "stdio")
@@ -674,10 +756,19 @@ class McpClient2 {
674
756
  return this;
675
757
  }
676
758
  async listTools() {
677
- const response = await this.#call("tools/list", {});
678
- if (response.error)
679
- throw new Error(response.error.message);
680
- return response.result?.tools ?? [];
759
+ const tools = [];
760
+ let cursor;
761
+ for (let page = 0;page < McpClient2.maxToolPages; page += 1) {
762
+ const response = await this.#call("tools/list", cursor ? { cursor } : {});
763
+ if (response.error)
764
+ throw new Error(response.error.message);
765
+ tools.push(...response.result?.tools ?? []);
766
+ const next = response.result?.nextCursor;
767
+ if (!next || next === cursor)
768
+ return tools;
769
+ cursor = next;
770
+ }
771
+ return tools;
681
772
  }
682
773
  async callTool(name, args) {
683
774
  const response = await this.#call("tools/call", { name, arguments: args });
@@ -761,11 +852,16 @@ class McpClient2 {
761
852
  headers: {
762
853
  "content-type": "application/json",
763
854
  accept: "application/json, text/event-stream",
855
+ "mcp-protocol-version": protocolVersion,
856
+ ...this.ref.headers,
857
+ ...this.#token ? { authorization: `Bearer ${this.#token}` } : {},
764
858
  ...this.#sessionId ? { "mcp-session-id": this.#sessionId } : {}
765
859
  },
766
860
  body: JSON.stringify(body),
767
861
  signal: AbortSignal.timeout(30000)
768
862
  });
863
+ if (response.status === 401)
864
+ throw new McpUnauthorized2(this.ref.name, response.headers.get("www-authenticate") ?? "");
769
865
  this.#sessionId = response.headers.get("mcp-session-id") ?? this.#sessionId;
770
866
  const text = await response.text();
771
867
  if (!text.trim())
@@ -797,12 +893,22 @@ class McpToolPack2 {
797
893
  const pack = new McpToolPack2;
798
894
  for (const ref of refs) {
799
895
  const base = { name: ref.name, transport: ref.transport, target: McpServerConfig.targetOf(ref) };
896
+ const token = ref.transport === "http" ? await McpSignIn.token(ref, options.onNotice) : undefined;
800
897
  try {
801
- const client = await new McpClient2(ref).connect();
898
+ const client = await new McpClient2(ref, token).connect();
802
899
  const tools = await client.listTools();
803
900
  pack.#clients.push({ client, tools });
804
- pack.#status.push({ ...base, tools: tools.map((tool) => McpToolPack2.#nameOf(ref.name, tool.name)) });
901
+ pack.#status.push({
902
+ ...base,
903
+ tools: tools.map((tool) => McpToolPack2.#nameOf(ref.name, tool.name)),
904
+ auth: token ? "authorized" : "none"
905
+ });
805
906
  } catch (error) {
907
+ if (error instanceof McpUnauthorized2) {
908
+ options.onNotice?.(`MCP server "${ref.name}" needs signing in \u2014 /mcp login ${ref.name}`);
909
+ pack.#status.push({ ...base, tools: [], auth: "required" });
910
+ continue;
911
+ }
806
912
  options.onNotice?.(`MCP server "${ref.name}" is unavailable: ${String(error)}`);
807
913
  pack.#status.push({ ...base, tools: [], error: String(error) });
808
914
  }
@@ -832,7 +938,7 @@ class McpToolPack2 {
832
938
  label: `${client.ref.name}: ${tool.name}`,
833
939
  description,
834
940
  promptSnippet: `${name}: ${description.split(".")[0] ?? description}`,
835
- parameters: Type3.Unsafe(tool.inputSchema),
941
+ parameters: Type4.Unsafe(tool.inputSchema),
836
942
  execute: async (_id, params) => {
837
943
  const result = await client.callTool(tool.name, params ?? {});
838
944
  return {
@@ -858,7 +964,7 @@ class McpToolPack2 {
858
964
  // pkgs/@akanjs/devkit/codeAgent/tools/SessionToolPack.ts
859
965
  import { SessionManager } from "@earendil-works/pi-coding-agent";
860
966
  import { codeAgentClip as codeAgentClip2, codeAgentOutputChars as codeAgentOutputChars2 } from "akanjs/common";
861
- import { Type as Type4 } from "typebox";
967
+ import { Type as Type5 } from "typebox";
862
968
  var snippetChars = 240;
863
969
 
864
970
  class SessionToolPack2 {
@@ -881,9 +987,9 @@ class SessionToolPack2 {
881
987
  label: "Search sessions",
882
988
  description: "Search earlier coding-agent conversations in this workspace for a phrase, and get back the matching sessions with a snippet. Use it when the user refers to something decided before, or when you suspect this problem has been solved here already.",
883
989
  promptSnippet: "session_search: find earlier conversations in this workspace by phrase",
884
- parameters: Type4.Object({
885
- query: Type4.String({ description: "A phrase to look for, matched case-insensitively." }),
886
- limit: Type4.Optional(Type4.Number({ description: "How many sessions to return, default 5." }))
990
+ parameters: Type5.Object({
991
+ query: Type5.String({ description: "A phrase to look for, matched case-insensitively." }),
992
+ limit: Type5.Optional(Type5.Number({ description: "How many sessions to return, default 5." }))
887
993
  }),
888
994
  execute: async (_id, params) => {
889
995
  const matches = await this.#search(params.query, params.limit ?? 5);
@@ -895,7 +1001,7 @@ class SessionToolPack2 {
895
1001
  label: "Read session",
896
1002
  description: "Read one earlier conversation, found with session_search, as plain text.",
897
1003
  promptSnippet: "session_read: read one earlier conversation by id",
898
- parameters: Type4.Object({ sessionId: Type4.String() }),
1004
+ parameters: Type5.Object({ sessionId: Type5.String() }),
899
1005
  execute: async (_id, params) => {
900
1006
  const text = await this.#read(params.sessionId);
901
1007
  return { content: [{ type: "text", text }], details: undefined, isError: text.startsWith("No session") };
@@ -928,7 +1034,7 @@ class SessionToolPack2 {
928
1034
 
929
1035
  // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
930
1036
  import { codeAgentClip as codeAgentClip3, codeAgentOutputChars as codeAgentOutputChars3 } from "akanjs/common";
931
- import { Type as Type5 } from "typebox";
1037
+ import { Type as Type6 } from "typebox";
932
1038
 
933
1039
  class WebToolPack2 {
934
1040
  #options;
@@ -993,7 +1099,7 @@ class WebToolPack2 {
993
1099
  label: "Fetch",
994
1100
  description: "Fetch a URL and return its readable text. HTML is stripped to text; the result is truncated.",
995
1101
  promptSnippet: "web_fetch: fetch a URL and return its readable text",
996
- parameters: Type5.Object({ url: Type5.String({ description: "Absolute http(s) URL." }) }),
1102
+ parameters: Type6.Object({ url: Type6.String({ description: "Absolute http(s) URL." }) }),
997
1103
  execute: async (_id, params) => {
998
1104
  const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
999
1105
  if (refusal)
@@ -1018,9 +1124,9 @@ ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1018
1124
  label: "Search",
1019
1125
  description: `Search the web (${provider.name}) and return the top results as title, url and snippet.`,
1020
1126
  promptSnippet: "web_search: search the web and return titles, urls and snippets",
1021
- parameters: Type5.Object({
1022
- query: Type5.String(),
1023
- count: Type5.Optional(Type5.Number({ description: "How many results, default 5." }))
1127
+ parameters: Type6.Object({
1128
+ query: Type6.String(),
1129
+ count: Type6.Optional(Type6.Number({ description: "How many results, default 5." }))
1024
1130
  }),
1025
1131
  execute: async (_id, params) => {
1026
1132
  const key = process.env[provider.envKey] ?? "";
@@ -1076,10 +1182,19 @@ class AkanCodePlugins2 {
1076
1182
  const sessionExtension = sessions.extension();
1077
1183
  const mail = new MailToolPack2({ profile: options.profile, mailbox: options.mailbox });
1078
1184
  const mailExtension = mail.extension();
1185
+ const onAsk = options.onAsk;
1186
+ const ask = onAsk ? new AskToolPack({ profile: options.profile, ask: onAsk }) : undefined;
1187
+ const askExtension = ask?.extension();
1079
1188
  if (!options.profile.tools.akan)
1080
1189
  return {
1081
- extensions: [mcp?.extension(), webExtension, sessionExtension, mailExtension].filter((entry) => !!entry),
1082
- toolNames: [...mcp?.toolNames ?? [], ...web.names(), ...sessions.names(), ...mail.names()],
1190
+ extensions: [mcp?.extension(), webExtension, sessionExtension, mailExtension, askExtension].filter((entry) => !!entry),
1191
+ toolNames: [
1192
+ ...mcp?.toolNames ?? [],
1193
+ ...web.names(),
1194
+ ...sessions.names(),
1195
+ ...mail.names(),
1196
+ ...ask?.names() ?? []
1197
+ ],
1083
1198
  mcp: mcpStatus,
1084
1199
  dispose
1085
1200
  };
@@ -1114,6 +1229,8 @@ class AkanCodePlugins2 {
1114
1229
  extensions.push(sessionExtension);
1115
1230
  if (mailExtension)
1116
1231
  extensions.push(mailExtension);
1232
+ if (askExtension)
1233
+ extensions.push(askExtension);
1117
1234
  return {
1118
1235
  extensions,
1119
1236
  toolNames: [
@@ -1122,7 +1239,8 @@ class AkanCodePlugins2 {
1122
1239
  ...mcp?.toolNames ?? [],
1123
1240
  ...web.names(),
1124
1241
  ...sessions.names(),
1125
- ...mail.names()
1242
+ ...mail.names(),
1243
+ ...ask?.names() ?? []
1126
1244
  ],
1127
1245
  mcp: mcpStatus,
1128
1246
  dispose
@@ -1154,7 +1272,9 @@ class AkanCodePlugins2 {
1154
1272
  }
1155
1273
 
1156
1274
  // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1157
- import { existsSync as existsSync7, mkdirSync } from "fs";
1275
+ import { existsSync as existsSync7, mkdirSync, mkdtempSync } from "fs";
1276
+ import { tmpdir } from "os";
1277
+ import path7 from "path";
1158
1278
  import { ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
1159
1279
 
1160
1280
  // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
@@ -1281,6 +1401,7 @@ var akanSystemPrompt2 = (profile) => {
1281
1401
  "- Prefer an akan workflow to a direct source edit. `list_workflows` / `explain_workflow` / `plan_workflow`, then `apply_workflow({ planPath })`. Editing source by hand is the fallback, not the default.",
1282
1402
  "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
1283
1403
  "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
1404
+ "- A question about `akan code` itself \u2014 its slash commands, MCP servers, sessions, models, keys, or where its files live \u2014 is answered by the `akan-code` skill. Read it rather than describing the tool from memory.",
1284
1405
  "- Never hand-edit a generated file (`lib/cnst.ts`, `db.ts`, `st.ts`, any `*/index.ts` barrel). Fix the owning source and run `repair_generated` or `akan sync`."
1285
1406
  ];
1286
1407
  if (profile.tools.builtin.includes("write"))
@@ -1289,6 +1410,8 @@ var akanSystemPrompt2 = (profile) => {
1289
1410
  lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
1290
1411
  if (profile.tools.subagent)
1291
1412
  lines.push('- Hand a self-contained search or read to `task` \u2014 `type: "explore"` reads and reports, `type: "code"` makes the change. Only its answer comes back, so give it everything it needs and ask for exactly what you want returned. Do it yourself when the reading is short or when you need the files in front of you.');
1413
+ if (profile.ui.canPrompt)
1414
+ lines.push("- Ask with `ask_user` when two readings of the request would lead to materially different work, and offer the options you can name. Do not ask for a choice that has an obvious default, and do not ask permission for work you were already asked to do \u2014 make the call, say the assumption, and carry on.");
1292
1415
  if (profile.approval !== "never")
1293
1416
  lines.push(`- Some tool calls need the user's approval (policy: ${profile.approval}). A refusal is an answer; do not retry it a different way.`);
1294
1417
  lines.push("- Secrets are refused at the tool boundary. If you need a value from `.env`, ask the user for it rather than looking for another way to read the file.", "", "## How to answer", "", "- Answer in as few words as carry the answer. A short question gets one sentence with no heading.", "- Do not open with what you are about to do, and do not close with a summary of what you did. The tool calls are already on the user's screen.", "- Skip headings, bold labels and bullet lists unless the answer is genuinely a list. Prose is shorter.", "- State a file as `path:line`. Do not paste a file back that the user can open.", "- Say what you could not do, and why, in one line. Do not offer alternatives nobody asked for.");
@@ -1374,11 +1497,20 @@ class AkanCodeServices2 {
1374
1497
  return SettingsManager.inMemory({
1375
1498
  enableInstallTelemetry: false,
1376
1499
  enableAnalytics: false,
1377
- quietStartup: true
1500
+ quietStartup: true,
1501
+ compaction: { reserveTokens: 32768 }
1378
1502
  });
1379
1503
  }
1380
- static async runtime(workspaceRoot) {
1504
+ static async runtime(workspaceRoot, proxy = null) {
1381
1505
  mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
1506
+ if (proxy) {
1507
+ const runtime = await ModelRuntime.create({
1508
+ authPath: path7.join(mkdtempSync(path7.join(tmpdir(), "akan-code-proxy-")), "auth.json"),
1509
+ modelsPath: akanCodePaths2.modelsFile()
1510
+ });
1511
+ await proxy.apply(runtime);
1512
+ return runtime;
1513
+ }
1382
1514
  const runtime = await ModelRuntime.create({
1383
1515
  authPath: akanCodePaths2.authFile(),
1384
1516
  modelsPath: akanCodePaths2.modelsFile()
@@ -1387,7 +1519,7 @@ class AkanCodeServices2 {
1387
1519
  return runtime;
1388
1520
  }
1389
1521
  static sessions(workspaceRoot, cwd, profile, resume) {
1390
- if (profile.session.store !== "file")
1522
+ if (profile.session.store === "memory")
1391
1523
  return SessionManager2.inMemory(cwd);
1392
1524
  const dir = akanCodePaths2.sessionsDir(workspaceRoot);
1393
1525
  mkdirSync(dir, { recursive: true });
@@ -1403,8 +1535,12 @@ class AkanCodeServices2 {
1403
1535
  static #skillPaths(profile, workspaceRoot) {
1404
1536
  if (!profile.context.skills)
1405
1537
  return [];
1406
- const workspace = akanCodePaths2.skillsDir(workspaceRoot);
1407
- return [akanCodePaths2.builtinSkillsDir(), existsSync7(workspace) ? workspace : undefined].filter((dir) => !!dir);
1538
+ const dirs = [
1539
+ akanCodePaths2.builtinSkillsDir(),
1540
+ akanCodePaths2.globalSkillsDir(),
1541
+ akanCodePaths2.skillsDir(workspaceRoot)
1542
+ ];
1543
+ return dirs.filter((dir) => !!dir && existsSync7(dir));
1408
1544
  }
1409
1545
  static resourceOptions(options) {
1410
1546
  const present = (dir) => existsSync7(dir) ? [dir] : [];
@@ -1430,6 +1566,7 @@ class CodeAgentAsks2 {
1430
1566
  #question;
1431
1567
  #approvals = new Map;
1432
1568
  #nextId = 0;
1569
+ #epoch = Date.now().toString(36);
1433
1570
  get pendingQuestionId() {
1434
1571
  return this.#question?.question.questionId;
1435
1572
  }
@@ -1441,7 +1578,7 @@ class CodeAgentAsks2 {
1441
1578
  }
1442
1579
  nextId(prefix) {
1443
1580
  this.#nextId += 1;
1444
- return `${prefix}${this.#nextId}`;
1581
+ return `${prefix}${this.#epoch}.${this.#nextId}`;
1445
1582
  }
1446
1583
  openQuestion(question) {
1447
1584
  this.#question?.deferred.resolve("");
@@ -1550,7 +1687,15 @@ class CodeAgentEventMapper2 {
1550
1687
  case "compaction_start":
1551
1688
  return [{ type: "compaction", phase: "start", reason: event.reason }];
1552
1689
  case "compaction_end":
1553
- return [{ type: "compaction", phase: "end", reason: event.reason }];
1690
+ return [
1691
+ {
1692
+ type: "compaction",
1693
+ phase: "end",
1694
+ reason: event.reason,
1695
+ ...event.errorMessage ? { error: event.errorMessage } : {},
1696
+ ...event.aborted ? { aborted: true } : {}
1697
+ }
1698
+ ];
1554
1699
  case "auto_retry_start":
1555
1700
  return [
1556
1701
  {
@@ -1694,7 +1839,7 @@ class CodeAgentEventMapper2 {
1694
1839
  }
1695
1840
 
1696
1841
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentGate.ts
1697
- import path7 from "path";
1842
+ import path8 from "path";
1698
1843
  var writeTools = new Set(["write", "edit"]);
1699
1844
  var commandTools = new Set(["bash"]);
1700
1845
 
@@ -1733,9 +1878,9 @@ class CodeAgentGate2 {
1733
1878
  return `The command names "${hit}", which the profile's deny list covers. Ask the user for the value instead of reading the file.`;
1734
1879
  }
1735
1880
  #deniedReason(target) {
1736
- const absolute = path7.isAbsolute(target) ? target : path7.resolve(this.#profile.paths.root, target);
1737
- const relative = path7.relative(this.#profile.paths.root, absolute);
1738
- if (relative.startsWith("..") || path7.isAbsolute(relative))
1881
+ const absolute = path8.isAbsolute(target) ? target : path8.resolve(this.#profile.paths.root, target);
1882
+ const relative = path8.relative(this.#profile.paths.root, absolute);
1883
+ if (relative.startsWith("..") || path8.isAbsolute(relative))
1739
1884
  return `Path is outside the agent root (${this.#profile.paths.root}).`;
1740
1885
  if (this.#deny.some((glob) => glob.match(absolute) || glob.match(relative)))
1741
1886
  return "Path is on the profile's deny list (secrets and environment files are never readable).";
@@ -1782,6 +1927,137 @@ class CodeAgentGate2 {
1782
1927
  }
1783
1928
  }
1784
1929
 
1930
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentProxy.ts
1931
+ import { readFile } from "fs/promises";
1932
+ class CodeAgentProxy {
1933
+ static placeholderKey = "akan-code-proxy";
1934
+ baseUrl;
1935
+ providers;
1936
+ #tokenFile;
1937
+ #token;
1938
+ constructor({ baseUrl, providers, tokenFile, token }) {
1939
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
1940
+ this.providers = providers;
1941
+ this.#tokenFile = tokenFile;
1942
+ this.#token = token;
1943
+ if (!tokenFile && !token)
1944
+ throw new Error("AKAN_CODE_PROXY_URL needs AKAN_CODE_PROXY_TOKEN_FILE or AKAN_CODE_PROXY_TOKEN");
1945
+ }
1946
+ static fromEnv(profile) {
1947
+ const baseUrl = process.env.AKAN_CODE_PROXY_URL?.trim() || profile?.network.proxyBaseUrl;
1948
+ if (!baseUrl)
1949
+ return null;
1950
+ const providers = (process.env.AKAN_CODE_PROXY_PROVIDERS ?? akanCodeDefaultModel2.provider).split(",").map((provider) => provider.trim()).filter(Boolean);
1951
+ return new CodeAgentProxy({
1952
+ baseUrl,
1953
+ providers,
1954
+ tokenFile: process.env.AKAN_CODE_PROXY_TOKEN_FILE?.trim() || undefined,
1955
+ token: process.env.AKAN_CODE_PROXY_TOKEN?.trim() || undefined
1956
+ });
1957
+ }
1958
+ baseUrlFor(provider) {
1959
+ return this.baseUrl.replaceAll("{provider}", provider);
1960
+ }
1961
+ async token() {
1962
+ if (!this.#tokenFile)
1963
+ return this.#token ?? "";
1964
+ const token = (await readFile(this.#tokenFile, "utf8")).trim();
1965
+ if (!token)
1966
+ throw new Error(`${this.#tokenFile} is empty`);
1967
+ return token;
1968
+ }
1969
+ async apply(runtime) {
1970
+ const target = runtime;
1971
+ const original = target.prepareRequest?.bind(runtime);
1972
+ if (!original)
1973
+ throw new Error("The model runtime no longer exposes prepareRequest; update CodeAgentProxy");
1974
+ for (const provider of this.providers)
1975
+ await runtime.setRuntimeApiKey(provider, CodeAgentProxy.placeholderKey);
1976
+ target.prepareRequest = async (model, options = {}) => {
1977
+ const apiKey = await this.token();
1978
+ const prepared = await original(model, { ...options, apiKey });
1979
+ return {
1980
+ ...prepared,
1981
+ model: { ...prepared.model, baseUrl: this.baseUrlFor(prepared.model.provider) },
1982
+ options: { ...prepared.options, apiKey }
1983
+ };
1984
+ };
1985
+ }
1986
+ }
1987
+
1988
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentSuspended.ts
1989
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
1990
+ import path9 from "path";
1991
+
1992
+ class CodeAgentSuspended {
1993
+ #file;
1994
+ #state;
1995
+ constructor(file) {
1996
+ this.#file = file;
1997
+ this.#state = CodeAgentSuspended.#read(file);
1998
+ }
1999
+ static fileOf(sessionDir, sessionId) {
2000
+ return sessionDir ? path9.join(sessionDir, `${sessionId}.suspended.json`) : null;
2001
+ }
2002
+ get question() {
2003
+ return this.#state.question;
2004
+ }
2005
+ get approvals() {
2006
+ return this.#state.approvals;
2007
+ }
2008
+ setQuestion(question) {
2009
+ this.#state = { ...this.#state, question };
2010
+ this.#save();
2011
+ }
2012
+ addApproval(request) {
2013
+ this.#state = { ...this.#state, approvals: [...this.#state.approvals, request] };
2014
+ this.#save();
2015
+ }
2016
+ takeQuestion(questionId) {
2017
+ const question = this.#state.question;
2018
+ if (question?.questionId !== questionId)
2019
+ return;
2020
+ this.#state = { approvals: this.#state.approvals };
2021
+ this.#save();
2022
+ return question;
2023
+ }
2024
+ takeApproval(approvalId) {
2025
+ const request = this.#state.approvals.find((approval) => approval.approvalId === approvalId);
2026
+ if (!request)
2027
+ return;
2028
+ this.#state = { ...this.#state, approvals: this.#state.approvals.filter((approval) => approval !== request) };
2029
+ this.#save();
2030
+ return request;
2031
+ }
2032
+ clear() {
2033
+ if (!this.#state.question && !this.#state.approvals.length)
2034
+ return;
2035
+ this.#state = { approvals: [] };
2036
+ this.#save();
2037
+ }
2038
+ #save() {
2039
+ if (!this.#file)
2040
+ return;
2041
+ if (!this.#state.question && !this.#state.approvals.length) {
2042
+ rmSync(this.#file, { force: true });
2043
+ return;
2044
+ }
2045
+ mkdirSync2(path9.dirname(this.#file), { recursive: true });
2046
+ writeFileSync(this.#file, `${JSON.stringify(this.#state)}
2047
+ `);
2048
+ }
2049
+ static #read(file) {
2050
+ if (!file || !existsSync8(file))
2051
+ return { approvals: [] };
2052
+ try {
2053
+ const parsed = JSON.parse(readFileSync4(file, "utf8"));
2054
+ return { ...parsed.question ? { question: parsed.question } : {}, approvals: parsed.approvals ?? [] };
2055
+ } catch {
2056
+ return { approvals: [] };
2057
+ }
2058
+ }
2059
+ }
2060
+
1785
2061
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentUi.ts
1786
2062
  class CodeAgentUi2 {
1787
2063
  #handlers;
@@ -1825,15 +2101,15 @@ class CodeAgentUi2 {
1825
2101
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeMailbox.ts
1826
2102
  import {
1827
2103
  appendFileSync,
1828
- existsSync as existsSync8,
1829
- mkdirSync as mkdirSync2,
2104
+ existsSync as existsSync9,
2105
+ mkdirSync as mkdirSync3,
1830
2106
  readdirSync as readdirSync3,
1831
- readFileSync as readFileSync4,
1832
- rmSync,
2107
+ readFileSync as readFileSync5,
2108
+ rmSync as rmSync2,
1833
2109
  statSync as statSync4,
1834
- writeFileSync
2110
+ writeFileSync as writeFileSync2
1835
2111
  } from "fs";
1836
- import path8 from "path";
2112
+ import path10 from "path";
1837
2113
 
1838
2114
  class CodeMailbox2 {
1839
2115
  static staleMs = 20000;
@@ -1853,11 +2129,11 @@ class CodeMailbox2 {
1853
2129
  this.#name = name;
1854
2130
  }
1855
2131
  get inbox() {
1856
- return path8.join(this.#dir, "inbox", `${this.#id}.jsonl`);
2132
+ return path10.join(this.#dir, "inbox", `${this.#id}.jsonl`);
1857
2133
  }
1858
2134
  open(cwd, onMail) {
1859
- mkdirSync2(path8.join(this.#dir, "live"), { recursive: true });
1860
- mkdirSync2(path8.join(this.#dir, "inbox"), { recursive: true });
2135
+ mkdirSync3(path10.join(this.#dir, "live"), { recursive: true });
2136
+ mkdirSync3(path10.join(this.#dir, "inbox"), { recursive: true });
1861
2137
  this.#read = CodeMailbox2.#lines(this.inbox).length;
1862
2138
  this.beat(cwd);
1863
2139
  this.#beat = setInterval(() => this.beat(cwd), CodeMailbox2.beatMs);
@@ -1874,7 +2150,7 @@ class CodeMailbox2 {
1874
2150
  this.#beat = null;
1875
2151
  this.#poll = null;
1876
2152
  try {
1877
- rmSync(path8.join(this.#dir, "live", `${this.#id}.json`), { force: true });
2153
+ rmSync2(path10.join(this.#dir, "live", `${this.#id}.json`), { force: true });
1878
2154
  } catch {}
1879
2155
  }
1880
2156
  rename(name, cwd) {
@@ -1884,15 +2160,15 @@ class CodeMailbox2 {
1884
2160
  beat(cwd) {
1885
2161
  const peer = { id: this.#id, name: this.#name, cwd, pid: process.pid, at: Date.now() };
1886
2162
  try {
1887
- writeFileSync(path8.join(this.#dir, "live", `${this.#id}.json`), JSON.stringify(peer));
2163
+ writeFileSync2(path10.join(this.#dir, "live", `${this.#id}.json`), JSON.stringify(peer));
1888
2164
  } catch {}
1889
2165
  }
1890
2166
  peers() {
1891
- const dir = path8.join(this.#dir, "live");
1892
- if (!existsSync8(dir))
2167
+ const dir = path10.join(this.#dir, "live");
2168
+ if (!existsSync9(dir))
1893
2169
  return [];
1894
2170
  const now = Date.now();
1895
- return readdirSync3(dir).filter((entry) => entry.endsWith(".json")).map((entry) => CodeMailbox2.#peer(path8.join(dir, entry))).filter((peer) => !!peer && peer.id !== this.#id && now - peer.at < CodeMailbox2.staleMs).sort((left, right) => right.at - left.at);
2171
+ return readdirSync3(dir).filter((entry) => entry.endsWith(".json")).map((entry) => CodeMailbox2.#peer(path10.join(dir, entry))).filter((peer) => !!peer && peer.id !== this.#id && now - peer.at < CodeMailbox2.staleMs).sort((left, right) => right.at - left.at);
1896
2172
  }
1897
2173
  find(target) {
1898
2174
  const needle = target.trim().toLowerCase();
@@ -1910,8 +2186,8 @@ class CodeMailbox2 {
1910
2186
  throw new Error(`Too many messages: ${CodeMailbox2.sendsPerMinute} a minute is the ceiling.`);
1911
2187
  this.#sent.push(now);
1912
2188
  const mail = { at: now, from: this.#id, fromName: this.#name, text };
1913
- mkdirSync2(path8.join(this.#dir, "inbox"), { recursive: true });
1914
- appendFileSync(path8.join(this.#dir, "inbox", `${to}.jsonl`), `${JSON.stringify(mail)}
2189
+ mkdirSync3(path10.join(this.#dir, "inbox"), { recursive: true });
2190
+ appendFileSync(path10.join(this.#dir, "inbox", `${to}.jsonl`), `${JSON.stringify(mail)}
1915
2191
  `);
1916
2192
  }
1917
2193
  #drain() {
@@ -1924,7 +2200,7 @@ class CodeMailbox2 {
1924
2200
  }
1925
2201
  static #peer(file) {
1926
2202
  try {
1927
- const peer = JSON.parse(readFileSync4(file, "utf8"));
2203
+ const peer = JSON.parse(readFileSync5(file, "utf8"));
1928
2204
  return typeof peer.id === "string" ? { ...peer, at: peer.at || statSync4(file).mtimeMs } : undefined;
1929
2205
  } catch {
1930
2206
  return;
@@ -1932,7 +2208,7 @@ class CodeMailbox2 {
1932
2208
  }
1933
2209
  static #lines(file) {
1934
2210
  try {
1935
- return readFileSync4(file, "utf8").split(`
2211
+ return readFileSync5(file, "utf8").split(`
1936
2212
  `).filter((line) => !!line.trim());
1937
2213
  } catch {
1938
2214
  return [];
@@ -1949,15 +2225,15 @@ class CodeMailbox2 {
1949
2225
 
1950
2226
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionFork.ts
1951
2227
  import { randomBytes } from "crypto";
1952
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1953
- import path9 from "path";
2228
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
2229
+ import path11 from "path";
1954
2230
  import { codeAgentSessionName } from "akanjs/common";
1955
2231
  class CodeSessionFork2 {
1956
2232
  static fork(dir, id, name) {
1957
2233
  const source = CodeSessionIndex2.fileOf(dir, id);
1958
2234
  if (!source)
1959
2235
  throw new Error(`No stored session ${id} in ${dir}`);
1960
- const lines = readFileSync5(source, "utf8").split(`
2236
+ const lines = readFileSync6(source, "utf8").split(`
1961
2237
  `).filter((line) => !!line.trim());
1962
2238
  if (!lines.length)
1963
2239
  throw new Error(`Session ${id} has nothing in it to fork`);
@@ -1970,8 +2246,8 @@ class CodeSessionFork2 {
1970
2246
  first.id = forked;
1971
2247
  first.timestamp = at.toISOString();
1972
2248
  records.push(CodeSessionFork2.#named(records, name ?? CodeSessionFork2.#nameOf(records), at));
1973
- const file = path9.join(dir, `${at.toISOString().replace(/[:.]/g, "-")}_${forked}.jsonl`);
1974
- writeFileSync2(file, `${records.map((record) => JSON.stringify(record)).join(`
2249
+ const file = path11.join(dir, `${at.toISOString().replace(/[:.]/g, "-")}_${forked}.jsonl`);
2250
+ writeFileSync3(file, `${records.map((record) => JSON.stringify(record)).join(`
1975
2251
  `)}
1976
2252
  `);
1977
2253
  return { id: forked, file };
@@ -2006,6 +2282,7 @@ class CodeAgent2 {
2006
2282
  #gate;
2007
2283
  #mapper = new CodeAgentEventMapper2;
2008
2284
  #asks = new CodeAgentAsks2;
2285
+ #suspended = new CodeAgentSuspended(null);
2009
2286
  #listeners = new Set;
2010
2287
  #session;
2011
2288
  #disposePlugins;
@@ -2027,9 +2304,9 @@ class CodeAgent2 {
2027
2304
  const cwd = options.cwd ?? workspaceRoot;
2028
2305
  const agent = new CodeAgent2(options.profile);
2029
2306
  const settingsManager = AkanCodeServices2.settings();
2030
- const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot);
2307
+ const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot, CodeAgentProxy.fromEnv(options.profile));
2031
2308
  const model = await akanCodeModel2(modelRuntime, options.model);
2032
- const compaction = DEFAULT_COMPACTION_SETTINGS;
2309
+ const compaction = settingsManager.getCompactionSettings(model);
2033
2310
  const modelWarnings = akanCodeModelWarnings2(model, compaction.reserveTokens + compaction.keepRecentTokens);
2034
2311
  const akan = await AkanCodePlugins2.build({
2035
2312
  workspace: options.workspace,
@@ -2041,7 +2318,8 @@ class CodeAgent2 {
2041
2318
  currentSessionId: () => agent.sessionId,
2042
2319
  mailbox: () => agent.#mailbox,
2043
2320
  onNotice: (message) => agent.#emit({ type: "notice", level: "warning", message }),
2044
- onAgents: (agents) => agent.#emit({ type: "subagent", agents })
2321
+ onAgents: (agents) => agent.#emit({ type: "subagent", agents }),
2322
+ onAsk: (question) => agent.ask(question)
2045
2323
  });
2046
2324
  const resourceLoader = new DefaultResourceLoader({
2047
2325
  cwd,
@@ -2070,10 +2348,11 @@ class CodeAgent2 {
2070
2348
  agent.#disposePlugins = akan.dispose;
2071
2349
  agent.#mcp = akan.mcp;
2072
2350
  agent.#workspaceRoot = workspaceRoot;
2073
- if (options.profile.session.store === "file")
2351
+ if (options.profile.session.store !== "memory")
2074
2352
  agent.#sessionDir = akanCodePaths2.sessionsDir(workspaceRoot);
2075
2353
  await agent.#attach(session, options.mode ?? "print");
2076
- if (agent.#sessionDir)
2354
+ agent.#suspended = new CodeAgentSuspended(CodeAgentSuspended.fileOf(agent.#sessionDir, agent.sessionId));
2355
+ if (agent.#sessionDir && options.profile.session.store === "file")
2077
2356
  agent.#openMail(akanCodePaths2.mailDir(workspaceRoot), cwd);
2078
2357
  for (const message of modelWarnings)
2079
2358
  agent.#emit({ type: "notice", level: "warning", message });
@@ -2147,6 +2426,11 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2147
2426
  announce() {
2148
2427
  this.#emit({ type: "session", info: this.info });
2149
2428
  this.#restore();
2429
+ const { question, approvals } = this.#suspended;
2430
+ if (question)
2431
+ this.#emit({ type: "question", question });
2432
+ for (const request of approvals)
2433
+ this.#emit({ type: "approval", request });
2150
2434
  }
2151
2435
  #restore() {
2152
2436
  const session = this.#session;
@@ -2160,6 +2444,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2160
2444
  if (text)
2161
2445
  this.#emit({ type: "message", turnId, role: message.role, text });
2162
2446
  }
2447
+ this.#emitContextNow();
2163
2448
  }
2164
2449
  static #textOf(content) {
2165
2450
  if (typeof content === "string")
@@ -2203,8 +2488,12 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2203
2488
  }
2204
2489
  if (this.#profile.interaction.question !== "suspend")
2205
2490
  return false;
2206
- this.#emit({ type: "question_resolved", questionId, answer, rendered });
2207
- await this.prompt(`The user answered: ${rendered}
2491
+ const suspended = this.#suspended.takeQuestion(questionId);
2492
+ if (!suspended)
2493
+ return false;
2494
+ const suspendedRendered = codeAgentRenderAnswer(suspended, answer);
2495
+ this.#emit({ type: "question_resolved", questionId, answer, rendered: suspendedRendered });
2496
+ await this.prompt(`The user answered: ${suspendedRendered}
2208
2497
  Continue the task.`);
2209
2498
  return true;
2210
2499
  }
@@ -2215,6 +2504,8 @@ Continue the task.`);
2215
2504
  }
2216
2505
  if (this.#profile.interaction.approval !== "suspend")
2217
2506
  return false;
2507
+ if (!this.#suspended.takeApproval(approvalId))
2508
+ return false;
2218
2509
  this.#emit({ type: "approval_resolved", approvalId, approved });
2219
2510
  if (approved)
2220
2511
  await this.prompt("The user approved the pending action. Retry it and continue.");
@@ -2249,6 +2540,23 @@ Continue the task.`);
2249
2540
  session.setThinkingLevel(effort);
2250
2541
  this.#emit({ type: "session", info: this.info });
2251
2542
  }
2543
+ catalogue() {
2544
+ const runtime = this.#session?.modelRuntime;
2545
+ if (!runtime)
2546
+ return [];
2547
+ const current = this.#session?.model;
2548
+ return runtime.getProviders().map((provider) => ({
2549
+ id: provider.id,
2550
+ name: provider.name ?? provider.id,
2551
+ authorized: runtime.hasConfiguredAuth(provider.id),
2552
+ models: (runtime.getModels(provider.id) ?? []).map((model) => ({
2553
+ id: model.id,
2554
+ name: model.name,
2555
+ ...model.contextWindow ? { contextWindow: model.contextWindow } : {},
2556
+ current: model.provider === current?.provider && model.id === current?.id
2557
+ }))
2558
+ })).sort((a, b) => a.id.localeCompare(b.id));
2559
+ }
2252
2560
  async setModel(ref) {
2253
2561
  const session = this.#require();
2254
2562
  const model = await akanCodeModel2(session.modelRuntime, ref);
@@ -2301,6 +2609,9 @@ Continue the task.`);
2301
2609
  #emitContextUsage(eventType) {
2302
2610
  if (eventType !== "agent_end" && eventType !== "compaction_end")
2303
2611
  return;
2612
+ this.#emitContextNow();
2613
+ }
2614
+ #emitContextNow() {
2304
2615
  const usage = this.#session?.getContextUsage();
2305
2616
  if (usage?.tokens == null)
2306
2617
  return;
@@ -2360,45 +2671,46 @@ Continue the task.`);
2360
2671
  if (!this.#profile.ui.canPrompt)
2361
2672
  return true;
2362
2673
  const approvalId = this.#asks.nextId("a");
2363
- this.#emit({
2364
- type: "approval",
2365
- request: {
2366
- approvalId,
2367
- toolCallId,
2368
- name,
2369
- summary: codeAgentClip5(summary, codeAgentLabelChars2),
2370
- policy: this.#profile.approval
2371
- }
2372
- });
2674
+ const request = {
2675
+ approvalId,
2676
+ toolCallId,
2677
+ name,
2678
+ summary: codeAgentClip5(summary, codeAgentLabelChars2),
2679
+ policy: this.#profile.approval
2680
+ };
2681
+ this.#emit({ type: "approval", request });
2373
2682
  if (this.#profile.interaction.approval === "suspend") {
2683
+ this.#suspended.addApproval(request);
2374
2684
  this.#mapper.noteOutcome("awaiting");
2375
2685
  return false;
2376
2686
  }
2377
2687
  return await this.#asks.openApproval(approvalId);
2378
2688
  }
2689
+ async ask(spec) {
2690
+ const question = { questionId: this.#asks.nextId("q"), ...spec };
2691
+ if (!this.#profile.ui.canPrompt) {
2692
+ this.#emit({ type: "question_skipped", question, reason: "no-host" });
2693
+ return;
2694
+ }
2695
+ this.#emit({ type: "question", question });
2696
+ if (this.#profile.interaction.question === "suspend") {
2697
+ this.#suspended.setQuestion(question);
2698
+ this.#mapper.noteOutcome("awaiting");
2699
+ return;
2700
+ }
2701
+ return await this.#asks.openQuestion(question) || undefined;
2702
+ }
2379
2703
  #ui() {
2380
2704
  return new CodeAgentUi2({
2381
- ask: async (prompt, kind, choices) => {
2382
- if (!this.#profile.ui.canPrompt)
2383
- return;
2384
- const questionId = this.#asks.nextId("q");
2385
- const question = {
2386
- questionId,
2387
- prompt,
2388
- kind,
2389
- ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2390
- freeText: kind === "text"
2391
- };
2392
- this.#emit({ type: "question", question });
2393
- if (this.#profile.interaction.question === "suspend") {
2394
- this.#mapper.noteOutcome("awaiting");
2395
- return;
2396
- }
2397
- return await this.#asks.openQuestion(question) || undefined;
2398
- },
2705
+ ask: async (prompt, kind, choices) => await this.ask({
2706
+ prompt,
2707
+ kind,
2708
+ ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2709
+ freeText: kind === "text"
2710
+ }),
2399
2711
  notify: (level, message) => this.#emit({ type: "notice", level, message })
2400
2712
  });
2401
2713
  }
2402
2714
  }
2403
2715
 
2404
- export { AkanEditScope2, DevLogFeedback2, PreviewChrome2, PreviewView2, PreviewFeedback2, TurnFeedback2, AkanCli2, AkanVerifier2, VerifyFeedback2, AkanToolPack2, MailToolPack2, McpClient2, McpToolPack2, SessionToolPack2, WebToolPack2, AkanCodePlugins2, AkanEnvKeys2, akanSystemPrompt2, CodeSessionIndex2, AkanCodeServices2, CodeAgentAsks2, CodeAgentEventMapper2, CodeAgentGate2, CodeAgentUi2, CodeMailbox2, CodeSessionFork2, CodeAgent2 };
2716
+ export { AkanEditScope2, DevLogFeedback2, PreviewChrome2, PreviewView2, PreviewFeedback2, TurnFeedback2, AkanCli2, AkanVerifier2, VerifyFeedback2, AkanToolPack2, MailToolPack2, McpUnauthorized2, McpClient2, McpToolPack2, SessionToolPack2, WebToolPack2, AkanCodePlugins2, AkanEnvKeys2, akanSystemPrompt2, CodeSessionIndex2, AkanCodeServices2, CodeAgentAsks2, CodeAgentEventMapper2, CodeAgentGate2, CodeAgentUi2, CodeMailbox2, CodeSessionFork2, CodeAgent2 };