@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.3

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 (177) hide show
  1. package/CHANGELOG.md +95 -6
  2. package/dist/types/auto-thinking/classifier.d.ts +35 -0
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/cli/extension-flags.d.ts +36 -0
  5. package/dist/types/config/config-file.d.ts +4 -0
  6. package/dist/types/config/file-lock.d.ts +23 -0
  7. package/dist/types/config/keybindings.d.ts +2 -1
  8. package/dist/types/config/model-registry.d.ts +6 -0
  9. package/dist/types/config/settings-schema.d.ts +112 -69
  10. package/dist/types/edit/hashline/diff.d.ts +9 -3
  11. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  13. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  14. package/dist/types/eval/agent-bridge.d.ts +25 -0
  15. package/dist/types/eval/backend.d.ts +17 -2
  16. package/dist/types/eval/budget-bridge.d.ts +29 -0
  17. package/dist/types/eval/idle-timeout.d.ts +28 -0
  18. package/dist/types/eval/js/executor.d.ts +8 -0
  19. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  20. package/dist/types/eval/py/executor.d.ts +13 -0
  21. package/dist/types/exec/bash-executor.d.ts +1 -0
  22. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  23. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  24. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  25. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  26. package/dist/types/extensibility/shared-events.d.ts +2 -2
  27. package/dist/types/memory-backend/index.d.ts +1 -1
  28. package/dist/types/memory-backend/resolve.d.ts +1 -1
  29. package/dist/types/memory-backend/types.d.ts +3 -3
  30. package/dist/types/mnemopi/backend.d.ts +4 -0
  31. package/dist/types/mnemopi/config.d.ts +29 -0
  32. package/dist/types/mnemopi/state.d.ts +72 -0
  33. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  34. package/dist/types/modes/components/model-selector.d.ts +3 -2
  35. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  36. package/dist/types/modes/controllers/command-controller.d.ts +7 -0
  37. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  38. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  39. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  40. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  41. package/dist/types/modes/interactive-mode.d.ts +7 -3
  42. package/dist/types/modes/magic-keywords.d.ts +14 -0
  43. package/dist/types/modes/markdown-prose.d.ts +27 -0
  44. package/dist/types/modes/orchestrate.d.ts +7 -2
  45. package/dist/types/modes/shared.d.ts +1 -1
  46. package/dist/types/modes/theme/theme.d.ts +2 -1
  47. package/dist/types/modes/turn-budget.d.ts +18 -0
  48. package/dist/types/modes/types.d.ts +7 -3
  49. package/dist/types/modes/ultrathink.d.ts +7 -2
  50. package/dist/types/modes/workflow.d.ts +15 -0
  51. package/dist/types/sdk.d.ts +15 -4
  52. package/dist/types/session/agent-session.d.ts +59 -23
  53. package/dist/types/session/session-manager.d.ts +18 -0
  54. package/dist/types/session/session-storage.d.ts +6 -0
  55. package/dist/types/session/shake-types.d.ts +24 -0
  56. package/dist/types/task/executor.d.ts +2 -2
  57. package/dist/types/thinking.d.ts +39 -1
  58. package/dist/types/tiny/device.d.ts +3 -3
  59. package/dist/types/tiny/models.d.ts +34 -1
  60. package/dist/types/tiny/title-protocol.d.ts +4 -0
  61. package/dist/types/tools/index.d.ts +19 -3
  62. package/dist/types/tools/memory-edit.d.ts +1 -1
  63. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  64. package/package.json +10 -10
  65. package/src/auto-thinking/classifier.ts +180 -0
  66. package/src/autoresearch/tools/run-experiment.ts +45 -113
  67. package/src/cli/args.ts +39 -16
  68. package/src/cli/extension-flags.ts +48 -0
  69. package/src/cli/plugin-cli.ts +11 -2
  70. package/src/config/config-file.ts +98 -13
  71. package/src/config/file-lock.ts +60 -17
  72. package/src/config/keybindings.ts +78 -27
  73. package/src/config/model-registry.ts +7 -1
  74. package/src/config/settings-schema.ts +118 -71
  75. package/src/config/settings.ts +12 -0
  76. package/src/edit/hashline/diff.ts +87 -22
  77. package/src/edit/renderer.ts +16 -12
  78. package/src/edit/streaming.ts +17 -6
  79. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  80. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  81. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  82. package/src/eval/__tests__/shared-executors.test.ts +53 -0
  83. package/src/eval/agent-bridge.ts +295 -0
  84. package/src/eval/backend.ts +17 -2
  85. package/src/eval/budget-bridge.ts +48 -0
  86. package/src/eval/idle-timeout.ts +80 -0
  87. package/src/eval/js/executor.ts +35 -7
  88. package/src/eval/js/index.ts +2 -1
  89. package/src/eval/js/shared/local-module-loader.ts +75 -10
  90. package/src/eval/js/shared/prelude.txt +85 -1
  91. package/src/eval/js/tool-bridge.ts +9 -0
  92. package/src/eval/py/executor.ts +41 -14
  93. package/src/eval/py/index.ts +2 -1
  94. package/src/eval/py/prelude.py +132 -1
  95. package/src/exec/bash-executor.ts +2 -3
  96. package/src/extensibility/custom-tools/types.ts +2 -2
  97. package/src/extensibility/extensions/runner.ts +12 -2
  98. package/src/extensibility/plugins/git-url.ts +90 -4
  99. package/src/extensibility/plugins/manager.ts +103 -7
  100. package/src/extensibility/shared-events.ts +2 -2
  101. package/src/internal-urls/docs-index.generated.ts +88 -88
  102. package/src/main.ts +50 -56
  103. package/src/memory-backend/index.ts +1 -1
  104. package/src/memory-backend/resolve.ts +3 -3
  105. package/src/memory-backend/types.ts +3 -3
  106. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  107. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  108. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  109. package/src/modes/acp/acp-agent.ts +13 -3
  110. package/src/modes/components/agent-dashboard.ts +6 -6
  111. package/src/modes/components/custom-editor.ts +4 -11
  112. package/src/modes/components/extensions/state-manager.ts +3 -4
  113. package/src/modes/components/footer.ts +18 -12
  114. package/src/modes/components/hook-selector.ts +86 -20
  115. package/src/modes/components/model-selector.ts +20 -11
  116. package/src/modes/components/oauth-selector.ts +93 -21
  117. package/src/modes/components/omfg-panel.ts +141 -0
  118. package/src/modes/components/settings-defs.ts +9 -2
  119. package/src/modes/components/settings-selector.ts +4 -1
  120. package/src/modes/components/status-line/segments.ts +13 -5
  121. package/src/modes/components/tips.txt +2 -1
  122. package/src/modes/components/tool-execution.ts +38 -19
  123. package/src/modes/components/tree-selector.ts +4 -3
  124. package/src/modes/components/user-message-selector.ts +94 -19
  125. package/src/modes/components/user-message.ts +8 -1
  126. package/src/modes/controllers/command-controller.ts +57 -0
  127. package/src/modes/controllers/event-controller.ts +65 -3
  128. package/src/modes/controllers/input-controller.ts +14 -11
  129. package/src/modes/controllers/omfg-controller.ts +283 -0
  130. package/src/modes/controllers/omfg-rule.ts +647 -0
  131. package/src/modes/controllers/selector-controller.ts +21 -6
  132. package/src/modes/gradient-highlight.ts +23 -6
  133. package/src/modes/interactive-mode.ts +41 -7
  134. package/src/modes/magic-keywords.ts +20 -0
  135. package/src/modes/markdown-prose.ts +247 -0
  136. package/src/modes/orchestrate.ts +17 -11
  137. package/src/modes/shared.ts +3 -11
  138. package/src/modes/theme/theme.ts +6 -0
  139. package/src/modes/turn-budget.ts +31 -0
  140. package/src/modes/types.ts +7 -1
  141. package/src/modes/ultrathink.ts +16 -10
  142. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  143. package/src/modes/workflow.ts +42 -0
  144. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  145. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  146. package/src/prompts/system/omfg-user.md +51 -0
  147. package/src/prompts/system/system-prompt.md +1 -0
  148. package/src/prompts/system/workflow-notice.md +70 -0
  149. package/src/prompts/tools/eval.md +13 -1
  150. package/src/prompts/tools/memory-edit.md +1 -1
  151. package/src/sdk.ts +86 -38
  152. package/src/session/agent-session.ts +558 -80
  153. package/src/session/session-manager.ts +32 -0
  154. package/src/session/session-storage.ts +68 -8
  155. package/src/session/shake-types.ts +44 -0
  156. package/src/slash-commands/builtin-registry.ts +41 -16
  157. package/src/task/executor.ts +3 -3
  158. package/src/task/index.ts +6 -6
  159. package/src/thinking.ts +73 -1
  160. package/src/tiny/device.ts +4 -10
  161. package/src/tiny/models.ts +54 -2
  162. package/src/tiny/title-protocol.ts +11 -1
  163. package/src/tiny/worker.ts +19 -7
  164. package/src/tools/eval.ts +202 -26
  165. package/src/tools/grouped-file-output.ts +9 -2
  166. package/src/tools/index.ts +17 -5
  167. package/src/tools/memory-edit.ts +4 -4
  168. package/src/tools/memory-recall.ts +5 -5
  169. package/src/tools/memory-reflect.ts +5 -5
  170. package/src/tools/memory-retain.ts +4 -4
  171. package/src/tools/render-utils.ts +2 -1
  172. package/src/tools/search.ts +480 -76
  173. package/dist/types/mnemosyne/backend.d.ts +0 -4
  174. package/dist/types/mnemosyne/config.d.ts +0 -29
  175. package/dist/types/mnemosyne/state.d.ts +0 -72
  176. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  177. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
