@akanjs/cli 3.0.0-beta.16 → 3.0.0-beta.18

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 (134) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-zgccwajm.js → CodeAgent-0035exh3.js} +2 -2
  3. package/{CodeTui-993tx7gf.js → CodeTui-eweg1hep.js} +384 -28
  4. package/{agent.command-n664dtcv.js → agent.command-65zqfkg0.js} +6 -6
  5. package/akanCode.js +11 -9
  6. package/{application.command-t5542jte.js → application.command-2wcj5da3.js} +18 -11
  7. package/{applicationBuildRunner-x4gbfn7y.js → applicationBuildRunner-m0hhgwsx.js} +6 -5
  8. package/{applicationReleasePackager-z61ep017.js → applicationReleasePackager-vykfzgx3.js} +1 -1
  9. package/buildBatch.proc.js +2 -2
  10. package/{capacitorApp-shv3ajh5.js → capacitorApp-2daqhb5h.js} +2 -2
  11. package/{cloud.command-4x35k1z6.js → cloud.command-960bgbf4.js} +8 -8
  12. package/{cloud.script-3k8nbbpn.js → cloud.script-qhcnqt1n.js} +8 -8
  13. package/{code.command-mrfb7a1g.js → code.command-4feh3ef7.js} +7 -7
  14. package/{context.command-pqsm5jrk.js → context.command-kzehkx48.js} +14 -14
  15. package/context.runner-z0rrcz7b.js +22 -0
  16. package/{devTui-gy6mendg.js → devTui-ffjsarqa.js} +3 -3
  17. package/{devTuiView-y1548wy6.js → devTuiView-1q1mdgrv.js} +1 -1
  18. package/{guideline.command-rpc2emw8.js → guideline.command-3w2g20x4.js} +8 -13
  19. package/guidelines/conventions/conventions.instruction.md +37 -22
  20. package/guidelines/queryRule/queryRule.instruction.md +1 -1
  21. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  22. package/guidelines/transportRule/transportRule.instruction.md +10 -8
  23. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +4 -3
  24. package/incrementalBuilder.proc.js +1 -1
  25. package/{index-sxg87a8f.js → index-14zrtgfa.js} +164 -98
  26. package/{index-57p9p179.js → index-3hpxeys1.js} +6 -5
  27. package/{index-sn7dnzq4.js → index-4r42ext5.js} +1 -1
  28. package/{index-3mg4h788.js → index-6q589y9t.js} +2 -2
  29. package/index-9dp031by.js +12 -0
  30. package/{index-p4p9t45x.js → index-bwxpk2cj.js} +4 -18
  31. package/{index-g9zghs4h.js → index-cjw1sj1x.js} +12 -12
  32. package/{index-te0y1c0m.js → index-d9fjz06h.js} +12 -7
  33. package/{index-4n4821gp.js → index-dw943k93.js} +4 -4
  34. package/{index-4cvpzkq6.js → index-ef228gzf.js} +3 -3
  35. package/{index-3cymrzsb.js → index-erfc1x40.js} +17 -3
  36. package/{index-ns128c4q.js → index-h94faz96.js} +13 -20
  37. package/index-jd01kj5r.js +656 -0
  38. package/{index-5405kc0x.js → index-jwexzh42.js} +363 -247
  39. package/{index-wfwknnje.js → index-jwrpsrg3.js} +8 -6
  40. package/{index-kvzjda3v.js → index-m41ptvat.js} +5 -5
  41. package/{index-qh7brn20.js → index-mt2154s2.js} +6 -2
  42. package/{index-xjc7gg65.js → index-pn8mhpps.js} +23 -33
  43. package/{index-vpx68e4x.js → index-q9c3c0hy.js} +2 -2
  44. package/{index-v17gj504.js → index-rpgxs245.js} +2 -2
  45. package/{index-twq9p2sp.js → index-s5rhm04y.js} +0 -6
  46. package/{index-018dszb3.js → index-sbfk9daf.js} +10 -3
  47. package/{index-zg0v1zte.js → index-wcg582a8.js} +5 -5
  48. package/{index-6na0sd2b.js → index-ycy302an.js} +5 -2
  49. package/{index-qhtj9y6j.js → index-yw6g3bbv.js} +74 -21
  50. package/{index-2pm1j01n.js → index-zmj143x7.js} +4 -4
  51. package/{index-zn8qgeqx.js → index-zq0mr3hh.js} +1 -1
  52. package/index.js +21 -21
  53. package/{library.command-m8p2r1dh.js → library.command-fp0nhr62.js} +3 -3
  54. package/{localRegistry.command-tt00nwwg.js → localRegistry.command-8y0ecxb9.js} +7 -7
  55. package/{module.command-mfzvqt0n.js → module.command-kqkn48t2.js} +5 -5
  56. package/{package.command-8sy6jjwy.js → package.command-qverjh22.js} +4 -4
  57. package/package.json +8 -6
  58. package/{page.command-taenb02b.js → page.command-94bwhd0g.js} +3 -3
  59. package/{primitive.command-7kpncmkk.js → primitive.command-2ydvtah2.js} +6 -6
  60. package/{prompter-9mn3kp36.js → prompter-1f8d9pcc.js} +1 -1
  61. package/{quality.command-1ar94hrw.js → quality.command-fa0gth4p.js} +3 -4
  62. package/{repair.command-n08t6j8p.js → repair.command-4qng2hpm.js} +5 -5
  63. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  64. package/{scalar.command-mq8qfp4t.js → scalar.command-pb2y95sh.js} +4 -4
  65. package/skills/akan-code/SKILL.md +122 -0
  66. package/skills/akan-module/SKILL.md +2 -2
  67. package/skills/akan-store/SKILL.md +6 -2
  68. package/{subspace.command-x9897gj4.js → subspace.command-vkxmwp74.js} +4 -4
  69. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  70. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  71. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  72. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  73. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  74. package/templates/appSample/lib/task/task.signal.test.ts +14 -0
  75. package/templates/appSample/lib/task/task.signal.ts +1 -1
  76. package/templates/appSample/srvkit/AuthGuard.ts +4 -1
  77. package/templates/appSample/srvkit/SessionInternalArg.ts +3 -1
  78. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  79. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  80. package/templates/crudPages/new/page.tsx +2 -1
  81. package/templates/crudPages/page.tsx +2 -1
  82. package/templates/crudSinglePage/page.tsx +2 -1
  83. package/templates/module/__model__.signal.ts +12 -4
  84. package/templates/pkgRoot/tsconfig.json.template +2 -7
  85. package/templates/service/__model__.abstract.md.template +8 -0
  86. package/{tunnel.command-ht2es78c.js → tunnel.command-02cvp9nf.js} +2 -2
  87. package/{workflow.command-tvz4nm9n.js → workflow.command-132txnrh.js} +9 -9
  88. package/{workspace.command-ayqa1rgt.js → workspace.command-d5wkswek.js} +21 -21
  89. package/context.runner-bys6ykcp.js +0 -22
  90. package/guidelines/agentRule/agentRule.generate.json +0 -31
  91. package/guidelines/componentRule/componentRule.generate.json +0 -31
  92. package/guidelines/conventions/conventions.generate.json +0 -25
  93. package/guidelines/cssRule/cssRule.generate.json +0 -31
  94. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  95. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  96. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  97. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  98. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  99. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  100. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  101. package/guidelines/framework/framework.generate.json +0 -32
  102. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  103. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  104. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  105. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  106. package/guidelines/modelService/modelService.generate.json +0 -31
  107. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  108. package/guidelines/modelStore/modelStore.generate.json +0 -31
  109. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  110. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  111. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  112. package/guidelines/modelView/modelView.generate.json +0 -31
  113. package/guidelines/modelZone/modelZone.generate.json +0 -31
  114. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  115. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  116. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  117. package/guidelines/queryRule/queryRule.generate.json +0 -31
  118. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  119. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  120. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  121. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  122. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  123. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  124. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  125. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  126. package/guidelines/transportRule/transportRule.generate.json +0 -31
  127. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  128. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  129. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  130. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -34
  131. package/index-jgrnpt6y.js +0 -121
  132. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  133. package/templates/module/__model__.abstract.md +0 -28
  134. package/templates/service/__model__.abstract.md +0 -28
