akernel-runtime 0.1.7__tar.gz → 0.1.9__tar.gz

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 (99) hide show
  1. akernel_runtime-0.1.9/.github/release-notes/v0.1.8.md +14 -0
  2. akernel_runtime-0.1.9/.github/release-notes/v0.1.9.md +14 -0
  3. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CHANGELOG.md +22 -0
  4. {akernel_runtime-0.1.7/src/akernel_runtime.egg-info → akernel_runtime-0.1.9}/PKG-INFO +5 -3
  5. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/README.md +4 -2
  6. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/package.json +1 -1
  7. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/pyproject.toml +1 -1
  8. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9/src/akernel_runtime.egg-info}/PKG-INFO +5 -3
  9. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/SOURCES.txt +2 -0
  10. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/__init__.py +1 -1
  11. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/cli.py +272 -60
  12. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/tests/test_runtime.py +50 -2
  13. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.env.example +0 -0
  14. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/pull_request_template.md +0 -0
  17. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.0.md +0 -0
  18. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.1.md +0 -0
  19. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.2.md +0 -0
  20. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.3.md +0 -0
  21. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.4.md +0 -0
  22. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.5.md +0 -0
  23. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.6.md +0 -0
  24. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.7.md +0 -0
  25. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/workflows/ci.yml +0 -0
  26. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/workflows/release.yml +0 -0
  27. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CODE_OF_CONDUCT.md +0 -0
  28. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CONTRIBUTING.md +0 -0
  29. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/LICENSE +0 -0
  30. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/MANIFEST.in +0 -0
  31. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/NOTICE +0 -0
  32. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/SECURITY.md +0 -0
  33. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/00-vision.md +0 -0
  34. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/01-architecture.md +0 -0
  35. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/02-execution-plan.md +0 -0
  36. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/03-cli-mvp.md +0 -0
  37. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/04-evaluation.md +0 -0
  38. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/05-local-wake.md +0 -0
  39. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/06-skill-compiler.md +0 -0
  40. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/07-release-and-ci.md +0 -0
  41. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/08-open-source-plan.md +0 -0
  42. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/09-product-roadmap.md +0 -0
  43. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/10-benchmark-evidence.md +0 -0
  44. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/11-publishing-setup.md +0 -0
  45. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/01-routing.json +0 -0
  46. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/02-memory.json +0 -0
  47. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/03-budget-profiles.json +0 -0
  48. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/01-context-pressure.json +0 -0
  49. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/02-agent-editing.json +0 -0
  50. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/03-global-memory-marketplace.json +0 -0
  51. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/evals/phase2.json +0 -0
  52. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/marketplace/skills/index.json +0 -0
  53. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/context_budget.json +0 -0
  54. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/edit_file.json +0 -0
  55. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/markdown/context_budget.md +0 -0
  56. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/README.md +0 -0
  57. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/bin/akernel.js +0 -0
  58. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/scripts/install_remote.ps1 +0 -0
  59. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/scripts/release_check.ps1 +0 -0
  60. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.cfg +0 -0
  61. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.cmd +0 -0
  62. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.ps1 +0 -0
  63. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/dependency_links.txt +0 -0
  64. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/entry_points.txt +0 -0
  65. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/requires.txt +0 -0
  66. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/top_level.txt +0 -0
  67. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/__main__.py +0 -0
  68. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/agent_reports.py +0 -0
  69. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/benchmarks.py +0 -0
  70. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/budget.py +0 -0
  71. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/context.py +0 -0
  72. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/evals.py +0 -0
  73. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/global_memory.py +0 -0
  74. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/loop.py +0 -0
  75. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace.py +0 -0
  76. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_budget.json +0 -0
  77. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_compaction.json +0 -0
  78. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/edit_file.json +0 -0
  79. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/index.json +0 -0
  80. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/long_task_planning.json +0 -0
  81. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/multi_file_bugfix.json +0 -0
  82. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/memory.py +0 -0
  83. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/models.py +0 -0
  84. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/planner.py +0 -0
  85. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/policy.py +0 -0
  86. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/project.py +0 -0
  87. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/providers.py +0 -0
  88. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/report_costs.py +0 -0
  89. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/runner.py +0 -0
  90. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/skills.py +0 -0
  91. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/state_writer.py +0 -0
  92. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/storage.py +0 -0
  93. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tasks.py +0 -0
  94. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/text.py +0 -0
  95. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tokenizer.py +0 -0
  96. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tools.py +0 -0
  97. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/verifier.py +0 -0
  98. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/wake.cmd +0 -0
  99. {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/wake.ps1 +0 -0
@@ -0,0 +1,14 @@
1
+ # Context Kernel v0.1.8
2
+
3
+ This patch fixes the default TUI rendering rhythm so chat turns no longer flood the terminal with repeated full-screen layouts.
4
+
5
+ ## Highlights
6
+
7
+ - The startup workspace view still appears once.
8
+ - Each normal user message now appends only the user turn, compact status, and assistant result.
9
+ - Full redraw remains available through `/clear` or `AKERNEL_ALT_SCREEN=1`.
10
+
11
+ ## Verification
12
+
13
+ - Added regression coverage to ensure normal TUI turns do not repeatedly print the full header.
14
+ - Ran the full Python test suite.
@@ -0,0 +1,14 @@
1
+ # Context Kernel v0.1.9
2
+
3
+ This release polishes the default `akernel` chat experience and adds more Claude/Gemini-style terminal agent workflows.
4
+
5
+ ## Highlights
6
+
7
+ - Reworked the default TUI startup into a quieter chat-first header while keeping the full cockpit dashboard available with `AKERNEL_ALT_SCREEN=1`.
8
+ - Added inline `@path` attachment inside normal chat tasks, so exact workspace files can be referenced without a separate attach command.
9
+ - Added project and user Markdown slash commands under `.akernel/commands` and `~/.akernel/commands`, with `/commands` discovery and completion.
10
+ - Improved `/` and `@` completion so suggestions work on the current cursor token, including inline text such as `inspect @README`.
11
+
12
+ ## Validation
13
+
14
+ - `python -m unittest discover -s tests`
@@ -8,6 +8,28 @@ The project follows a pragmatic pre-1.0 changelog: breaking changes may occur, b
8
8
 
9
9
  No changes yet.
10
10
 
11
+ ## 0.1.9 - 2026-05-14
12
+
13
+ ### Added
14
+
15
+ - Added inline `@path` attachment inside normal chat tasks, so exact workspace files can be referenced without a separate attach command.
16
+ - Added project and user Markdown slash commands under `.akernel/commands` and `~/.akernel/commands`, with `/commands` discovery and completion.
17
+
18
+ ### Changed
19
+
20
+ - Reworked the default TUI startup into a quieter chat-first header while keeping the full cockpit dashboard available with `AKERNEL_ALT_SCREEN=1`.
21
+ - Improved command and file completion so `/` and `@` work on the current cursor token, including inline text such as `inspect @README`.
22
+
23
+ ## 0.1.8 - 2026-05-14
24
+
25
+ ### Changed
26
+
27
+ - Changed the default TUI loop to render the full workspace view once, then append only incremental user/status/assistant updates for each turn.
28
+
29
+ ### Fixed
30
+
31
+ - Prevented normal chat turns from repeatedly printing the full fixed UI layout and filling the terminal scrollback.
32
+
11
33
  ## 0.1.7 - 2026-05-14
12
34
 
13
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akernel-runtime
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Agent Kernel: a CLI-first context-native agent runtime prototype.
5
5
  Author: Context Kernel contributors
6
6
  License-Expression: Apache-2.0
@@ -135,7 +135,7 @@ akernel
135
135
 
136
136
  ### npm Launcher
137
137
 
138
- The npm launcher is prepared for publication. After `@context-akernel/akernel` is published, it will provide a Node-style entrypoint and bootstrap the Python package if it is missing:
138
+ The npm launcher is published as `@context-akernel/akernel`. It provides a Node-style entrypoint and bootstraps the Python package if it is missing:
139
139
 
140
140
  ```powershell
141
141
  npm install -g @context-akernel/akernel
@@ -155,7 +155,9 @@ akernel --ui tui
155
155
  akernel --provider mock
156
156
  ```
157
157
 
158
- Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the full-screen terminal UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification. Use `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. The shell UI renders a cockpit-style dashboard with a status header, command strip, transcript, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
158
+ Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the polished terminal chat UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification.
159
+
160
+ The default chat UI is scrollback-friendly: it shows a concise session header once, then appends only the user turn, compact run status, and assistant result. Type `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. Type `/` for command completion, `@` for workspace file completion, or mention an exact `@path` inside a task to attach that file automatically. Project and user slash commands can be saved as Markdown prompts under `.akernel/commands` or `~/.akernel/commands`; use `/commands` to list them. Set `AKERNEL_ALT_SCREEN=1` if you prefer the older full cockpit dashboard with a side panel, transcript viewport, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
159
161
 
160
162
  If you want to prepare the benchmark workspace manually:
161
163
 
@@ -107,7 +107,7 @@ akernel
107
107
 
108
108
  ### npm Launcher
109
109
 
110
- The npm launcher is prepared for publication. After `@context-akernel/akernel` is published, it will provide a Node-style entrypoint and bootstrap the Python package if it is missing:
110
+ The npm launcher is published as `@context-akernel/akernel`. It provides a Node-style entrypoint and bootstraps the Python package if it is missing:
111
111
 
112
112
  ```powershell
113
113
  npm install -g @context-akernel/akernel
@@ -127,7 +127,9 @@ akernel --ui tui
127
127
  akernel --provider mock
128
128
  ```
129
129
 
130
- Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the full-screen terminal UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification. Use `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. The shell UI renders a cockpit-style dashboard with a status header, command strip, transcript, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
130
+ Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the polished terminal chat UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification.
131
+
132
+ The default chat UI is scrollback-friendly: it shows a concise session header once, then appends only the user turn, compact run status, and assistant result. Type `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. Type `/` for command completion, `@` for workspace file completion, or mention an exact `@path` inside a task to attach that file automatically. Project and user slash commands can be saved as Markdown prompts under `.akernel/commands` or `~/.akernel/commands`; use `/commands` to list them. Set `AKERNEL_ALT_SCREEN=1` if you prefer the older full cockpit dashboard with a side panel, transcript viewport, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
131
133
 
132
134
  If you want to prepare the benchmark workspace manually:
133
135
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-akernel/akernel",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "npm launcher wrapper for the Context Kernel akernel CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/huanxin0825-ctrl/context-akernel#readme",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "akernel-runtime"
7
- version = "0.1.7"
7
+ version = "0.1.9"
8
8
  description = "Agent Kernel: a CLI-first context-native agent runtime prototype."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akernel-runtime
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Agent Kernel: a CLI-first context-native agent runtime prototype.
5
5
  Author: Context Kernel contributors
6
6
  License-Expression: Apache-2.0
@@ -135,7 +135,7 @@ akernel
135
135
 
136
136
  ### npm Launcher
137
137
 
138
- The npm launcher is prepared for publication. After `@context-akernel/akernel` is published, it will provide a Node-style entrypoint and bootstrap the Python package if it is missing:
138
+ The npm launcher is published as `@context-akernel/akernel`. It provides a Node-style entrypoint and bootstraps the Python package if it is missing:
139
139
 
140
140
  ```powershell
141
141
  npm install -g @context-akernel/akernel
@@ -155,7 +155,9 @@ akernel --ui tui
155
155
  akernel --provider mock
156
156
  ```
157
157
 
158
- Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the full-screen terminal UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification. Use `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. The shell UI renders a cockpit-style dashboard with a status header, command strip, transcript, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
158
+ Inside the interactive session, type a task and press Enter. Bare `akernel` accepts chat flags such as `--provider mock`, `--model`, `--aux-model`, `--max-steps`, and `--ui tui` without requiring the explicit `chat` subcommand. `--ui auto` uses the polished terminal chat UI on real terminals and falls back to classic output for CI, pipes, and tests. `akernel init . --scan` or `akernel project scan` writes a compact `.akernel/project.json` profile with detected languages, package managers, key files, project instruction files such as `AGENTS.md`, and test/build commands; this profile enters future context packets without loading the whole repository. If you ask to `run tests` or `verify` without naming a command, the agent prefers the scanned project test command. If you ask it to fix failing tests, the agent can run that profile test command, inspect one or more failing files from the command output, apply a bounded patch or rollback-safe batch patch when the failure is simple enough, and rerun verification.
159
+
160
+ The default chat UI is scrollback-friendly: it shows a concise session header once, then appends only the user turn, compact run status, and assistant result. Type `/status` for the live workspace view, `/model` for primary and auxiliary model roles, `/cost` for the last run's token report, `/task` to inspect the current task session, and `/exit` to leave. Type `/` for command completion, `@` for workspace file completion, or mention an exact `@path` inside a task to attach that file automatically. Project and user slash commands can be saved as Markdown prompts under `.akernel/commands` or `~/.akernel/commands`; use `/commands` to list them. Set `AKERNEL_ALT_SCREEN=1` if you prefer the older full cockpit dashboard with a side panel, transcript viewport, mission panel, model stack, workspace summary, task progress, run timeline, diagnostics, and assistant responses. In `--model-routing auto` mode, low/medium first-step planning can run on the auxiliary model while high-risk, deep, warning-heavy, or synthesis steps stay on the primary model. In `--aux-review auto` mode, auxiliary review runs before primary-model steps and is included in token cost reports.
159
161
 
160
162
  If you want to prepare the benchmark workspace manually:
161
163
 
@@ -23,6 +23,8 @@ wake.ps1
23
23
  .github/release-notes/v0.1.5.md
24
24
  .github/release-notes/v0.1.6.md
25
25
  .github/release-notes/v0.1.7.md
26
+ .github/release-notes/v0.1.8.md
27
+ .github/release-notes/v0.1.9.md
26
28
  .github/workflows/ci.yml
27
29
  .github/workflows/release.yml
28
30
  docs/00-vision.md
@@ -1,3 +1,3 @@
1
1
  """Context Kernel CLI-first agent runtime prototype."""
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.9"
@@ -7,6 +7,7 @@ import io
7
7
  import json
8
8
  import os
9
9
  from pathlib import Path
10
+ import re
10
11
  import shutil
11
12
  import sys
12
13
  import unicodedata
@@ -49,6 +50,7 @@ CHAT_COMMANDS: list[tuple[str, str]] = [
49
50
  ("/model", "show primary and auxiliary model roles"),
50
51
  ("/config", "show setup and environment guidance"),
51
52
  ("/compact", "show compact task brief"),
53
+ ("/commands", "list project and user slash commands"),
52
54
  ("/paste", "enter a multi-line task"),
53
55
  ("/task", "print current task session JSON"),
54
56
  ("/runs", "list recent agent runs"),
@@ -59,6 +61,7 @@ CHAT_COMMANDS: list[tuple[str, str]] = [
59
61
  ("/clear", "clear transcript"),
60
62
  ("/exit", "leave interactive session"),
61
63
  ]
64
+ INLINE_FILE_REF_RE = re.compile(r"(?<![\w@])@([^\s,;:]+)")
62
65
  OPENAI_ENV_KEYS = {
63
66
  "api_key": "AKERNEL_OPENAI_API_KEY",
64
67
  "base_url": "AKERNEL_OPENAI_BASE_URL",
@@ -1111,6 +1114,9 @@ def cmd_chat(args: argparse.Namespace) -> None:
1111
1114
  if lowered == "/compact":
1112
1115
  print_task_brief_panel(tasks, task_id)
1113
1116
  continue
1117
+ if lowered == "/commands":
1118
+ print_custom_commands_panel(workspace.root)
1119
+ continue
1114
1120
  if lowered == "/paste":
1115
1121
  pasted = read_paste_block()
1116
1122
  if not pasted:
@@ -1149,25 +1155,14 @@ def cmd_chat(args: argparse.Namespace) -> None:
1149
1155
  print(render_agent_cost_report(build_agent_cost_report(last_report)))
1150
1156
  continue
1151
1157
 
1158
+ attach_inline_file_references(workspace, tasks, task_id, request, pending_context)
1159
+ custom_prompt = expand_custom_chat_command(workspace.root, request)
1160
+ if custom_prompt is not None:
1161
+ request = custom_prompt
1152
1162
  request_for_agent = merge_pending_context(request, pending_context)
1153
1163
  pending_context.clear()
1154
1164
  print_chat_turn_start(request_for_agent, args)
1155
- last_report = AgentLoop(workspace).run(
1156
- request_for_agent,
1157
- provider_name=args.provider,
1158
- budget=args.budget,
1159
- profile=args.profile,
1160
- model=args.model,
1161
- aux_model=args.aux_model,
1162
- model_routing=args.model_routing,
1163
- aux_review=args.aux_review,
1164
- base_url=args.base_url,
1165
- task_id=task_id,
1166
- max_steps=args.max_steps,
1167
- remember=not args.no_remember,
1168
- allow_over_budget=args.allow_over_budget,
1169
- expect_json=args.expect_json,
1170
- )
1165
+ last_report = run_chat_agent(workspace, task_id, args, request_for_agent)
1171
1166
  print_chat_report(last_report)
1172
1167
 
1173
1168
 
@@ -1257,10 +1252,10 @@ def run_chat_loop_tui(
1257
1252
  break
1258
1253
  except KeyboardInterrupt:
1259
1254
  transcript.append({"role": "system", "title": "Interrupted", "text": "Keyboard interrupt received."})
1260
- render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="interrupted", state=state, clear=use_alt_screen)
1255
+ render_chat_tui_update(workspace, task_id, args, transcript, last_report, pending_context, status="interrupted", state=state, clear=use_alt_screen)
1261
1256
  break
1262
1257
  if not request:
1263
- render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
1258
+ render_chat_tui_update(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
1264
1259
  continue
1265
1260
  lowered = request.lower()
1266
1261
  if lowered in {"/exit", "/quit", "exit", "quit"}:
@@ -1283,32 +1278,31 @@ def run_chat_loop_tui(
1283
1278
  state=state,
1284
1279
  ):
1285
1280
  last_report = state.get("last_report")
1286
- render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
1281
+ render_chat_tui_update(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
1287
1282
  continue
1288
1283
 
1284
+ state["scroll_offset"] = 0
1285
+ attach_notice = capture_chat_output(
1286
+ lambda: attach_inline_file_references(workspace, tasks, task_id, request, pending_context)
1287
+ )
1288
+ if attach_notice:
1289
+ transcript.append({"role": "system", "title": "Attached Context", "text": attach_notice})
1290
+ render_chat_tui_message(transcript[-1])
1291
+ state["rendered_count"] = len(transcript)
1292
+ custom_prompt = expand_custom_chat_command(workspace.root, request)
1293
+ if custom_prompt is not None:
1294
+ request = custom_prompt
1289
1295
  request_for_agent = merge_pending_context(request, pending_context)
1290
1296
  pending_context.clear()
1291
- state["scroll_offset"] = 0
1292
1297
  transcript.append({"role": "user", "title": "You", "text": request_for_agent})
1293
- render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="running", state=state, clear=use_alt_screen)
1294
- last_report = AgentLoop(workspace).run(
1295
- request_for_agent,
1296
- provider_name=args.provider,
1297
- budget=args.budget,
1298
- profile=args.profile,
1299
- model=args.model,
1300
- aux_model=args.aux_model,
1301
- model_routing=args.model_routing,
1302
- aux_review=args.aux_review,
1303
- base_url=args.base_url,
1304
- task_id=task_id,
1305
- max_steps=args.max_steps,
1306
- remember=not args.no_remember,
1307
- allow_over_budget=args.allow_over_budget,
1308
- expect_json=args.expect_json,
1309
- )
1298
+ render_chat_tui_message({"role": "user", "title": "You", "text": request_for_agent})
1299
+ state["rendered_count"] = len(transcript)
1300
+ render_chat_tui_status("running", args, pending_context)
1301
+ last_report = run_chat_agent(workspace, task_id, args, request_for_agent)
1310
1302
  transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(last_report)})
1311
- render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
1303
+ render_chat_tui_message({"role": "assistant", "title": "Assistant", "text": format_tui_report(last_report)})
1304
+ state["rendered_count"] = len(transcript)
1305
+ render_chat_tui_status("ready", args, pending_context, last_report=last_report)
1312
1306
  finally:
1313
1307
  if use_alt_screen:
1314
1308
  print("\033[?1049l", end="")
@@ -1343,6 +1337,9 @@ def handle_tui_command(
1343
1337
  if lowered == "/compact":
1344
1338
  transcript.append({"role": "system", "title": "Compact Brief", "text": capture_chat_output(lambda: print_task_brief_panel(tasks, task_id))})
1345
1339
  return True
1340
+ if lowered == "/commands":
1341
+ transcript.append({"role": "system", "title": "Slash Commands", "text": capture_chat_output(lambda: print_custom_commands_panel(workspace.root))})
1342
+ return True
1346
1343
  if lowered == "/status":
1347
1344
  transcript.append({"role": "system", "title": "Status", "text": capture_chat_output(lambda: print_status_panel(workspace, task_id, args))})
1348
1345
  return True
@@ -1369,22 +1366,7 @@ def handle_tui_command(
1369
1366
  transcript.append({"role": "user", "title": "Pasted Task", "text": pasted})
1370
1367
  request_for_agent = merge_pending_context(pasted, pending_context)
1371
1368
  pending_context.clear()
1372
- report = AgentLoop(workspace).run(
1373
- request_for_agent,
1374
- provider_name=args.provider,
1375
- budget=args.budget,
1376
- profile=args.profile,
1377
- model=args.model,
1378
- aux_model=args.aux_model,
1379
- model_routing=args.model_routing,
1380
- aux_review=args.aux_review,
1381
- base_url=args.base_url,
1382
- task_id=task_id,
1383
- max_steps=args.max_steps,
1384
- remember=not args.no_remember,
1385
- allow_over_budget=args.allow_over_budget,
1386
- expect_json=args.expect_json,
1387
- )
1369
+ report = run_chat_agent(workspace, task_id, args, request_for_agent)
1388
1370
  state["last_report"] = report
1389
1371
  transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(report)})
1390
1372
  return True
@@ -1406,10 +1388,30 @@ def capture_chat_output(func: Any) -> str:
1406
1388
  return buffer.getvalue().strip()
1407
1389
 
1408
1390
 
1391
+ def run_chat_agent(workspace: Workspace, task_id: str, args: argparse.Namespace, request: str) -> dict[str, Any]:
1392
+ return AgentLoop(workspace).run(
1393
+ request,
1394
+ provider_name=args.provider,
1395
+ budget=args.budget,
1396
+ profile=args.profile,
1397
+ model=args.model,
1398
+ aux_model=args.aux_model,
1399
+ model_routing=args.model_routing,
1400
+ aux_review=args.aux_review,
1401
+ base_url=args.base_url,
1402
+ task_id=task_id,
1403
+ max_steps=args.max_steps,
1404
+ remember=not args.no_remember,
1405
+ allow_over_budget=args.allow_over_budget,
1406
+ expect_json=args.expect_json,
1407
+ )
1408
+
1409
+
1409
1410
  def format_chat_help_text() -> str:
1410
1411
  rows = [
1411
1412
  *CHAT_COMMANDS,
1412
1413
  ("@query", "search current workspace files; use @1, @2... to attach a listed match"),
1414
+ ("@path", "mention an exact file path inside a task to attach it automatically"),
1413
1415
  ("!command", "run a policy-checked command and attach its summary"),
1414
1416
  ]
1415
1417
  return "\n".join(f"{name:<10} {description}" for name, description in rows)
@@ -1423,21 +1425,27 @@ def read_chat_input(prompt: str, workspace: Workspace) -> str:
1423
1425
  from prompt_toolkit.completion import Completer, Completion
1424
1426
  from prompt_toolkit.formatted_text import ANSI
1425
1427
  from prompt_toolkit.shortcuts import CompleteStyle
1428
+ from prompt_toolkit.styles import Style
1426
1429
  except ImportError:
1427
1430
  return input(prompt)
1428
1431
 
1429
1432
  class ChatCompleter(Completer):
1430
1433
  def get_completions(self, document: Any, complete_event: Any) -> Any:
1431
1434
  text = document.text_before_cursor
1435
+ fragment = chat_completion_fragment(text)
1436
+ if not fragment:
1437
+ return
1432
1438
  for value, description in chat_completion_items(workspace.root, text):
1433
- yield Completion(value, start_position=-len(text), display=value, display_meta=description)
1439
+ yield Completion(value, start_position=-len(fragment), display=value, display_meta=description)
1434
1440
 
1435
1441
  session = PromptSession(
1436
1442
  completer=ChatCompleter(),
1437
1443
  complete_while_typing=True,
1438
1444
  complete_in_thread=True,
1439
1445
  complete_style=CompleteStyle.MULTI_COLUMN,
1446
+ bottom_toolbar=" / opens commands @ finds files Tab accepts Ctrl-C interrupts ",
1440
1447
  reserve_space_for_menu=6,
1448
+ style=Style.from_dict({"bottom-toolbar": "reverse ansiblack ansibrightcyan"}),
1441
1449
  )
1442
1450
  return session.prompt(ANSI(prompt))
1443
1451
 
@@ -1448,15 +1456,26 @@ def should_use_prompt_toolkit() -> bool:
1448
1456
  return bool(sys.stdin.isatty() and sys.stdout.isatty())
1449
1457
 
1450
1458
 
1459
+ def chat_completion_fragment(text: str) -> str:
1460
+ match = re.search(r"(^|\s)([/@][^\s]*)$", text)
1461
+ return match.group(2) if match else ""
1462
+
1463
+
1451
1464
  def chat_completion_items(root: Path, text: str, *, limit: int = 12) -> list[tuple[str, str]]:
1452
- value = text.strip()
1465
+ value = chat_completion_fragment(text) or text.strip()
1453
1466
  if value.startswith("/"):
1454
1467
  query = value.casefold()
1455
- return [
1468
+ builtins = [
1456
1469
  (command, description)
1457
1470
  for command, description in CHAT_COMMANDS
1458
1471
  if command.casefold().startswith(query)
1459
- ][:limit]
1472
+ ]
1473
+ custom = [
1474
+ (command, f"{spec['scope']} command: {spec['description']}")
1475
+ for command, spec in load_custom_chat_commands(root).items()
1476
+ if command.casefold().startswith(query)
1477
+ ]
1478
+ return (builtins + custom)[:limit]
1460
1479
  if value.startswith("@"):
1461
1480
  query = value[1:].strip()
1462
1481
  matches = find_workspace_files(root, query, limit=limit)
@@ -1483,6 +1502,55 @@ def render_chat_tui_screen(
1483
1502
  screen = build_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status, state=state)
1484
1503
  prefix = "\033[2J\033[H" if clear else "\n"
1485
1504
  print(prefix + screen + ("\n" if not clear else ""), end="")
1505
+ if state is not None:
1506
+ state["rendered_count"] = len(transcript)
1507
+
1508
+
1509
+ def render_chat_tui_update(
1510
+ workspace: Workspace,
1511
+ task_id: str,
1512
+ args: argparse.Namespace,
1513
+ transcript: list[dict[str, str]],
1514
+ last_report: dict[str, Any] | None,
1515
+ pending_context: list[str],
1516
+ *,
1517
+ status: str,
1518
+ state: dict[str, Any] | None = None,
1519
+ clear: bool = True,
1520
+ ) -> None:
1521
+ if clear:
1522
+ render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status, state=state, clear=True)
1523
+ return
1524
+ start = int((state or {}).get("rendered_count", max(0, len(transcript) - 1)))
1525
+ for item in transcript[start:]:
1526
+ render_chat_tui_message(item)
1527
+ if state is not None:
1528
+ state["rendered_count"] = len(transcript)
1529
+ render_chat_tui_status(status, args, pending_context, last_report=last_report)
1530
+
1531
+
1532
+ def render_chat_tui_message(item: dict[str, str]) -> None:
1533
+ width = chat_width()
1534
+ role = item.get("role", "system")
1535
+ title = item.get("title", role)
1536
+ label = tui_role_label(role, title)
1537
+ prefix = " " if role != "user" else "> "
1538
+ print("")
1539
+ print(chat_color(truncate_line(label, width), "cyan" if role == "system" else "green" if role == "assistant" else "yellow", bold=True))
1540
+ for line in wrap_plain(item.get("text", ""), width=max(20, width - len(prefix))).splitlines():
1541
+ print(truncate_line(prefix + line, width))
1542
+
1543
+
1544
+ def render_chat_tui_status(
1545
+ status: str,
1546
+ args: argparse.Namespace,
1547
+ pending_context: list[str],
1548
+ *,
1549
+ last_report: dict[str, Any] | None = None,
1550
+ ) -> None:
1551
+ tokens = 0 if not last_report else last_report.get("totals", {}).get("total_tokens", 0)
1552
+ summary = f"{status} | provider {args.provider} | primary {primary_model(args)} | attached {len(pending_context)} | tokens {tokens}"
1553
+ print(chat_color(truncate_line(summary, chat_width()), "dim"))
1486
1554
 
1487
1555
 
1488
1556
  def build_chat_tui_screen(
@@ -1497,9 +1565,9 @@ def build_chat_tui_screen(
1497
1565
  state: dict[str, Any] | None = None,
1498
1566
  ) -> str:
1499
1567
  width = chat_width()
1500
- height = max(24, shutil.get_terminal_size((width, 32)).lines)
1501
1568
  if (state or {}).get("scrollback_mode"):
1502
- height = min(height, 22)
1569
+ return "\n".join(tui_compact_start_lines(workspace, task_id, args, last_report, pending_context, status=status, width=width))
1570
+ height = max(24, shutil.get_terminal_size((width, 32)).lines)
1503
1571
  right_width = min(40, max(32, width // 3))
1504
1572
  left_width = max(46, width - right_width - 3)
1505
1573
  header = tui_header_lines(workspace, args, last_report, status=status, width=width)
@@ -1519,6 +1587,34 @@ def build_chat_tui_screen(
1519
1587
  return "\n".join(lines)
1520
1588
 
1521
1589
 
1590
+ def tui_compact_start_lines(
1591
+ workspace: Workspace,
1592
+ task_id: str,
1593
+ args: argparse.Namespace,
1594
+ last_report: dict[str, Any] | None,
1595
+ pending_context: list[str],
1596
+ *,
1597
+ status: str,
1598
+ width: int,
1599
+ ) -> list[str]:
1600
+ tokens = 0 if not last_report else last_report.get("totals", {}).get("total_tokens", 0)
1601
+ task_short = task_id[:12]
1602
+ return [
1603
+ "",
1604
+ chat_color(truncate_line("AKERNEL", width), "cyan", bold=True),
1605
+ chat_color(truncate_line("Token-frugal agent workspace. Focused chat, explicit context, durable memory.", width), "dim"),
1606
+ "",
1607
+ truncate_line(f"cwd {compact_path(Path.cwd())}", width),
1608
+ truncate_line(f"work {compact_path(workspace.root)}", width),
1609
+ truncate_line(f"models primary {primary_model(args)} | aux {auxiliary_model(args)} | routing {args.model_routing}", width),
1610
+ truncate_line(f"session task {task_short} | provider {args.provider} | profile {args.profile} | tokens {tokens} | status {status}", width),
1611
+ truncate_line(f"context attached {len(pending_context)} | type @ to search files, or mention @path inside a task", width),
1612
+ "",
1613
+ chat_color(truncate_line("shortcuts /help /status /model /commands /compact /runs /cost /clear /exit", width), "dim"),
1614
+ chat_color(truncate_line("input ask one concrete task; use !command for checked shell context", width), "dim"),
1615
+ ]
1616
+
1617
+
1522
1618
  def tui_header_lines(
1523
1619
  workspace: Workspace,
1524
1620
  args: argparse.Namespace,
@@ -1812,8 +1908,10 @@ def print_chat_help() -> None:
1812
1908
  ("/model", "show primary and auxiliary model roles"),
1813
1909
  ("/config", "show setup and environment guidance"),
1814
1910
  ("/compact", "show the compact task brief used for resume context"),
1911
+ ("/commands", "list saved project and user slash commands"),
1815
1912
  ("/paste", "enter a multi-line task; finish with /end"),
1816
1913
  ("@query", "search workspace files; use @1, @2... to attach a listed match"),
1914
+ ("@path", "mention an exact file path inside a task to attach it automatically"),
1817
1915
  ("!command", "run a policy-checked command and attach its summary"),
1818
1916
  ("/task", "print the current task session JSON"),
1819
1917
  ("/runs", "list recent agent runs"),
@@ -1847,6 +1945,100 @@ def print_recent_agent_runs(workspace: Workspace, *, limit: int) -> None:
1847
1945
  )
1848
1946
 
1849
1947
 
1948
+ def load_custom_chat_commands(root: Path) -> dict[str, dict[str, str]]:
1949
+ commands: dict[str, dict[str, str]] = {}
1950
+ for directory, scope in custom_command_directories(root):
1951
+ if not directory.exists():
1952
+ continue
1953
+ for path in sorted(directory.rglob("*.md"))[:80]:
1954
+ try:
1955
+ relative = path.relative_to(directory).with_suffix("").as_posix()
1956
+ body = path.read_text(encoding="utf-8")
1957
+ except (OSError, UnicodeDecodeError, ValueError):
1958
+ continue
1959
+ name = "/" + relative.strip("/")
1960
+ if not re.fullmatch(r"/[A-Za-z0-9][A-Za-z0-9_\-/]*", name):
1961
+ continue
1962
+ description, prompt = parse_custom_command(body)
1963
+ commands.setdefault(
1964
+ name,
1965
+ {
1966
+ "description": description or first_non_empty_line(prompt) or "run saved prompt",
1967
+ "path": str(path),
1968
+ "prompt": prompt,
1969
+ "scope": scope,
1970
+ },
1971
+ )
1972
+ return commands
1973
+
1974
+
1975
+ def custom_command_directories(root: Path) -> list[tuple[Path, str]]:
1976
+ return [
1977
+ (root / ".akernel" / "commands", "project"),
1978
+ (Path.home() / ".akernel" / "commands", "user"),
1979
+ ]
1980
+
1981
+
1982
+ def parse_custom_command(text: str) -> tuple[str, str]:
1983
+ description = ""
1984
+ body = text.strip()
1985
+ if body.startswith("---"):
1986
+ parts = body.split("---", 2)
1987
+ if len(parts) == 3:
1988
+ meta = parts[1]
1989
+ body = parts[2].strip()
1990
+ for line in meta.splitlines():
1991
+ key, _, value = line.partition(":")
1992
+ if key.strip().casefold() == "description":
1993
+ description = value.strip().strip('"').strip("'")
1994
+ break
1995
+ return description, body
1996
+
1997
+
1998
+ def first_non_empty_line(text: str) -> str:
1999
+ for line in text.splitlines():
2000
+ value = line.strip().lstrip("#").strip()
2001
+ if value:
2002
+ return value[:80]
2003
+ return ""
2004
+
2005
+
2006
+ def print_custom_commands_panel(root: Path) -> None:
2007
+ commands = load_custom_chat_commands(root)
2008
+ if not commands:
2009
+ chat_notice(
2010
+ "Slash Commands",
2011
+ "No custom commands yet. Add Markdown prompts under .akernel/commands or ~/.akernel/commands.",
2012
+ )
2013
+ return
2014
+ chat_panel(
2015
+ "Slash Commands",
2016
+ [
2017
+ (name, f"{spec['description']} ({spec['scope']})")
2018
+ for name, spec in sorted(commands.items())
2019
+ ],
2020
+ )
2021
+
2022
+
2023
+ def expand_custom_chat_command(root: Path, request: str) -> str | None:
2024
+ command, _, arguments = request.strip().partition(" ")
2025
+ if not command.startswith("/"):
2026
+ return None
2027
+ spec = load_custom_chat_commands(root).get(command)
2028
+ if not spec:
2029
+ return None
2030
+ prompt = spec["prompt"]
2031
+ if not prompt:
2032
+ return arguments.strip() or f"Run custom command {command}."
2033
+ arguments = arguments.strip()
2034
+ return (
2035
+ prompt.replace("{{args}}", arguments)
2036
+ .replace("{{arguments}}", arguments)
2037
+ .replace("$ARGUMENTS", arguments)
2038
+ .strip()
2039
+ )
2040
+
2041
+
1850
2042
  IGNORED_FILE_FINDER_DIRS = {
1851
2043
  ".git",
1852
2044
  ".hg",
@@ -1862,6 +2054,26 @@ IGNORED_FILE_FINDER_DIRS = {
1862
2054
  }
1863
2055
 
1864
2056
 
2057
+ def attach_inline_file_references(
2058
+ workspace: Workspace,
2059
+ tasks: TaskStore,
2060
+ task_id: str,
2061
+ request: str,
2062
+ pending_context: list[str],
2063
+ ) -> int:
2064
+ attached = 0
2065
+ seen: set[str] = set()
2066
+ for match in INLINE_FILE_REF_RE.finditer(request):
2067
+ path = match.group(1).strip("`'\".,;:!?)]}")
2068
+ if not path or path.isdigit() or path in seen:
2069
+ continue
2070
+ seen.add(path)
2071
+ if (workspace.root / path).is_file():
2072
+ attach_chat_file(workspace, tasks, task_id, path, pending_context)
2073
+ attached += 1
2074
+ return attached
2075
+
2076
+
1865
2077
  def attach_chat_file_command(
1866
2078
  workspace: Workspace,
1867
2079
  tasks: TaskStore,
@@ -10,7 +10,14 @@ from unittest.mock import patch
10
10
  from context_kernel.agent_reports import build_agent_cost_report, render_agent_cost_report
11
11
  from context_kernel.benchmarks import BenchmarkRunner, render_benchmark_evidence_markdown, render_benchmark_markdown
12
12
  from context_kernel.budget import allocate_budget
13
- from context_kernel.cli import build_chat_tui_screen, chat_completion_items, load_batch_patch_specs, main, print_agent_report
13
+ from context_kernel.cli import (
14
+ build_chat_tui_screen,
15
+ chat_completion_items,
16
+ expand_custom_chat_command,
17
+ load_batch_patch_specs,
18
+ main,
19
+ print_agent_report,
20
+ )
14
21
  from context_kernel.context import ContextBuilder
15
22
  from context_kernel.evals import EvalRunner
16
23
  from context_kernel.global_memory import pull_global_memories, push_global_memories
@@ -1353,10 +1360,12 @@ class RuntimeTests(unittest.TestCase):
1353
1360
  reports = list(workspace.agent_runs_dir.glob("*.json"))
1354
1361
 
1355
1362
  self.assertEqual(len(reports), 1)
1356
- self.assertIn("AKERNEL // READY", output)
1363
+ self.assertIn("AKERNEL", output)
1364
+ self.assertIn("shortcuts /help /status /model /commands", output)
1357
1365
  self.assertIn("Assistant", output)
1358
1366
  self.assertIn("Mock agent response", output)
1359
1367
  self.assertIn("bye", output)
1368
+ self.assertEqual(output.count("shortcuts /help /status /model /commands"), 1)
1360
1369
 
1361
1370
  def test_tui_screen_can_render_older_history_window(self) -> None:
1362
1371
  with tempfile.TemporaryDirectory() as tmp:
@@ -1426,9 +1435,48 @@ class RuntimeTests(unittest.TestCase):
1426
1435
 
1427
1436
  command_items = chat_completion_items(root, "/mo")
1428
1437
  file_items = chat_completion_items(root, "@run")
1438
+ inline_file_items = chat_completion_items(root, "please inspect @REA")
1429
1439
 
1430
1440
  self.assertIn(("/model", "show primary and auxiliary model roles"), command_items)
1431
1441
  self.assertIn(("@src/runtime.py", "attach file"), file_items)
1442
+ self.assertIn(("@README.md", "attach file"), inline_file_items)
1443
+
1444
+ def test_chat_inline_file_reference_attaches_context(self) -> None:
1445
+ with tempfile.TemporaryDirectory() as tmp:
1446
+ root = Path(tmp)
1447
+ (root / "notes.md").write_text("inline context works", encoding="utf-8")
1448
+ workspace = Workspace(root)
1449
+ workspace.init()
1450
+
1451
+ with patch("builtins.input", side_effect=["Summarize @notes.md", "/exit"]):
1452
+ with patch("sys.stdout", new=io.StringIO()):
1453
+ main(["--workspace", str(workspace.root), "chat", "--provider", "mock", "--max-steps", "1"])
1454
+
1455
+ reports = list(workspace.agent_runs_dir.glob("*.json"))
1456
+ tool_traces = list(workspace.tool_traces_dir.glob("*.json"))
1457
+ saved = Workspace.read_json(reports[0])
1458
+
1459
+ self.assertEqual(len(reports), 1)
1460
+ self.assertGreaterEqual(len(tool_traces), 1)
1461
+ self.assertIn("inline context works", saved["request"])
1462
+
1463
+ def test_custom_slash_command_expands_saved_prompt(self) -> None:
1464
+ with tempfile.TemporaryDirectory() as tmp:
1465
+ root = Path(tmp)
1466
+ command_dir = root / ".akernel" / "commands"
1467
+ command_dir.mkdir(parents=True)
1468
+ (command_dir / "review.md").write_text(
1469
+ "---\ndescription: Review the current change\n---\nReview this change: {{args}}",
1470
+ encoding="utf-8",
1471
+ )
1472
+ workspace = Workspace(root)
1473
+ workspace.init()
1474
+
1475
+ items = chat_completion_items(root, "/rev")
1476
+ expanded = expand_custom_chat_command(root, "/review cli polish")
1477
+
1478
+ self.assertIn(("/review", "project command: Review the current change"), items)
1479
+ self.assertEqual(expanded, "Review this change: cli polish")
1432
1480
 
1433
1481
  def test_tui_screen_surfaces_task_plan_and_command_strip(self) -> None:
1434
1482
  with tempfile.TemporaryDirectory() as tmp:
File without changes
File without changes