@@ -1,5 +1,6 @@
1
1
  import ultrathinkNotice from "../prompts/system/ultrathink-notice.md" with { type: "text" };
2
- import { createGradientHighlighter } from "./gradient-highlight";
2
+ import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
3
+ import { keywordInProse } from "./markdown-prose";
3
4
 
4
5
  /**
5
6
  * "ultrathink" keyword support, mirroring Claude Code's affordance.
@@ -7,19 +8,24 @@ import { createGradientHighlighter } from "./gradient-highlight";
7
8
  * Typing the standalone word in the input editor paints it with a rainbow
8
9
  * gradient ({@link highlightUltrathink}); submitting a message that mentions it
9
10
  * appends a hidden {@link ULTRATHINK_NOTICE} nudging the model toward careful
10
- * multi-step reasoning. Matching is word-bounded and case-insensitive, so
11
- * "ultrathinking"/"ultrathinks" never trigger either behavior.
11
+ * multi-step reasoning. Matching is whitespace-delimited and case-sensitive
12
+ * (lowercase only), so "ultrathinking", "Ultrathink", or "ultrathink.ts" never
13
+ * trigger either behavior.
12
14
  */
13
15
 
14
- // Detection: standalone keyword, any case. Non-global so `.test` stays stateless.
15
- const ULTRATHINK_WORD = /\bultrathink\b/i;
16
+ // Detection: lowercase keyword flanked by whitespace or a string edge. Non-global so `.test` stays stateless.
17
+ const ULTRATHINK_WORD = /(?<!\S)ultrathink(?!\S)/;
16
18
 