@@ -1,7 +1,10 @@
1
1
  // @bun
2
2
  import {
3
- SubagentPool2
4
- } from "./index-jgrnpt6y.js";
3
+ SubagentPool2,
4
+ akanCodePaths2,
5
+ McpServerConfig,
6
+ McpSignIn
7
+ } from "./index-jd01kj5r.js";
5
8
  import {
6
9
  stripAnsi
7
10
  } from "./index-7md2g9vw.js";
@@ -9,7 +12,7 @@ import {
9
12
  akanCodeModel2,
10
13
  akanCodeModelSupportsImages2,
11
14
  akanCodeModelWarnings2
12
- } from "./index-wfwknnje.js";
15
+ } from "./index-jwrpsrg3.js";
13
16
 
14
17
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgent.ts
15
18
  import {
@@ -511,7 +514,7 @@ class AkanToolPack2 {
511
514
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
512
515
  }
513
516
  async names() {
514
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
517
+ const { ContextRunner } = await import("./context.runner-z0rrcz7b.js");
515
518
  return [
516
519
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
517
520
  AkanToolPack2.verifyToolName
@@ -519,7 +522,7 @@ class AkanToolPack2 {
519
522
  }
520
523
  static verifyToolName = "akan_verify";
521
524
  async#register(pi) {
522
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
525
+ const { ContextRunner } = await import("./context.runner-z0rrcz7b.js");
523
526
  const runner = new ContextRunner;
524
527
  const guidelineNames = await AkanToolPack2.#guidelineNames();
525
528
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -561,7 +564,7 @@ class AkanToolPack2 {
561
564
  }
562
565
  static async#guidelineNames() {
563
566
  try {
564
- const { Prompter } = await import("./prompter-9mn3kp36.js");
567
+ const { Prompter } = await import("./prompter-1f8d9pcc.js");
565
568
  return await Prompter.listGuidelines();
566
569
  } catch {
567
570
  return [];
@@ -574,9 +577,78 @@ class AkanToolPack2 {
574
577
  }
575
578
  }
576
579
 
577
- // pkgs/@akanjs/devkit/codeAgent/tools/MailToolPack.ts
580
+ // pkgs/@akanjs/devkit/codeAgent/tools/AskToolPack.ts
578
581
  import { Type as Type2 } from "typebox";
579
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
+
580
652
  class MailToolPack2 {
581
653
  static toolNames = ["list_peers", "send_peer_message"];
582
654
  #options;
@@ -597,7 +669,7 @@ class MailToolPack2 {
597
669
  label: "List peer sessions",
598
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.",
599
671
  promptSnippet: "list_peers: the other akan code sessions running here",
600
- parameters: Type2.Object({}),
672
+ parameters: Type3.Object({}),
601
673
  execute: async () => ({
602
674
  content: [{ type: "text", text: MailToolPack2.#render(this.#options.mailbox()?.peers() ?? []) }],
603
675
  details: undefined
@@ -608,9 +680,9 @@ class MailToolPack2 {
608
680
  label: "Message a peer session",
609
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.",
610
682
  promptSnippet: "send_peer_message: hand work to another akan code session",
611
- parameters: Type2.Object({
612
- to: Type2.String({ description: "The peer's name, or the front of its id, from list_peers." }),
613
- 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." })
614
686
  }),
615
687
  execute: async (_id, params) => {
616
688
  const mailbox = this.#options.mailbox();
@@ -639,43 +711,36 @@ class MailToolPack2 {
639
711
  }
640
712
 
641
713
  // pkgs/@akanjs/devkit/codeAgent/tools/McpToolPack.ts
642
- import { existsSync as existsSync5, readFileSync } from "fs";
643
- import { codeAgentClip, codeAgentOutputChars } from "akanjs/common";
644
- import { Type as Type3 } from "typebox";
645
-
646
- // pkgs/@akanjs/devkit/codeAgent/agent/akanCodePaths.ts
647
- import { existsSync as existsSync4 } from "fs";
648
- import { homedir as homedir2 } from "os";
649
- import path4 from "path";
650
- var builtinSkillsDir = () => {
651
- const candidates = [path4.join(import.meta.dir, "skills"), path4.join(import.meta.dir, "..", "skills")];
652
- return candidates.find((candidate) => existsSync4(candidate));
653
- };
654
- var akanCodePaths2 = {
655
- workspaceDir: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code"),
656
- sessionsDir: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code", "sessions"),
657
- extensionsDir: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code", "extensions"),
658
- skillsDir: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code", "skills"),
659
- mailDir: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code", "mail"),
660
- builtinSkillsDir,
661
- mcpFile: (workspaceRoot) => path4.join(workspaceRoot, ".akan", "code", "mcp.json"),
662
- globalDir: () => path4.join(homedir2(), ".akan", "code"),
663
- authFile: () => path4.join(homedir2(), ".akan", "code", "auth.json"),
664
- modelsFile: () => path4.join(homedir2(), ".akan", "code", "models.json")
665
- };
714
+ import {
715
+ codeAgentClip,
716
+ codeAgentOutputChars
717
+ } from "akanjs/common";
718
+ import { Type as Type4 } from "typebox";
666
719
 
667
720
  // pkgs/@akanjs/devkit/codeAgent/tools/McpClient.ts
668
721
  var protocolVersion = "2025-06-18";
669
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
+
670
732
  class McpClient2 {
733
+ static maxToolPages = 50;
671
734
  ref;
672
735
  #proc;
673
736
  #nextId = 0;
674
737
  #buffer = "";
675
738
  #pending = new Map;
676
739
  #sessionId;
677
- constructor(ref) {
740
+ #token;
741
+ constructor(ref, token) {
678
742
  this.ref = ref;
743
+ this.#token = token;
679
744
  }
680
745
  async connect() {
681
746
  if (this.ref.transport === "stdio")
@@ -691,10 +756,19 @@ class McpClient2 {
691
756
  return this;
692
757
  }
693
758
  async listTools() {
694
- const response = await this.#call("tools/list", {});
695
- if (response.error)
696
- throw new Error(response.error.message);
697
- 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;
698
772
  }
699
773
  async callTool(name, args) {
700
774
  const response = await this.#call("tools/call", { name, arguments: args });
@@ -778,11 +852,16 @@ class McpClient2 {
778
852
  headers: {
779
853
  "content-type": "application/json",
780
854
  accept: "application/json, text/event-stream",
855
+ "mcp-protocol-version": protocolVersion,
856
+ ...this.ref.headers,
857
+ ...this.#token ? { authorization: `Bearer ${this.#token}` } : {},
781
858
  ...this.#sessionId ? { "mcp-session-id": this.#sessionId } : {}
782
859
  },
783
860
  body: JSON.stringify(body),
784
861
  signal: AbortSignal.timeout(30000)
785
862
  });
863
+ if (response.status === 401)
864
+ throw new McpUnauthorized2(this.ref.name, response.headers.get("www-authenticate") ?? "");
786
865
  this.#sessionId = response.headers.get("mcp-session-id") ?? this.#sessionId;
787
866
  const text = await response.text();
788
867
  if (!text.trim())
@@ -804,29 +883,44 @@ class McpClient2 {
804
883
 
805
884
  // pkgs/@akanjs/devkit/codeAgent/tools/McpToolPack.ts
806
885
  class McpToolPack2 {
807
- #options;
808
886
  #clients = [];
809
- constructor(options) {
810
- this.#options = options;
811
- }
887
+ #status = [];
888
+ constructor() {}
812
889
  static async connect(options) {
813
890
  const refs = McpToolPack2.#refs(options);
814
891
  if (!refs.length)
815
892
  return;
816
- const pack = new McpToolPack2(options);
893
+ const pack = new McpToolPack2;
817
894
  for (const ref of refs) {
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;
818
897
  try {
819
- const client = await new McpClient2(ref).connect();
820
- pack.#clients.push({ client, tools: await client.listTools() });
898
+ const client = await new McpClient2(ref, token).connect();
899
+ const tools = await client.listTools();
900
+ pack.#clients.push({ client, tools });
901
+ pack.#status.push({
902
+ ...base,
903
+ tools: tools.map((tool) => McpToolPack2.#nameOf(ref.name, tool.name)),
904
+ auth: token ? "authorized" : "none"
905
+ });
821
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
+ }
822
912
  options.onNotice?.(`MCP server "${ref.name}" is unavailable: ${String(error)}`);
913
+ pack.#status.push({ ...base, tools: [], error: String(error) });
823
914
  }
824
915
  }
825
- return pack.#clients.length ? pack : undefined;
916
+ return pack;
826
917
  }
827
918
  get toolNames() {
828
919
  return this.#clients.flatMap(({ client, tools }) => tools.map((tool) => McpToolPack2.#nameOf(client.ref.name, tool.name)));
829
920
  }
921
+ get status() {
922
+ return this.#status;
923
+ }
830
924
  extension() {
831
925
  return { name: "akan-mcp", factory: (pi) => this.#register(pi) };
832
926
  }
@@ -844,7 +938,7 @@ class McpToolPack2 {
844
938
  label: `${client.ref.name}: ${tool.name}`,
845
939
  description,
846
940
  promptSnippet: `${name}: ${description.split(".")[0] ?? description}`,
847
- parameters: Type3.Unsafe(tool.inputSchema),
941
+ parameters: Type4.Unsafe(tool.inputSchema),
848
942
  execute: async (_id, params) => {
849
943
  const result = await client.callTool(tool.name, params ?? {});
850
944
  return {
@@ -863,36 +957,14 @@ class McpToolPack2 {
863
957
  if (options.profile.tools.mcp === "off")
864
958
  return [];
865
959
  const declared = Array.isArray(options.profile.tools.mcp) ? options.profile.tools.mcp : [];
866
- return [...McpToolPack2.#fromFile(options.workspaceRoot), ...declared];
867
- }
868
- static #fromFile(workspaceRoot) {
869
- const file = akanCodePaths2.mcpFile(workspaceRoot);
870
- if (!existsSync5(file))
871
- return [];
872
- try {
873
- const parsed = JSON.parse(readFileSync(file, "utf8"));
874
- const entries = Object.entries(parsed.mcpServers ?? parsed.servers ?? {});
875
- return entries.filter(([, entry]) => !entry.disabled).map(([name, entry]) => {
876
- const transport = entry.url ? "http" : "stdio";
877
- return {
878
- name,
879
- transport,
880
- ...entry.command ? { command: entry.command } : {},
881
- ...entry.args ? { args: entry.args } : {},
882
- ...entry.url ? { url: entry.url } : {},
883
- ...entry.env ? { env: entry.env } : {}
884
- };
885
- });
886
- } catch {
887
- return [];
888
- }
960
+ return [...McpServerConfig.read(options.workspaceRoot), ...declared];
889
961
  }
890
962
  }
891
963
 
892
964
  // pkgs/@akanjs/devkit/codeAgent/tools/SessionToolPack.ts
893
965
  import { SessionManager } from "@earendil-works/pi-coding-agent";
894
966
  import { codeAgentClip as codeAgentClip2, codeAgentOutputChars as codeAgentOutputChars2 } from "akanjs/common";
895
- import { Type as Type4 } from "typebox";
967
+ import { Type as Type5 } from "typebox";
896
968
  var snippetChars = 240;
897
969
 
898
970
  class SessionToolPack2 {
@@ -915,9 +987,9 @@ class SessionToolPack2 {
915
987
  label: "Search sessions",
916
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.",
917
989
  promptSnippet: "session_search: find earlier conversations in this workspace by phrase",
918
- parameters: Type4.Object({
919
- query: Type4.String({ description: "A phrase to look for, matched case-insensitively." }),
920
- 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." }))
921
993
  }),
922
994
  execute: async (_id, params) => {
923
995
  const matches = await this.#search(params.query, params.limit ?? 5);
@@ -929,7 +1001,7 @@ class SessionToolPack2 {
929
1001
  label: "Read session",
930
1002
  description: "Read one earlier conversation, found with session_search, as plain text.",
931
1003
  promptSnippet: "session_read: read one earlier conversation by id",
932
- parameters: Type4.Object({ sessionId: Type4.String() }),
1004
+ parameters: Type5.Object({ sessionId: Type5.String() }),
933
1005
  execute: async (_id, params) => {
934
1006
  const text = await this.#read(params.sessionId);
935
1007
  return { content: [{ type: "text", text }], details: undefined, isError: text.startsWith("No session") };
@@ -962,7 +1034,7 @@ class SessionToolPack2 {
962
1034
 
963
1035
  // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
964
1036
  import { codeAgentClip as codeAgentClip3, codeAgentOutputChars as codeAgentOutputChars3 } from "akanjs/common";
965
- import { Type as Type5 } from "typebox";
1037
+ import { Type as Type6 } from "typebox";
966
1038
 
967
1039
  class WebToolPack2 {
968
1040
  #options;
@@ -1027,7 +1099,7 @@ class WebToolPack2 {
1027
1099
  label: "Fetch",
1028
1100
  description: "Fetch a URL and return its readable text. HTML is stripped to text; the result is truncated.",
1029
1101
  promptSnippet: "web_fetch: fetch a URL and return its readable text",
1030
- parameters: Type5.Object({ url: Type5.String({ description: "Absolute http(s) URL." }) }),
1102
+ parameters: Type6.Object({ url: Type6.String({ description: "Absolute http(s) URL." }) }),
1031
1103
  execute: async (_id, params) => {
1032
1104
  const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
1033
1105
  if (refusal)
@@ -1052,9 +1124,9 @@ ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1052
1124
  label: "Search",
1053
1125
  description: `Search the web (${provider.name}) and return the top results as title, url and snippet.`,
1054
1126
  promptSnippet: "web_search: search the web and return titles, urls and snippets",
1055
- parameters: Type5.Object({
1056
- query: Type5.String(),
1057
- 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." }))
1058
1130
  }),
1059
1131
  execute: async (_id, params) => {
1060
1132
  const key = process.env[provider.envKey] ?? "";
@@ -1098,6 +1170,7 @@ class AkanCodePlugins2 {
1098
1170
  ...options.onNotice ? { onNotice: options.onNotice } : {}
1099
1171
  });
1100
1172
  const dispose = () => mcp?.close();
1173
+ const mcpStatus = mcp?.status ?? [];
1101
1174
  const web = new WebToolPack2({ profile: options.profile });
1102
1175
  const webExtension = web.extension();
1103
1176
  const sessions = new SessionToolPack2({
@@ -1109,10 +1182,20 @@ class AkanCodePlugins2 {
1109
1182
  const sessionExtension = sessions.extension();
1110
1183
  const mail = new MailToolPack2({ profile: options.profile, mailbox: options.mailbox });
1111
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();
1112
1188
  if (!options.profile.tools.akan)
1113
1189
  return {
1114
- extensions: [mcp?.extension(), webExtension, sessionExtension, mailExtension].filter((entry) => !!entry),
1115
- 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
+ ],
1198
+ mcp: mcpStatus,
1116
1199
  dispose
1117
1200
  };
1118
1201
  const readOnly = !options.profile.tools.builtin.includes("write");
@@ -1127,7 +1210,8 @@ class AkanCodePlugins2 {
1127
1210
  cwd: options.cwd,
1128
1211
  parent: options.profile,
1129
1212
  depth: options.depth,
1130
- ...options.onNotice ? { onNotice: options.onNotice } : {}
1213
+ ...options.onNotice ? { onNotice: options.onNotice } : {},
1214
+ ...options.onAgents ? { onAgents: options.onAgents } : {}
1131
1215
  });
1132
1216
  if (subagent)
1133
1217
  extensions.push(subagent);
@@ -1145,6 +1229,8 @@ class AkanCodePlugins2 {
1145
1229
  extensions.push(sessionExtension);
1146
1230
  if (mailExtension)
1147
1231
  extensions.push(mailExtension);
1232
+ if (askExtension)
1233
+ extensions.push(askExtension);
1148
1234
  return {
1149
1235
  extensions,
1150
1236
  toolNames: [
@@ -1153,8 +1239,10 @@ class AkanCodePlugins2 {
1153
1239
  ...mcp?.toolNames ?? [],
1154
1240
  ...web.names(),
1155
1241
  ...sessions.names(),
1156
- ...mail.names()
1242
+ ...mail.names(),
1243
+ ...ask?.names() ?? []
1157
1244
  ],
1245
+ mcp: mcpStatus,
1158
1246
  dispose
1159
1247
  };
1160
1248
  }
@@ -1184,36 +1272,31 @@ class AkanCodePlugins2 {
1184
1272
  }
1185
1273
 
1186
1274
  // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1187
- import { existsSync as existsSync9, mkdirSync as mkdirSync2 } from "fs";
1188
- import {
1189
- AuthStorage,
1190
- ModelRegistry,
1191
- SessionManager as SessionManager2,
1192
- SettingsManager
1193
- } from "@earendil-works/pi-coding-agent";
1275
+ import { existsSync as existsSync7, mkdirSync } from "fs";
1276
+ import { ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
1194
1277
 
1195
1278
  // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
1196
- import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
1197
- import path5 from "path";
1279
+ import { existsSync as existsSync4, readFileSync } from "fs";
1280
+ import path4 from "path";
1198
1281
 
1199
1282
  class AkanEnvKeys2 {
1200
- static apply(authStorage, workspaceRoot) {
1283
+ static async apply(runtime, workspaceRoot) {
1201
1284
  const applied = [];
1202
1285
  for (const [name, value] of AkanEnvKeys2.#entries(workspaceRoot)) {
1203
1286
  const provider = AkanEnvKeys2.#providerOf(name);
1204
1287
  if (!provider || !value)
1205
1288
  continue;
1206
- authStorage.setRuntimeApiKey(provider, value);
1289
+ await runtime.setRuntimeApiKey(provider, value);
1207
1290
  applied.push(provider);
1208
1291
  }
1209
1292
  return applied;
1210
1293
  }
1211
1294
  static #entries(workspaceRoot) {
1212
1295
  const fromProcess = Object.entries(process.env).filter(([, value]) => typeof value === "string");
1213
- const file = path5.join(workspaceRoot, ".env");
1214
- if (!existsSync6(file))
1296
+ const file = path4.join(workspaceRoot, ".env");
1297
+ if (!existsSync4(file))
1215
1298
  return fromProcess;
1216
- const fromFile = readFileSync2(file, "utf8").split(`
1299
+ const fromFile = readFileSync(file, "utf8").split(`
1217
1300
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
1218
1301
  const index = line.indexOf("=");
1219
1302
  return [
@@ -1229,89 +1312,82 @@ class AkanEnvKeys2 {
1229
1312
  }
1230
1313
  }
1231
1314
 
1232
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanAuthStorageBackend.ts
1233
- import {
1234
- closeSync,
1235
- existsSync as existsSync7,
1236
- mkdirSync,
1237
- openSync,
1238
- readFileSync as readFileSync3,
1239
- renameSync,
1240
- unlinkSync,
1241
- writeFileSync
1242
- } from "fs";
1243
- import path6 from "path";
1315
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanContextFiles.ts
1316
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1317
+ import path5 from "path";
1244
1318
 
1245
- class AkanAuthStorageBackend2 {
1246
- #authPath;
1247
- #lockPath;
1248
- constructor(authPath) {
1249
- this.#authPath = authPath;
1250
- this.#lockPath = `${authPath}.lock`;
1251
- }
1252
- withLock(fn) {
1253
- this.#acquire();
1254
- try {
1255
- const { result, next } = fn(this.#read());
1256
- if (next !== undefined)
1257
- this.#write(next);
1258
- return result;
1259
- } finally {
1260
- this.#release();
1319
+ class AkanContextFiles {
1320
+ static #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1321
+ static extend(base, options) {
1322
+ const seen = new Set(base.map((file) => file.path));
1323
+ const contents = new Set(base.map((file) => file.content.trim()));
1324
+ const added = [];
1325
+ for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
1326
+ if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim())
1327
+ continue;
1328
+ seen.add(file.path);
1329
+ contents.add(file.content.trim());
1330
+ added.push(file);
1261
1331
  }
1332
+ return [...base, ...added];
1262
1333
  }
1263
- async withLockAsync(fn) {
1264
- this.#acquire();
1265
- try {
1266
- const { result, next } = await fn(this.#read());
1267
- if (next !== undefined)
1268
- this.#write(next);
1269
- return result;
1270
- } finally {
1271
- this.#release();
1334
+ static #claude(base) {
1335
+ const files = [];
1336
+ for (const file of base) {
1337
+ if (!/^AGENTS\.(md|MD)$/.test(path5.basename(file.path)))
1338
+ continue;
1339
+ for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
1340
+ const candidate = path5.join(path5.dirname(file.path), name);
1341
+ const content = AkanContextFiles.#read(candidate);
1342
+ if (content !== undefined)
1343
+ files.push({ path: candidate, content });
1344
+ }
1272
1345
  }
1273
- }
1274
- #read() {
1275
- if (!existsSync7(this.#authPath))
1276
- return;
1277
- const text = readFileSync3(this.#authPath, "utf8");
1278
- return text.trim() ? text : undefined;
1279
- }
1280
- #write(next) {
1281
- mkdirSync(path6.dirname(this.#authPath), { recursive: true, mode: 448 });
1282
- const temp = `${this.#authPath}.${process.pid}.tmp`;
1283
- writeFileSync(temp, next, { mode: 384 });
1284
- renameSync(temp, this.#authPath);
1285
- }
1286
- #acquire() {
1287
- mkdirSync(path6.dirname(this.#lockPath), { recursive: true, mode: 448 });
1288
- const deadline = Date.now() + 5000;
1289
- for (;; ) {
1290
- try {
1291
- closeSync(openSync(this.#lockPath, "wx"));
1292
- return;
1293
- } catch {
1294
- if (this.#lockIsStale() || Date.now() > deadline) {
1295
- try {
1296
- unlinkSync(this.#lockPath);
1297
- } catch {}
1346
+ return files;
1347
+ }
1348
+ static #cursor(options) {
1349
+ const files = [];
1350
+ const roots = [...new Set([options.workspaceRoot, options.cwd])];
1351
+ for (const root of roots) {
1352
+ const legacy = AkanContextFiles.#read(path5.join(root, ".cursorrules"));
1353
+ if (legacy !== undefined)
1354
+ files.push({ path: path5.join(root, ".cursorrules"), content: legacy });
1355
+ for (const file of AkanContextFiles.#mdcFiles(path5.join(root, ".cursor", "rules"))) {
1356
+ const raw = AkanContextFiles.#read(file);
1357
+ if (raw === undefined)
1298
1358
  continue;
1299
- }
1300
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
1359
+ const body = AkanContextFiles.#applied(raw);
1360
+ if (body !== undefined)
1361
+ files.push({ path: file, content: body });
1301
1362
  }
1302
1363
  }
1364
+ return files;
1303
1365
  }
1304
- #lockIsStale() {
1366
+ static #applied(raw) {
1367
+ const match = AkanContextFiles.#frontmatter.exec(raw);
1368
+ if (!match)
1369
+ return raw;
1370
+ if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? ""))
1371
+ return;
1372
+ return raw.slice(match[0].length);
1373
+ }
1374
+ static #mdcFiles(dir) {
1375
+ if (!existsSync5(dir))
1376
+ return [];
1305
1377
  try {
1306
- return Date.now() - Bun.file(this.#lockPath).lastModified > 30000;
1378
+ return readdirSync(dir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".mdc")).map((entry) => path5.join(entry.parentPath ?? dir, entry.name)).sort();
1307
1379
  } catch {
1308
- return true;
1380
+ return [];
1309
1381
  }
1310
1382
  }
1311
- #release() {
1383
+ static #read(file) {
1312
1384
  try {
1313
- unlinkSync(this.#lockPath);
1314
- } catch {}
1385
+ if (!existsSync5(file) || !statSync2(file).isFile())
1386
+ return;
1387
+ return readFileSync2(file, "utf8").replace(/^\uFEFF/, "");
1388
+ } catch {
1389
+ return;
1390
+ }
1315
1391
  }
1316
1392
  }
1317
1393
 
@@ -1323,6 +1399,7 @@ var akanSystemPrompt2 = (profile) => {
1323
1399
  "- 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.",
1324
1400
  "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
1325
1401
  "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
1402
+ "- 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.",
1326
1403
  "- 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`."
1327
1404
  ];
1328
1405
  if (profile.tools.builtin.includes("write"))
@@ -1331,6 +1408,8 @@ var akanSystemPrompt2 = (profile) => {
1331
1408
  lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
1332
1409
  if (profile.tools.subagent)
1333
1410
  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.');
1411
+ if (profile.ui.canPrompt)
1412
+ 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.");
1334
1413
  if (profile.approval !== "never")
1335
1414
  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.`);
1336
1415
  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.");
@@ -1339,26 +1418,26 @@ var akanSystemPrompt2 = (profile) => {
1339
1418
  };
1340
1419
 
1341
1420
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
1342
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
1343
- import path7 from "path";
1421
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
1422
+ import path6 from "path";
1344
1423
 
1345
1424
  class CodeSessionIndex2 {
1346
1425
  static readLimit = 512 * 1024;
1347
1426
  static list(dir, limit = 30) {
1348
- if (!existsSync8(dir))
1427
+ if (!existsSync6(dir))
1349
1428
  return [];
1350
- const files = readdirSync(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path7.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
1429
+ const files = readdirSync2(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path6.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
1351
1430
  return files.map((entry) => CodeSessionIndex2.#read(entry.file, entry.updatedAt)).filter((entry) => !!entry);
1352
1431
  }
1353
1432
  static fileOf(dir, id) {
1354
- if (!existsSync8(dir))
1433
+ if (!existsSync6(dir))
1355
1434
  return;
1356
- const match = readdirSync(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
1357
- return match ? path7.join(dir, match) : undefined;
1435
+ const match = readdirSync2(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
1436
+ return match ? path6.join(dir, match) : undefined;
1358
1437
  }
1359
1438
  static #mtime(file) {
1360
1439
  try {
1361
- return statSync2(file).mtimeMs;
1440
+ return statSync3(file).mtimeMs;
1362
1441
  } catch {
1363
1442
  return 0;
1364
1443
  }
@@ -1389,9 +1468,9 @@ class CodeSessionIndex2 {
1389
1468
  }
1390
1469
  static #lines(file) {
1391
1470
  try {
1392
- if (statSync2(file).size > CodeSessionIndex2.readLimit)
1471
+ if (statSync3(file).size > CodeSessionIndex2.readLimit)
1393
1472
  return [];
1394
- return readFileSync4(file, "utf8").split(`
1473
+ return readFileSync3(file, "utf8").split(`
1395
1474
  `).filter((line) => !!line.trim());
1396
1475
  } catch {
1397
1476
  return [];
@@ -1419,20 +1498,20 @@ class AkanCodeServices2 {
1419
1498
  quietStartup: true
1420
1499
  });
1421
1500
  }
1422
- static auth(workspaceRoot) {
1423
- mkdirSync2(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
1424
- const authStorage = AuthStorage.fromStorage(new AkanAuthStorageBackend2(akanCodePaths2.authFile()));
1425
- AkanEnvKeys2.apply(authStorage, workspaceRoot);
1426
- return authStorage;
1427
- }
1428
- static models(authStorage) {
1429
- return ModelRegistry.create(authStorage, akanCodePaths2.modelsFile());
1501
+ static async runtime(workspaceRoot) {
1502
+ mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
1503
+ const runtime = await ModelRuntime.create({
1504
+ authPath: akanCodePaths2.authFile(),
1505
+ modelsPath: akanCodePaths2.modelsFile()
1506
+ });
1507
+ await AkanEnvKeys2.apply(runtime, workspaceRoot);
1508
+ return runtime;
1430
1509
  }
1431
1510
  static sessions(workspaceRoot, cwd, profile, resume) {
1432
1511
  if (profile.session.store !== "file")
1433
1512
  return SessionManager2.inMemory(cwd);
1434
1513
  const dir = akanCodePaths2.sessionsDir(workspaceRoot);
1435
- mkdirSync2(dir, { recursive: true });
1514
+ mkdirSync(dir, { recursive: true });
1436
1515
  const manager = SessionManager2.create(cwd, dir);
1437
1516
  if (!resume)
1438
1517
  return manager;
@@ -1445,17 +1524,24 @@ class AkanCodeServices2 {
1445
1524
  static #skillPaths(profile, workspaceRoot) {
1446
1525
  if (!profile.context.skills)
1447
1526
  return [];
1448
- const workspace = akanCodePaths2.skillsDir(workspaceRoot);
1449
- return [akanCodePaths2.builtinSkillsDir(), existsSync9(workspace) ? workspace : undefined].filter((dir) => !!dir);
1527
+ const dirs = [
1528
+ akanCodePaths2.builtinSkillsDir(),
1529
+ akanCodePaths2.globalSkillsDir(),
1530
+ akanCodePaths2.skillsDir(workspaceRoot)
1531
+ ];
1532
+ return dirs.filter((dir) => !!dir && existsSync7(dir));
1450
1533
  }
1451
1534
  static resourceOptions(options) {
1452
- const present = (dir) => existsSync9(dir) ? [dir] : [];
1535
+ const present = (dir) => existsSync7(dir) ? [dir] : [];
1453
1536
  return {
1454
1537
  noExtensions: true,
1455
1538
  noSkills: true,
1456
1539
  noPromptTemplates: true,
1457
1540
  noThemes: true,
1458
1541
  noContextFiles: !options.profile.context.projectFiles,
1542
+ agentsFilesOverride: ({ agentsFiles }) => ({
1543
+ agentsFiles: options.profile.context.projectFiles ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd }) : agentsFiles
1544
+ }),
1459
1545
  appendSystemPrompt: [akanSystemPrompt2(options.profile)],
1460
1546
  additionalExtensionPaths: present(akanCodePaths2.extensionsDir(options.workspaceRoot)),
1461
1547
  additionalSkillPaths: AkanCodeServices2.#skillPaths(options.profile, options.workspaceRoot),
@@ -1733,7 +1819,7 @@ class CodeAgentEventMapper2 {
1733
1819
  }
1734
1820
 
1735
1821
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentGate.ts
1736
- import path8 from "path";
1822
+ import path7 from "path";
1737
1823
  var writeTools = new Set(["write", "edit"]);
1738
1824
  var commandTools = new Set(["bash"]);
1739
1825
 
@@ -1772,9 +1858,9 @@ class CodeAgentGate2 {
1772
1858
  return `The command names "${hit}", which the profile's deny list covers. Ask the user for the value instead of reading the file.`;
1773
1859
  }
1774
1860
  #deniedReason(target) {
1775
- const absolute = path8.isAbsolute(target) ? target : path8.resolve(this.#profile.paths.root, target);
1776
- const relative = path8.relative(this.#profile.paths.root, absolute);
1777
- if (relative.startsWith("..") || path8.isAbsolute(relative))
1861
+ const absolute = path7.isAbsolute(target) ? target : path7.resolve(this.#profile.paths.root, target);
1862
+ const relative = path7.relative(this.#profile.paths.root, absolute);
1863
+ if (relative.startsWith("..") || path7.isAbsolute(relative))
1778
1864
  return `Path is outside the agent root (${this.#profile.paths.root}).`;
1779
1865
  if (this.#deny.some((glob) => glob.match(absolute) || glob.match(relative)))
1780
1866
  return "Path is on the profile's deny list (secrets and environment files are never readable).";
@@ -1864,15 +1950,15 @@ class CodeAgentUi2 {
1864
1950
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeMailbox.ts
1865
1951
  import {
1866
1952
  appendFileSync,
1867
- existsSync as existsSync10,
1868
- mkdirSync as mkdirSync3,
1869
- readdirSync as readdirSync2,
1870
- readFileSync as readFileSync5,
1953
+ existsSync as existsSync8,
1954
+ mkdirSync as mkdirSync2,
1955
+ readdirSync as readdirSync3,
1956
+ readFileSync as readFileSync4,
1871
1957
  rmSync,
1872
- statSync as statSync3,
1873
- writeFileSync as writeFileSync2
1958
+ statSync as statSync4,
1959
+ writeFileSync
1874
1960
  } from "fs";
1875
- import path9 from "path";
1961
+ import path8 from "path";
1876
1962
 
1877
1963
  class CodeMailbox2 {
1878
1964
  static staleMs = 20000;
@@ -1892,11 +1978,11 @@ class CodeMailbox2 {
1892
1978
  this.#name = name;
1893
1979
  }
1894
1980
  get inbox() {
1895
- return path9.join(this.#dir, "inbox", `${this.#id}.jsonl`);
1981
+ return path8.join(this.#dir, "inbox", `${this.#id}.jsonl`);
1896
1982
  }
1897
1983
  open(cwd, onMail) {
1898
- mkdirSync3(path9.join(this.#dir, "live"), { recursive: true });
1899
- mkdirSync3(path9.join(this.#dir, "inbox"), { recursive: true });
1984
+ mkdirSync2(path8.join(this.#dir, "live"), { recursive: true });
1985
+ mkdirSync2(path8.join(this.#dir, "inbox"), { recursive: true });
1900
1986
  this.#read = CodeMailbox2.#lines(this.inbox).length;
1901
1987
  this.beat(cwd);
1902
1988
  this.#beat = setInterval(() => this.beat(cwd), CodeMailbox2.beatMs);
@@ -1913,7 +1999,7 @@ class CodeMailbox2 {
1913
1999
  this.#beat = null;
1914
2000
  this.#poll = null;
1915
2001
  try {
1916
- rmSync(path9.join(this.#dir, "live", `${this.#id}.json`), { force: true });
2002
+ rmSync(path8.join(this.#dir, "live", `${this.#id}.json`), { force: true });
1917
2003
  } catch {}
1918
2004
  }
1919
2005
  rename(name, cwd) {
@@ -1923,15 +2009,15 @@ class CodeMailbox2 {
1923
2009
  beat(cwd) {
1924
2010
  const peer = { id: this.#id, name: this.#name, cwd, pid: process.pid, at: Date.now() };
1925
2011
  try {
1926
- writeFileSync2(path9.join(this.#dir, "live", `${this.#id}.json`), JSON.stringify(peer));
2012
+ writeFileSync(path8.join(this.#dir, "live", `${this.#id}.json`), JSON.stringify(peer));
1927
2013
  } catch {}
1928
2014
  }
1929
2015
  peers() {
1930
- const dir = path9.join(this.#dir, "live");
1931
- if (!existsSync10(dir))
2016
+ const dir = path8.join(this.#dir, "live");
2017
+ if (!existsSync8(dir))
1932
2018
  return [];
1933
2019
  const now = Date.now();
1934
- return readdirSync2(dir).filter((entry) => entry.endsWith(".json")).map((entry) => CodeMailbox2.#peer(path9.join(dir, entry))).filter((peer) => !!peer && peer.id !== this.#id && now - peer.at < CodeMailbox2.staleMs).sort((left, right) => right.at - left.at);
2020
+ 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);
1935
2021
  }
1936
2022
  find(target) {
1937
2023
  const needle = target.trim().toLowerCase();
@@ -1949,8 +2035,8 @@ class CodeMailbox2 {
1949
2035
  throw new Error(`Too many messages: ${CodeMailbox2.sendsPerMinute} a minute is the ceiling.`);
1950
2036
  this.#sent.push(now);
1951
2037
  const mail = { at: now, from: this.#id, fromName: this.#name, text };
1952
- mkdirSync3(path9.join(this.#dir, "inbox"), { recursive: true });
1953
- appendFileSync(path9.join(this.#dir, "inbox", `${to}.jsonl`), `${JSON.stringify(mail)}
2038
+ mkdirSync2(path8.join(this.#dir, "inbox"), { recursive: true });
2039
+ appendFileSync(path8.join(this.#dir, "inbox", `${to}.jsonl`), `${JSON.stringify(mail)}
1954
2040
  `);
1955
2041
  }
1956
2042
  #drain() {
@@ -1963,15 +2049,15 @@ class CodeMailbox2 {
1963
2049
  }
1964
2050
  static #peer(file) {
1965
2051
  try {
1966
- const peer = JSON.parse(readFileSync5(file, "utf8"));
1967
- return typeof peer.id === "string" ? { ...peer, at: peer.at || statSync3(file).mtimeMs } : undefined;
2052
+ const peer = JSON.parse(readFileSync4(file, "utf8"));
2053
+ return typeof peer.id === "string" ? { ...peer, at: peer.at || statSync4(file).mtimeMs } : undefined;
1968
2054
  } catch {
1969
2055
  return;
1970
2056
  }
1971
2057
  }
1972
2058
  static #lines(file) {
1973
2059
  try {
1974
- return readFileSync5(file, "utf8").split(`
2060
+ return readFileSync4(file, "utf8").split(`
1975
2061
  `).filter((line) => !!line.trim());
1976
2062
  } catch {
1977
2063
  return [];
@@ -1988,15 +2074,15 @@ class CodeMailbox2 {
1988
2074
 
1989
2075
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionFork.ts
1990
2076
  import { randomBytes } from "crypto";
1991
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1992
- import path10 from "path";
2077
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
2078
+ import path9 from "path";
1993
2079
  import { codeAgentSessionName } from "akanjs/common";
1994
2080
  class CodeSessionFork2 {
1995
2081
  static fork(dir, id, name) {
1996
2082
  const source = CodeSessionIndex2.fileOf(dir, id);
1997
2083
  if (!source)
1998
2084
  throw new Error(`No stored session ${id} in ${dir}`);
1999
- const lines = readFileSync6(source, "utf8").split(`
2085
+ const lines = readFileSync5(source, "utf8").split(`
2000
2086
  `).filter((line) => !!line.trim());
2001
2087
  if (!lines.length)
2002
2088
  throw new Error(`Session ${id} has nothing in it to fork`);
@@ -2009,8 +2095,8 @@ class CodeSessionFork2 {
2009
2095
  first.id = forked;
2010
2096
  first.timestamp = at.toISOString();
2011
2097
  records.push(CodeSessionFork2.#named(records, name ?? CodeSessionFork2.#nameOf(records), at));
2012
- const file = path10.join(dir, `${at.toISOString().replace(/[:.]/g, "-")}_${forked}.jsonl`);
2013
- writeFileSync3(file, `${records.map((record) => JSON.stringify(record)).join(`
2098
+ const file = path9.join(dir, `${at.toISOString().replace(/[:.]/g, "-")}_${forked}.jsonl`);
2099
+ writeFileSync2(file, `${records.map((record) => JSON.stringify(record)).join(`
2014
2100
  `)}
2015
2101
  `);
2016
2102
  return { id: forked, file };
@@ -2052,6 +2138,8 @@ class CodeAgent2 {
2052
2138
  #disposed = false;
2053
2139
  #sessionDir;
2054
2140
  #mailbox;
2141
+ #mcp = [];
2142
+ #workspaceRoot = "";
2055
2143
  #replay = [];
2056
2144
  #replayFrom = 0;
2057
2145
  #lastContextTokens;
@@ -2064,9 +2152,8 @@ class CodeAgent2 {
2064
2152
  const cwd = options.cwd ?? workspaceRoot;
2065
2153
  const agent = new CodeAgent2(options.profile);
2066
2154
  const settingsManager = AkanCodeServices2.settings();
2067
- const authStorage = AkanCodeServices2.auth(workspaceRoot);
2068
- const modelRegistry = AkanCodeServices2.models(authStorage);
2069
- const model = await akanCodeModel2(modelRegistry, options.model);
2155
+ const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot);
2156
+ const model = await akanCodeModel2(modelRuntime, options.model);
2070
2157
  const compaction = DEFAULT_COMPACTION_SETTINGS;
2071
2158
  const modelWarnings = akanCodeModelWarnings2(model, compaction.reserveTokens + compaction.keepRecentTokens);
2072
2159
  const akan = await AkanCodePlugins2.build({
@@ -2078,7 +2165,9 @@ class CodeAgent2 {
2078
2165
  depth: options.depth ?? 0,
2079
2166
  currentSessionId: () => agent.sessionId,
2080
2167
  mailbox: () => agent.#mailbox,
2081
- onNotice: (message) => agent.#emit({ type: "notice", level: "warning", message })
2168
+ onNotice: (message) => agent.#emit({ type: "notice", level: "warning", message }),
2169
+ onAgents: (agents) => agent.#emit({ type: "subagent", agents }),
2170
+ onAsk: (question) => agent.ask(question)
2082
2171
  });
2083
2172
  const resourceLoader = new DefaultResourceLoader({
2084
2173
  cwd,
@@ -2095,8 +2184,7 @@ class CodeAgent2 {
2095
2184
  const { session } = await createAgentSession({
2096
2185
  cwd,
2097
2186
  agentDir: akanCodePaths2.globalDir(),
2098
- authStorage,
2099
- modelRegistry,
2187
+ modelRuntime,
2100
2188
  settingsManager,
2101
2189
  resourceLoader,
2102
2190
  model,
@@ -2106,6 +2194,8 @@ class CodeAgent2 {
2106
2194
  customTools: options.customTools
2107
2195
  });
2108
2196
  agent.#disposePlugins = akan.dispose;
2197
+ agent.#mcp = akan.mcp;
2198
+ agent.#workspaceRoot = workspaceRoot;
2109
2199
  if (options.profile.session.store === "file")
2110
2200
  agent.#sessionDir = akanCodePaths2.sessionsDir(workspaceRoot);
2111
2201
  await agent.#attach(session, options.mode ?? "print");
@@ -2125,6 +2215,12 @@ class CodeAgent2 {
2125
2215
  sessions() {
2126
2216
  return this.#sessionDir ? CodeSessionIndex2.list(this.#sessionDir) : [];
2127
2217
  }
2218
+ get workspaceRoot() {
2219
+ return this.#workspaceRoot;
2220
+ }
2221
+ mcpServers() {
2222
+ return this.#mcp;
2223
+ }
2128
2224
  peers() {
2129
2225
  return this.#mailbox?.peers() ?? [];
2130
2226
  }
@@ -2190,6 +2286,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2190
2286
  if (text)
2191
2287
  this.#emit({ type: "message", turnId, role: message.role, text });
2192
2288
  }
2289
+ this.#emitContextNow();
2193
2290
  }
2194
2291
  static #textOf(content) {
2195
2292
  if (typeof content === "string")
@@ -2279,9 +2376,26 @@ Continue the task.`);
2279
2376
  session.setThinkingLevel(effort);
2280
2377
  this.#emit({ type: "session", info: this.info });
2281
2378
  }
2379
+ catalogue() {
2380
+ const runtime = this.#session?.modelRuntime;
2381
+ if (!runtime)
2382
+ return [];
2383
+ const current = this.#session?.model;
2384
+ return runtime.getProviders().map((provider) => ({
2385
+ id: provider.id,
2386
+ name: provider.name ?? provider.id,
2387
+ authorized: runtime.hasConfiguredAuth(provider.id),
2388
+ models: (runtime.getModels(provider.id) ?? []).map((model) => ({
2389
+ id: model.id,
2390
+ name: model.name,
2391
+ ...model.contextWindow ? { contextWindow: model.contextWindow } : {},
2392
+ current: model.provider === current?.provider && model.id === current?.id
2393
+ }))
2394
+ })).sort((a, b) => a.id.localeCompare(b.id));
2395
+ }
2282
2396
  async setModel(ref) {
2283
2397
  const session = this.#require();
2284
- const model = await akanCodeModel2(session.modelRegistry, ref);
2398
+ const model = await akanCodeModel2(session.modelRuntime, ref);
2285
2399
  if (!model)
2286
2400
  throw new Error(`Unknown model: ${ref.provider}/${ref.id}`);
2287
2401
  await session.setModel(model);
@@ -2331,6 +2445,9 @@ Continue the task.`);
2331
2445
  #emitContextUsage(eventType) {
2332
2446
  if (eventType !== "agent_end" && eventType !== "compaction_end")
2333
2447
  return;
2448
+ this.#emitContextNow();
2449
+ }
2450
+ #emitContextNow() {
2334
2451
  const usage = this.#session?.getContextUsage();
2335
2452
  if (usage?.tokens == null)
2336
2453
  return;
@@ -2406,29 +2523,28 @@ Continue the task.`);
2406
2523
  }
2407
2524
  return await this.#asks.openApproval(approvalId);
2408
2525
  }
2526
+ async ask(spec) {
2527
+ if (!this.#profile.ui.canPrompt)
2528
+ return;
2529
+ const question = { questionId: this.#asks.nextId("q"), ...spec };
2530
+ this.#emit({ type: "question", question });
2531
+ if (this.#profile.interaction.question === "suspend") {
2532
+ this.#mapper.noteOutcome("awaiting");
2533
+ return;
2534
+ }
2535
+ return await this.#asks.openQuestion(question) || undefined;
2536
+ }
2409
2537
  #ui() {
2410
2538
  return new CodeAgentUi2({
2411
- ask: async (prompt, kind, choices) => {
2412
- if (!this.#profile.ui.canPrompt)
2413
- return;
2414
- const questionId = this.#asks.nextId("q");
2415
- const question = {
2416
- questionId,
2417
- prompt,
2418
- kind,
2419
- ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2420
- freeText: kind === "text"
2421
- };
2422
- this.#emit({ type: "question", question });
2423
- if (this.#profile.interaction.question === "suspend") {
2424
- this.#mapper.noteOutcome("awaiting");
2425
- return;
2426
- }
2427
- return await this.#asks.openQuestion(question) || undefined;
2428
- },
2539
+ ask: async (prompt, kind, choices) => await this.ask({
2540
+ prompt,
2541
+ kind,
2542
+ ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2543
+ freeText: kind === "text"
2544
+ }),
2429
2545
  notify: (level, message) => this.#emit({ type: "notice", level, message })
2430
2546
  });
2431
2547
  }
2432
2548
  }
2433
2549
 
2434
- export { AkanEditScope2, DevLogFeedback2, PreviewChrome2, PreviewView2, PreviewFeedback2, TurnFeedback2, AkanCli2, AkanVerifier2, VerifyFeedback2, AkanToolPack2, MailToolPack2, akanCodePaths2, McpClient2, McpToolPack2, SessionToolPack2, WebToolPack2, AkanCodePlugins2, AkanEnvKeys2, AkanAuthStorageBackend2, akanSystemPrompt2, CodeSessionIndex2, AkanCodeServices2, CodeAgentAsks2, CodeAgentEventMapper2, CodeAgentGate2, CodeAgentUi2, CodeMailbox2, CodeSessionFork2, CodeAgent2 };
2550
+ 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 };