17
19
  /** Hidden system notice appended after a user message that mentions "ultrathink". */
18
20
  export const ULTRATHINK_NOTICE: string = ultrathinkNotice.trim();
19
21
 
20
- /** Whether `text` contains the standalone keyword "ultrathink" (any case). */
22
+ /**
23
+ * Whether `text` contains the standalone keyword "ultrathink" (lowercase,
24
+ * whitespace-delimited) in prose — never inside a code block, inline code span,
25
+ * or XML/HTML section.
26
+ */
21
27
  export function containsUltrathink(text: string): boolean {
22
- return ULTRATHINK_WORD.test(text);
28
+ return keywordInProse(text, ULTRATHINK_WORD);
23
29
  }
24
30
 
25
31
  /**
@@ -27,9 +33,9 @@ export function containsUltrathink(text: string): boolean {
27
33
  * Sweeps red→violet (hue 0..330), stopping short of the wrap back to red so the
28
34
  * gradient resolves smoothly regardless of casing or match length.
29
35
  */
30
- export const highlightUltrathink: (text: string) => string = createGradientHighlighter({
31
- probe: /ultrathink/i,
32
- highlight: /\bultrathink\b/gi,
36
+ export const highlightUltrathink: KeywordHighlighter = createGradientHighlighter({
37
+ probe: /ultrathink/,
38
+ highlight: /(?<!\S)ultrathink(?!\S)/g,
33
39
  stops: 14,
34
40
  hue: t => t * 330,
35
41
  });
@@ -39,7 +39,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
39
39
  `| \`${appKey(bindings, "app.suspend")}\` | Suspend to background |`,
40
40
  `| \`${appKey(bindings, "app.thinking.cycle")}\` | Cycle thinking level |`,
41
41
  `| \`${appKey(bindings, "app.model.cycleForward")}\` | Cycle role models (slow/default/smol) |`,
42
- `| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle role models (temporary) |`,
42
+ `| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle role models (backward) |`,
43
43
  `| \`${appKey(bindings, "app.model.selectTemporary")}\` | Select model (temporary) |`,
44
44
  `| \`${appKey(bindings, "app.model.select")}\` | Select model (set roles) |`,
45
45
  `| \`${appKey(bindings, "app.plan.toggle")}\` | Toggle plan mode |`,
@@ -0,0 +1,42 @@
1
+ import workflowNotice from "../prompts/system/workflow-notice.md" with { type: "text" };
2
+ import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
3
+ import { keywordInProse } from "./markdown-prose";
4
+
5
+ /**
6
+ * "workflow" keyword support.
7
+ *
8
+ * Typing the standalone word in the input editor paints it with a warm
9
+ * amber→green gradient ({@link highlightWorkflow}); submitting a message that
10
+ * mentions it appends a hidden {@link WORKFLOW_NOTICE} that steers the model to
11
+ * author a deterministic multi-subagent workflow in eval cells (agent/parallel/
12
+ * pipeline). Matching is whitespace-delimited and case-sensitive (lowercase
13
+ * only) — "workflow"/"workflows" trigger, but "workflowed", "Workflow", and
14
+ * "workflow.ts" never do.
15
+ */
16
+
17
+ // Detection: lowercase keyword (singular or plural) flanked by whitespace or a string edge. Non-global so `.test` stays stateless.
18
+ const WORKFLOW_WORD = /(?<!\S)workflows?(?!\S)/;
19
+
20
+ /** Hidden system notice appended after a user message that mentions "workflow". */
21
+ export const WORKFLOW_NOTICE: string = workflowNotice.trim();
22
+
23
+ /**
24
+ * Whether `text` contains the standalone keyword "workflow"/"workflows"
25
+ * (lowercase, whitespace-delimited) in prose — never inside a code block, inline
26
+ * code span, or XML/HTML section.
27
+ */
28
+ export function containsWorkflow(text: string): boolean {
29
+ return keywordInProse(text, WORKFLOW_WORD);
30
+ }
31
+
32
+ /**
33
+ * Highlight every standalone "workflow"/"workflows" in `text` for editor display
34
+ * with a warm amber→green gradient (hue 30..150), visually distinct from
35
+ * ultrathink's rainbow and orchestrate's teal→violet.
36
+ */
37
+ export const highlightWorkflow: KeywordHighlighter = createGradientHighlighter({
38
+ probe: /workflow/,
39
+ highlight: /(?<!\S)workflows?(?!\S)/g,
40
+ stops: 14,
41
+ hue: t => 30 + t * 120,
42
+ });
@@ -0,0 +1,14 @@
1
+ Classify the difficulty of the coding request below into one bucket, by how much reasoning it needs.
2
+
3
+ Buckets:
4
+
5
+ - trivial — obvious, mechanical, or a direct question (rename, typo, one-liner, simple lookup).
6
+ - moderate — a real but localized task (a small feature, a normal bug fix, explaining code).
7
+ - hard — deep, multi-file, ambiguous, or tricky debugging or design.
8
+
9
+ Reply with exactly one word: trivial, moderate, or hard.
10
+
11
+ Request:
12
+ {{prompt}}
13
+
14
+ Answer:
@@ -0,0 +1,12 @@
1
+ You are a difficulty classifier for a coding agent. Read the user's request and decide how much reasoning effort the agent should spend on it this turn.
2
+
3
+ Reply with exactly one word — one of: `low`, `medium`, `high`, `xhigh`. No punctuation, no explanation, no other text.
4
+
5
+ Levels:
6
+
7
+ - `low` — Trivial or mechanical. A rename, a typo, a one-line edit, a formatting tweak, a direct factual question, or a request whose solution is obvious.
8
+ - `medium` — A localized change that needs some reasoning. A small self-contained feature, a straightforward bug fix in one place, or explaining a moderate piece of code.
9
+ - `high` — A non-trivial change. Spans multiple files or callers, requires real debugging, a moderate design decision, or a refactor with several moving parts.
10
+ - `xhigh` — Deep or open-ended. Subtle concurrency or algorithmic problems, cross-system reasoning, ambiguous requirements, large or risky refactors, or hard root-cause debugging.
11
+
12
+ Judge the inherent difficulty of the task, not how politely or verbosely it is phrased. When torn between two levels, choose the lower one.
@@ -0,0 +1,51 @@
1
+ <omfg>
2
+ The user is frustrated about recurring agent behavior.
3
+ Author ONE Time Traveling Stream Rule (TTSR) that would have caught the offending behavior earlier in this conversation.
4
+
5
+ TTSR mechanics:
6
+ - A rule is a markdown file with YAML frontmatter.
7
+ - `condition` is one or more JavaScript regex patterns tested against assistant streamed output.
8
+ - `scope` is a comma-separated allowlist. If present, only listed streams are checked.
9
+ - `text` = assistant prose only. `thinking` = hidden reasoning summaries. `tool` = every tool's arguments.
10
+ - `tool:<name>(<glob>)` = one tool, only when path-like args match the glob. Examples: `tool:write(*.rb)`, `tool:edit(*.ts)`.
11
+ - Prefer file-specific tool scopes for code complaints. Ruby code generated through `write` should use `tool:write(*.rb)`, not bare `tool` or `text`.
12
+ - Tool arguments may be serialized while streaming. Conditions for code containing quotes should tolerate JSON escaping when needed.
13
+ - When `condition` matches within `scope`, the stream is interrupted and the markdown body is injected as correction guidance.
14
+ - `description` is a one-line summary.
15
+
16
+ Output contract:
17
+ - Emit exactly one JSON object and nothing else.
18
+ - JSON fields: `name`, `description`, `condition`, `scope`, `body`.
19
+ - `name` MUST be kebab-case.
20
+ - `description` MUST be a one-line summary.
21
+ - `condition` MUST be a string or string array of JavaScript regex patterns.
22
+ - `condition` MUST match the specific offending assistant output visible earlier in this conversation.
23
+ - Escape regex backslashes for JSON exactly once: use `"\\beval\\s*\\("`, NEVER `"\\\\beval\\\\s*\\\\("`.
24
+ - Keep `condition` precise; NEVER use broad catch-alls.
25
+ - `scope` MUST be a string or string array.
26
+ - Keep `scope` as narrow as the complaint allows. NEVER use `tool, text` unless the same bad behavior occurred in both tool arguments and assistant prose.
27
+ - `body` MUST be markdown guidance explaining the right behavior concisely.
28
+ - The caller assembles YAML frontmatter. NEVER emit markdown frontmatter or a fenced code block around the JSON.
29
+
30
+ Example shape:
31
+ {
32
+ "name": "ts-no-any",
33
+ "description": "Never use `any` in TypeScript — use `unknown`, a generic, or the real type",
34
+ "condition": ": any|as any",
35
+ "scope": ["tool:edit(*.ts)", "tool:edit(*.tsx)", "tool:write(*.ts)", "tool:write(*.tsx)"],
36
+ "body": "Never use `: any` or `as any`. Use `unknown`, a domain type, a generic, or a type guard."
37
+ }
38
+
39
+ Complaint:
40
+ {{complaint}}
41
+
42
+ {{#if feedback}}
43
+ Failed attempts or requested amendments so far:
44
+ {{feedback}}
45
+
46
+ Latest candidate JSON:
47
+ {{previousRule}}
48
+
49
+ Regenerate one corrected rule. Fix the listed validation failures or user amendment; do not repeat failed scopes or conditions.
50
+ {{/if}}
51
+ </omfg>
@@ -41,6 +41,7 @@ Assumptions you didn't validate: incidents to debug.
41
41
  - Even if it was true, start, as if it was not. It's the only way to make progress.
42
42
  - Execute the work or delegate it.
43
43
  - You NEVER speculate about scope inflation ("this is actually a multi-week effort"). You have no comprehension of time, so stop pretending.
44
+ - You NEVER re-audit an applied edit, nor run `git status`/`git diff` as routine validation — the edit result, tests, and LSP ARE your verification. Exception: explicit request, protecting unrelated changes, or before commit/revert/reset/stash/delete.
44
45
  </critical>
45
46
 
46
47
  [ENV]
@@ -0,0 +1,70 @@
1
+ <system-notice>
2
+ The user's message above contains the **workflow** keyword: drive this task as a deterministic multi-subagent workflow. Author the orchestration as Python in the `eval` tool and fan out subagents — to be comprehensive (decompose and cover in parallel), to be confident (independent perspectives and adversarial checks before you commit), or to take on scale one context can't hold (audits, migrations, broad sweeps). This overrides any default tendency to do the whole task inline when fanning out would be more thorough.
3
+
4
+ <when>
5
+ Worth it when the task benefits from decomposition + parallel coverage, or from independent/adversarial cross-checking before you commit. For a quick lookup or single edit, just do it directly — don't spin up agents. Scout inline FIRST (list the files, scope the diff, find the call sites) to discover the work-list, then fan out over it — you don't need to know the shape before the *task*, only before the *fan-out*. Common shapes, each a well-scoped `eval` call you can chain across turns:
6
+ - **Understand** — parallel readers over subsystems → structured map
7
+ - **Design** — judge panel of N independent approaches → scored synthesis
8
+ - **Review** — split into dimensions → find per dimension → adversarially verify each finding
9
+ - **Research** — multi-modal sweep → deep-read the hits → synthesize
10
+ - **Migrate** — discover sites → transform each → verify
11
+ </when>
12
+
13
+ <helpers>
14
+ State persists across cells, so scout in one cell and fan out in the next. Every cell has:
15
+
16
+ - `agent(prompt, *, agent_type="task", model=None, context=None, label=None, schema=None)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent_type` picks a discovered agent ("explore", "reviewer", "oracle", …); `context` is shared background; `label` names the artifact. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes; eval-spawned agents nest at most 3 deep.
17
+ - `parallel(thunks, *, concurrency=4)` — run zero-arg callables concurrently through a bounded pool (default 4, max 16), preserving input order; returns once all finish. A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
18
+ - `pipeline(items, *stages, concurrency=4)` — map items through `stages` left-to-right. There is a BARRIER between stages: ALL items clear stage N before stage N+1 begins. Each stage is a one-arg callable; stage 1 gets the original item, later stages get the previous result.
19
+ - `llm(prompt, *, model="default", system=None, schema=None)` — oneshot, stateless model call (no tools, no history). Tiers: "smol", "default", "slow". Cheap classification/scoring inside a fan-out.
20
+ - `log(message)` — emit a progress line above the status tree. `phase(title)` — start a phase; the status lines that follow group under it.
21
+ - `budget` — `budget.total` (output-token ceiling, or `None` when none is set), `budget.spent()` (tokens spent this turn — main loop + eval subagents), `budget.remaining()` (`math.inf` when total is `None`), `budget.hard` (whether it's enforced). A ceiling is set by the user: `+Nk` in their message is advisory (you self-limit via `budget.remaining()`), `+Nk!` (or Goal Mode) is hard — `agent()` refuses to spawn once spent reaches it. Gate loops on `budget.total` first, since it's `None` when the user set no budget.
22
+
23
+ Everything runs INLINE and synchronously inside the eval call — no background mode, no resume, no separate progress app. Each eval call is one well-scoped fan-out; chain several across cells and turns for multi-phase work, reading each result before you decide the next phase.
24
+ </helpers>
25
+
26
+ <structure>
27
+ For independent per-item chains (review → verify, fetch → extract → score), wrap the WHOLE chain in one function and run it with `parallel()` — then each item flows through its own steps without waiting on the others:
28
+
29
+ DIMENSIONS = [{"key": "bugs", "prompt": "…"}, {"key": "perf", "prompt": "…"}]
30
+ def review_and_verify(d):
31
+ found = agent(d["prompt"], label=f"review:{d['key']}", schema=FINDINGS_SCHEMA)
32
+ return parallel([lambda f=f: {**f, "verdict": agent(
33
+ f"Refute if you can (default refuted when unsure): {f['title']}",
34
+ label=f"verify:{f['file']}", schema=VERDICT_SCHEMA)} for f in found["findings"]])
35
+ phase("Review")
36
+ results = parallel([lambda d=d: review_and_verify(d) for d in DIMENSIONS])
37
+ confirmed = [f for group in results for f in group if f["verdict"]["is_real"]]
38
+
39
+ Reach for `pipeline()` only when a stage genuinely needs ALL of the previous stage first — dedup/merge across the whole set, early-exit on zero, or "compare against the other findings" — because its inter-stage barrier makes every item wait for the slowest peer:
40
+
41
+ phase("Find")
42
+ found = parallel([lambda d=d: agent(d["prompt"], schema=FINDINGS_SCHEMA) for d in DIMENSIONS])
43
+ findings = dedupe([f for r in found for f in r["findings"]]) # needs everything at once
44
+ phase("Verify")
45
+ verdicts = parallel([lambda f=f: agent(verify_prompt(f), schema=VERDICT_SCHEMA) for f in findings])
46
+
47
+ Don't add a barrier just to flatten/map/filter — do that with plain Python between calls. Nested `parallel()` pools each cap independently, so keep total fan-out sane.
48
+ </structure>
49
+
50
+ <patterns>
51
+ Compose the harness the task calls for:
52
+ - **Adversarial verify** — N independent skeptics per finding, each prompted to REFUTE; keep it only if a majority survive. `votes = parallel([lambda i=i: agent(f"Refute: {claim}. refuted=true if unsure.", schema=VERDICT) for i in range(3)])`, then keep when `sum(not v["refuted"] for v in votes) ≥ 2`.
53
+ - **Perspective-diverse verify** — give each verifier a distinct lens (correctness, security, perf, does-it-reproduce) instead of N identical refuters.
54
+ - **Judge panel** — N attempts from different angles, scored by parallel judges; synthesize from the winner, graft the best of the rest.
55
+ - **Loop-until-dry** — for unknown-size discovery, keep spawning finders until K consecutive rounds surface nothing new; dedup against everything SEEN, not just what was confirmed, or it never converges.
56
+ - **Multi-modal sweep** — parallel finders each searching a different way (by-container, by-content, by-entity, by-time), each blind to the others.
57
+ - **Completeness critic** — a final agent that asks "what's missing — modality not run, claim unverified, file unread?"; its answer is the next round.
58
+ - **Budget/count loops** — `while len(bugs) < 10:` to hit a target, or `while budget.total and budget.remaining() > 50_000:` to scale depth to the turn budget; `log()` each round.
59
+ - **No silent caps** — if you bound coverage (top-N, no-retry, sampling), `log()` what you dropped; silent truncation reads as "covered everything" when it didn't.
60
+
61
+ Scale to the ask: "find any bugs" → a few finders, single-vote verify. "thoroughly audit / be comprehensive" → larger finder pool, 3–5-vote adversarial pass, a synthesis stage.
62
+ </patterns>
63
+
64
+ <execution>
65
+ - Decompose the surface first; capture it in `todo_write` when it spans phases.
66
+ - Prefer `schema=` for any agent whose output you branch on.
67
+ - After a fan-out returns, YOU own correctness: read the artifacts, run the gate, verify before acting. Subagents do the legwork; they don't get the last word.
68
+ - Keep going until the task is closed — a returned fan-out is a step, not a stopping point.
69
+ </execution>
70
+ </system-notice>
@@ -8,7 +8,7 @@ Cell fields:
8
8
  - `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
9
9
  - `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
10
10
  - `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`).
11
- - `timeout` (optional) — per-cell timeout in seconds (1-600). Default 30.
11
+ - `timeout` (optional) — per-cell **inactivity** budget in seconds (1-600). Default 30. The cell is interrupted only after this long with no progress, and every status event (`agent()` updates, `log()`/`phase()`, tool activity) resets the clock — so a long `agent()`/`parallel()` fanout that keeps reporting progress is not killed. Raw `print`/stdout does not reset it; raise `timeout` for a cell that runs long without emitting status.
12
12
  - `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
13
13
 
14
14
  **Work incrementally:**
@@ -46,6 +46,18 @@ tool.<name>(args) → unknown
46
46
  Invoke any session tool by name. `args` is the tool's parameter object.
47
47
  llm(prompt, model?="default", system?=None, schema?=None) → str | dict
48
48
  Oneshot, stateless LLM call (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text.
49
+ agent(prompt, agent_type?="task", model?=None, context?=None, label?=None, schema?=None) → str | dict
50
+ Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back.
51
+ parallel(thunks, concurrency?=4) → list
52
+ Run thunks (callables) through a bounded pool (default 4, max 16), preserving input order. Barrier: returns once all finish; a thunk that throws propagates.
53
+ pipeline(items, ...stages, concurrency?=4) → list
54
+ Map each item through stages left-to-right; a barrier runs between stages (every item clears stage N before stage N+1). Each stage is a one-arg callable: stage 1 gets the original item, later stages get the previous result.
55
+ log(message) → None
56
+ Emit a progress line above the status tree.
57
+ phase(title) → None
58
+ Start a phase; the status lines that follow group under it.
59
+ budget → per-turn token budget
60
+ {{#if py}}`budget.total` (ceiling or None), `budget.spent()` (output tokens this turn), `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} A ceiling is set by a `+Nk` message directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise total is None/null and spend is still tracked across the turn (main loop + eval subagents).
49
61
  ```
50
62
  </prelude>
51
63
 
@@ -1,4 +1,4 @@
1
- Edit Mnemosyne long-term memories by id.
1
+ Edit Mnemopi long-term memories by id.
2
2
 
3
3
  Use only with ids returned by the `recall` tool. Operations:
4
4
  - `update`: replace content and/or importance for a working memory.
package/src/sdk.ts CHANGED
@@ -88,7 +88,7 @@ import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls
88
88
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
89
89
  import { discoverAndLoadMCPTools, MCPManager, type MCPToolsLoadResult } from "./mcp";
90
90
  import { resolveMemoryBackend } from "./memory-backend";
91
- import { getMnemosyneSessionState, type MnemosyneSessionState } from "./mnemosyne/state";
91
+ import { getMnemopiSessionState, type MnemopiSessionState } from "./mnemopi/state";
92
92
  import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
93
93
  import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
94
94
  import {
@@ -112,7 +112,14 @@ import {
112
112
  loadProjectContextFiles as loadContextFilesInternal,
113
113
  } from "./system-prompt";
114
114
  import { AgentOutputManager } from "./task/output-manager";
115
- import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
115
+ import {
116
+ AUTO_THINKING,
117
+ type ConfiguredThinkingLevel,
118
+ parseThinkingLevel,
119
+ resolveProvisionalAutoLevel,
120
+ resolveThinkingLevelForModel,
121
+ toReasoningEffort,
122
+ } from "./thinking";
116
123
  import {
117
124
  collectDiscoverableTools,
118
125
  type DiscoverableTool,
@@ -254,7 +261,7 @@ export interface CreateAgentSessionOptions {
254
261
  * Used when model lookup is deferred because extension-provided models aren't registered yet. */
255
262
  modelPattern?: string;
256
263
  /** Thinking selector. Default: from settings, else unset */
257
- thinkingLevel?: ThinkingLevel;
264
+ thinkingLevel?: ConfiguredThinkingLevel;
258
265
  /** Models available for cycling (Ctrl+P in interactive mode) */
259
266
  scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
260
267
 
@@ -314,8 +321,8 @@ export interface CreateAgentSessionOptions {
314
321
  taskDepth?: number;
315
322
  /** Parent Hindsight state to alias for subagent memory tools. */
316
323
  parentHindsightSessionState?: HindsightSessionState;
317
- /** Parent Mnemosyne state to alias for subagent memory tools. */
318
- parentMnemosyneSessionState?: MnemosyneSessionState;
324
+ /** Parent Mnemopi state to alias for subagent memory tools. */
325
+ parentMnemopiSessionState?: MnemopiSessionState;
319
326
  /** Pre-allocated agent identity for IRC routing. Default: "0-Main" for top-level, parentTaskPrefix-derived for sub. */
320
327
  agentId?: string;
321
328
  /** Display name for the agent in IRC. Default: "main" or "sub". */
@@ -457,6 +464,44 @@ export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsRe
457
464
  return discoverAndLoadExtensions([], resolvedCwd);
458
465
  }
459
466
 
467
+ /**
468
+ * Load the discovered/configured extensions for a session — everything {@link
469
+ * createAgentSession} would load except the inline factory extensions it appends
470
+ * itself. Extracted so the CLI can resolve extension-registered flags (and thus
471
+ * classify `@file` arguments extension-aware) *before* a session — and its
472
+ * terminal breadcrumb — is created, then hand the result back through
473
+ * {@link CreateAgentSessionOptions.preloadedExtensions} so the work is not
474
+ * repeated. Keep this the single source of the discovery branch logic.
475
+ */
476
+ export async function loadSessionExtensions(
477
+ options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">,
478
+ cwd: string,
479
+ settings: Settings,
480
+ eventBus: EventBus,
481
+ ): Promise<LoadExtensionsResult> {
482
+ let result: LoadExtensionsResult;
483
+ if (options.disableExtensionDiscovery) {
484
+ const configuredPaths = options.additionalExtensionPaths ?? [];
485
+ result = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
486
+ } else {
487
+ // Merge CLI extension paths with settings extension paths.
488
+ const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
489
+ const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
490
+ result = await logger.time(
491
+ "discoverAndLoadExtensions",
492
+ discoverAndLoadExtensions,
493
+ configuredPaths,
494
+ cwd,
495
+ eventBus,
496
+ disabledExtensionIds,
497
+ );
498
+ }
499
+ for (const { path, error } of result.errors) {
500
+ logger.error("Failed to load extension", { path, error });
501
+ }
502
+ return result;
503
+ }
504
+
460
505
  /**
461
506
  * Discover skills from cwd and agentDir.
462
507
  */
@@ -1013,10 +1058,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1013
1058
  if (thinkingLevel === undefined) {
1014
1059
  thinkingLevel = settings.get("defaultThinkingLevel");
1015
1060
  }
1061
+ const autoThinking = thinkingLevel === AUTO_THINKING;
1062
+ // Concrete level the agent/session start with. With `auto` this is the
1063
+ // provisional level shown until the first per-turn classification resolves;
1064
+ // `auto` itself stays a session-only concept handled by AgentSession.
1065
+ let effectiveThinkingLevel: ThinkingLevel | undefined = thinkingLevel === AUTO_THINKING ? undefined : thinkingLevel;
1016
1066
  if (model) {
1017
1067
  const resolvedModel = model;
1018
- thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
1019
- resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
1068
+ effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
1069
+ autoThinking
1070
+ ? resolveProvisionalAutoLevel(resolvedModel)
1071
+ : resolveThinkingLevelForModel(resolvedModel, effectiveThinkingLevel),
1020
1072
  );
1021
1073
  // Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
1022
1074
  // with the rest of session setup (extension/skill load, tool registry,
@@ -1179,7 +1231,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1179
1231
  session ? session.trackEvalExecution(execution, abortController) : execution,
1180
1232
  getSessionId: () => sessionManager.getSessionId?.() ?? null,
1181
1233
  getHindsightSessionState: () => session?.getHindsightSessionState(),
1182
- getMnemosyneSessionState: () => getMnemosyneSessionState(session),
1234
+ getMnemopiSessionState: () => getMnemopiSessionState(session),
1183
1235
  getAgentId: () => resolvedAgentId,
1184
1236
  getToolByName: name => session?.getToolByName(name),
1185
1237
  agentRegistry,
@@ -1189,6 +1241,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1189
1241
  getPlanModeState: () => session?.getPlanModeState(),
1190
1242
  getGoalModeState: () => session?.getGoalModeState(),
1191
1243
  getGoalRuntime: () => session?.goalRuntime,
1244
+ getUsageStatistics: () => sessionManager.getUsageStatistics(),
1245
+ getTurnBudget: () => sessionManager.getTurnBudget(),
1246
+ recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
1192
1247
  getClientBridge: () => session?.clientBridge,
1193
1248
  getCompactContext: () => session.formatCompactContext(),
1194
1249
  getTodoPhases: () => session.getTodoPhases(),
@@ -1248,10 +1303,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1248
1303
  setActiveRules([...rulebookRules, ...alwaysApplyRules]);
1249
1304
  if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
1250
1305
  }
1306
+ const localProtocolOptions = options.localProtocolOptions ?? {
1307
+ getArtifactsDir,
1308
+ getSessionId: () => sessionManager.getSessionId?.() ?? null,
1309
+ };
1251
1310
  if (options.localProtocolOptions) {
1252
1311
  LocalProtocolHandler.setOverride(options.localProtocolOptions);
1253
1312
  }
1254
1313
  toolSession.getArtifactsDir = getArtifactsDir;
1314
+ toolSession.localProtocolOptions = localProtocolOptions;
1255
1315
  toolSession.agentOutputManager = new AgentOutputManager(
1256
1316
  getArtifactsDir,
1257
1317
  options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
@@ -1262,6 +1322,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1262
1322
 
1263
1323
  // Discover MCP tools from .mcp.json files
1264
1324
  let mcpManager: MCPManager | undefined = options.mcpManager;
1325
+ toolSession.mcpManager = mcpManager;
1265
1326
  const enableMCP = options.enableMCP ?? true;
1266
1327
  const customTools: CustomTool[] = [];
1267
1328
  if (enableMCP && !mcpManager) {
@@ -1280,6 +1341,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1280
1341
  authStorage,
1281
1342
  });
1282
1343
  mcpManager = mcpResult.manager;
1344
+ toolSession.mcpManager = mcpManager;
1283
1345
 
1284
1346
  if (settings.get("mcp.notifications")) {
1285
1347
  mcpManager.setNotificationsEnabled(true);
@@ -1343,32 +1405,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1343
1405
  inlineExtensions.push(createCustomToolsExtension(customTools));
1344
1406
  }
1345
1407
 
1346
- // Load extensions (discovers from standard locations + configured paths)
1347
- let extensionsResult: LoadExtensionsResult;
1348
- if (options.disableExtensionDiscovery) {
1349
- const configuredPaths = options.additionalExtensionPaths ?? [];
1350
- extensionsResult = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
1351
- for (const { path, error } of extensionsResult.errors) {
1352
- logger.error("Failed to load extension", { path, error });
1353
- }
1354
- } else if (options.preloadedExtensions) {
1355
- extensionsResult = options.preloadedExtensions;
1356
- } else {
1357
- // Merge CLI extension paths with settings extension paths
1358
- const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
1359
- const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
1360
- extensionsResult = await logger.time(
1361
- "discoverAndLoadExtensions",
1362
- discoverAndLoadExtensions,
1363
- configuredPaths,
1364
- cwd,
1365
- eventBus,
1366
- disabledExtensionIds,
1367
- );
1368
- for (const { path, error } of extensionsResult.errors) {
1369
- logger.error("Failed to load extension", { path, error });
1370
- }
1371
- }
1408
+ // Load extensions. A preloaded result (e.g. resolved by the CLI before
1409
+ // session creation so it can classify `@file` args extension-aware without
1410
+ // a session/breadcrumb existing yet) is reused as-is; otherwise discover now
1411
+ // through the shared helper. Preloaded wins over `disableExtensionDiscovery`
1412
+ // because the preloaded result already reflects that choice — re-running the
1413
+ // loader here would double-load.
1414
+ const extensionsResult: LoadExtensionsResult =
1415
+ options.preloadedExtensions ?? (await loadSessionExtensions(options, cwd, settings, eventBus));
1372
1416
 
1373
1417
  // Load inline extensions from factories
1374
1418
  if (inlineExtensions.length > 0) {
@@ -1838,7 +1882,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1838
1882
  initialState: {
1839
1883
  systemPrompt,
1840
1884
  model,
1841
- thinkingLevel: toReasoningEffort(thinkingLevel),
1885
+ thinkingLevel: toReasoningEffort(effectiveThinkingLevel),
1842
1886
  tools: initialTools,
1843
1887
  },
1844
1888
  convertToLlm: convertToLlmFinal,
@@ -1945,7 +1989,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1945
1989
  if (model) {
1946
1990
  sessionManager.appendModelChange(`${model.provider}/${model.id}`);
1947
1991
  }
1948
- sessionManager.appendThinkingLevelChange(thinkingLevel);
1992
+ if (!autoThinking) {
1993
+ // Do not write the `auto` selector before the first turn resolves; auto
1994
+ // classification persists its concrete effort once a real user turn runs.
1995
+ sessionManager.appendThinkingLevelChange(effectiveThinkingLevel);
1996
+ }
1949
1997
  if (initialServiceTier) {
1950
1998
  sessionManager.appendServiceTierChange(initialServiceTier);
1951
1999
  }
@@ -1953,7 +2001,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1953
2001
 
1954
2002
  session = new AgentSession({
1955
2003
  agent,
1956
- thinkingLevel,
2004
+ thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
1957
2005
  sessionManager,
1958
2006
  settings,
1959
2007
  evalKernelOwnerId,
@@ -2114,7 +2162,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2114
2162
  agentDir,
2115
2163
  taskDepth,
2116
2164
  parentHindsightSessionState: options.parentHindsightSessionState,
2117
- parentMnemosyneSessionState: options.parentMnemosyneSessionState,
2165
+ parentMnemopiSessionState: options.parentMnemopiSessionState,
2118
2166
  }),
2119
2167
  ),
2120
2168
  );