akernel-runtime 0.1.8__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.9.md +14 -0
  2. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/CHANGELOG.md +12 -0
  3. {akernel_runtime-0.1.8/src/akernel_runtime.egg-info → akernel_runtime-0.1.9}/PKG-INFO +5 -3
  4. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/README.md +4 -2
  5. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/packages/npm/akernel/package.json +1 -1
  6. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/pyproject.toml +1 -1
  7. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9/src/akernel_runtime.egg-info}/PKG-INFO +5 -3
  8. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/SOURCES.txt +1 -0
  9. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/__init__.py +1 -1
  10. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/cli.py +223 -57
  11. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/tests/test_runtime.py +50 -3
  12. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.env.example +0 -0
  13. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/pull_request_template.md +0 -0
  16. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.0.md +0 -0
  17. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.1.md +0 -0
  18. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.2.md +0 -0
  19. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.3.md +0 -0
  20. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.4.md +0 -0
  21. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.5.md +0 -0
  22. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.6.md +0 -0
  23. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.7.md +0 -0
  24. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.8.md +0 -0
  25. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/workflows/ci.yml +0 -0
  26. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/.github/workflows/release.yml +0 -0
  27. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/CODE_OF_CONDUCT.md +0 -0
  28. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/CONTRIBUTING.md +0 -0
  29. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/LICENSE +0 -0
  30. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/MANIFEST.in +0 -0
  31. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/NOTICE +0 -0
  32. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/SECURITY.md +0 -0
  33. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/00-vision.md +0 -0
  34. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/01-architecture.md +0 -0
  35. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/02-execution-plan.md +0 -0
  36. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/03-cli-mvp.md +0 -0
  37. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/04-evaluation.md +0 -0
  38. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/05-local-wake.md +0 -0
  39. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/06-skill-compiler.md +0 -0
  40. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/07-release-and-ci.md +0 -0
  41. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/08-open-source-plan.md +0 -0
  42. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/09-product-roadmap.md +0 -0
  43. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/10-benchmark-evidence.md +0 -0
  44. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/docs/11-publishing-setup.md +0 -0
  45. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/01-routing.json +0 -0
  46. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/02-memory.json +0 -0
  47. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/03-budget-profiles.json +0 -0
  48. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/scale/01-context-pressure.json +0 -0
  49. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/scale/02-agent-editing.json +0 -0
  50. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/benchmarks/scale/03-global-memory-marketplace.json +0 -0
  51. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/evals/phase2.json +0 -0
  52. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/marketplace/skills/index.json +0 -0
  53. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/skills/context_budget.json +0 -0
  54. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/skills/edit_file.json +0 -0
  55. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/examples/skills/markdown/context_budget.md +0 -0
  56. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/packages/npm/akernel/README.md +0 -0
  57. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/packages/npm/akernel/bin/akernel.js +0 -0
  58. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/scripts/install_remote.ps1 +0 -0
  59. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/scripts/release_check.ps1 +0 -0
  60. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/setup.cfg +0 -0
  61. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/setup.cmd +0 -0
  62. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/setup.ps1 +0 -0
  63. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/dependency_links.txt +0 -0
  64. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/entry_points.txt +0 -0
  65. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/requires.txt +0 -0
  66. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/top_level.txt +0 -0
  67. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/__main__.py +0 -0
  68. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/agent_reports.py +0 -0
  69. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/benchmarks.py +0 -0
  70. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/budget.py +0 -0
  71. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/context.py +0 -0
  72. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/evals.py +0 -0
  73. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/global_memory.py +0 -0
  74. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/loop.py +0 -0
  75. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace.py +0 -0
  76. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_budget.json +0 -0
  77. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_compaction.json +0 -0
  78. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/edit_file.json +0 -0
  79. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/index.json +0 -0
  80. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/long_task_planning.json +0 -0
  81. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/multi_file_bugfix.json +0 -0
  82. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/memory.py +0 -0
  83. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/models.py +0 -0
  84. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/planner.py +0 -0
  85. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/policy.py +0 -0
  86. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/project.py +0 -0
  87. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/providers.py +0 -0
  88. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/report_costs.py +0 -0
  89. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/runner.py +0 -0
  90. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/skills.py +0 -0
  91. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/state_writer.py +0 -0
  92. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/storage.py +0 -0
  93. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/tasks.py +0 -0
  94. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/text.py +0 -0
  95. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/tokenizer.py +0 -0
  96. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/tools.py +0 -0
  97. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/src/context_kernel/verifier.py +0 -0
  98. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/wake.cmd +0 -0
  99. {akernel_runtime-0.1.8 → akernel_runtime-0.1.9}/wake.ps1 +0 -0
@@ -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,18 @@ 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
+
11
23
  ## 0.1.8 - 2026-05-14
12
24
 
13
25
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akernel-runtime
3
- Version: 0.1.8
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.8",
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.8"
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.8
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
 
@@ -24,6 +24,7 @@ wake.ps1
24
24
  .github/release-notes/v0.1.6.md
25
25
  .github/release-notes/v0.1.7.md
26
26
  .github/release-notes/v0.1.8.md
27
+ .github/release-notes/v0.1.9.md
27
28
  .github/workflows/ci.yml
28
29
  .github/workflows/release.yml
29
30
  docs/00-vision.md
@@ -1,3 +1,3 @@
1
1
  """Context Kernel CLI-first agent runtime prototype."""
2
2
 
3
- __version__ = "0.1.8"
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
 
@@ -1286,30 +1281,27 @@ def run_chat_loop_tui(
1286
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
1298
  render_chat_tui_message({"role": "user", "title": "You", "text": request_for_agent})
1299
+ state["rendered_count"] = len(transcript)
1294
1300
  render_chat_tui_status("running", args, pending_context)
1295
- last_report = AgentLoop(workspace).run(
1296
- request_for_agent,
1297
- provider_name=args.provider,
1298
- budget=args.budget,
1299
- profile=args.profile,
1300
- model=args.model,
1301
- aux_model=args.aux_model,
1302
- model_routing=args.model_routing,
1303
- aux_review=args.aux_review,
1304
- base_url=args.base_url,
1305
- task_id=task_id,
1306
- max_steps=args.max_steps,
1307
- remember=not args.no_remember,
1308
- allow_over_budget=args.allow_over_budget,
1309
- expect_json=args.expect_json,
1310
- )
1301
+ last_report = run_chat_agent(workspace, task_id, args, request_for_agent)
1311
1302
  transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(last_report)})
1312
1303
  render_chat_tui_message({"role": "assistant", "title": "Assistant", "text": format_tui_report(last_report)})
1304
+ state["rendered_count"] = len(transcript)
1313
1305
  render_chat_tui_status("ready", args, pending_context, last_report=last_report)
1314
1306
  finally:
1315
1307
  if use_alt_screen:
@@ -1345,6 +1337,9 @@ def handle_tui_command(
1345
1337
  if lowered == "/compact":
1346
1338
  transcript.append({"role": "system", "title": "Compact Brief", "text": capture_chat_output(lambda: print_task_brief_panel(tasks, task_id))})
1347
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
1348
1343
  if lowered == "/status":
1349
1344
  transcript.append({"role": "system", "title": "Status", "text": capture_chat_output(lambda: print_status_panel(workspace, task_id, args))})
1350
1345
  return True
@@ -1371,22 +1366,7 @@ def handle_tui_command(
1371
1366
  transcript.append({"role": "user", "title": "Pasted Task", "text": pasted})
1372
1367
  request_for_agent = merge_pending_context(pasted, pending_context)
1373
1368
  pending_context.clear()
1374
- report = AgentLoop(workspace).run(
1375
- request_for_agent,
1376
- provider_name=args.provider,
1377
- budget=args.budget,
1378
- profile=args.profile,
1379
- model=args.model,
1380
- aux_model=args.aux_model,
1381
- model_routing=args.model_routing,
1382
- aux_review=args.aux_review,
1383
- base_url=args.base_url,
1384
- task_id=task_id,
1385
- max_steps=args.max_steps,
1386
- remember=not args.no_remember,
1387
- allow_over_budget=args.allow_over_budget,
1388
- expect_json=args.expect_json,
1389
- )
1369
+ report = run_chat_agent(workspace, task_id, args, request_for_agent)
1390
1370
  state["last_report"] = report
1391
1371
  transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(report)})
1392
1372
  return True
@@ -1408,10 +1388,30 @@ def capture_chat_output(func: Any) -> str:
1408
1388
  return buffer.getvalue().strip()
1409
1389
 
1410
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
+
1411
1410
  def format_chat_help_text() -> str:
1412
1411
  rows = [
1413
1412
  *CHAT_COMMANDS,
1414
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"),
1415
1415
  ("!command", "run a policy-checked command and attach its summary"),
1416
1416
  ]
1417
1417
  return "\n".join(f"{name:<10} {description}" for name, description in rows)
@@ -1425,21 +1425,27 @@ def read_chat_input(prompt: str, workspace: Workspace) -> str:
1425
1425
  from prompt_toolkit.completion import Completer, Completion
1426
1426
  from prompt_toolkit.formatted_text import ANSI
1427
1427
  from prompt_toolkit.shortcuts import CompleteStyle
1428
+ from prompt_toolkit.styles import Style
1428
1429
  except ImportError:
1429
1430
  return input(prompt)
1430
1431
 
1431
1432
  class ChatCompleter(Completer):
1432
1433
  def get_completions(self, document: Any, complete_event: Any) -> Any:
1433
1434
  text = document.text_before_cursor
1435
+ fragment = chat_completion_fragment(text)
1436
+ if not fragment:
1437
+ return
1434
1438
  for value, description in chat_completion_items(workspace.root, text):
1435
- 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)
1436
1440
 
1437
1441
  session = PromptSession(
1438
1442
  completer=ChatCompleter(),
1439
1443
  complete_while_typing=True,
1440
1444
  complete_in_thread=True,
1441
1445
  complete_style=CompleteStyle.MULTI_COLUMN,
1446
+ bottom_toolbar=" / opens commands @ finds files Tab accepts Ctrl-C interrupts ",
1442
1447
  reserve_space_for_menu=6,
1448
+ style=Style.from_dict({"bottom-toolbar": "reverse ansiblack ansibrightcyan"}),
1443
1449
  )
1444
1450
  return session.prompt(ANSI(prompt))
1445
1451
 
@@ -1450,15 +1456,26 @@ def should_use_prompt_toolkit() -> bool:
1450
1456
  return bool(sys.stdin.isatty() and sys.stdout.isatty())
1451
1457
 
1452
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
+
1453
1464
  def chat_completion_items(root: Path, text: str, *, limit: int = 12) -> list[tuple[str, str]]:
1454
- value = text.strip()
1465
+ value = chat_completion_fragment(text) or text.strip()
1455
1466
  if value.startswith("/"):
1456
1467
  query = value.casefold()
1457
- return [
1468
+ builtins = [
1458
1469
  (command, description)
1459
1470
  for command, description in CHAT_COMMANDS
1460
1471
  if command.casefold().startswith(query)
1461
- ][: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]
1462
1479
  if value.startswith("@"):
1463
1480
  query = value[1:].strip()
1464
1481
  matches = find_workspace_files(root, query, limit=limit)
@@ -1485,6 +1502,8 @@ def render_chat_tui_screen(
1485
1502
  screen = build_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status, state=state)
1486
1503
  prefix = "\033[2J\033[H" if clear else "\n"
1487
1504
  print(prefix + screen + ("\n" if not clear else ""), end="")
1505
+ if state is not None:
1506
+ state["rendered_count"] = len(transcript)
1488
1507
 
1489
1508
 
1490
1509
  def render_chat_tui_update(
@@ -1502,8 +1521,11 @@ def render_chat_tui_update(
1502
1521
  if clear:
1503
1522
  render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status, state=state, clear=True)
1504
1523
  return
1505
- if transcript:
1506
- render_chat_tui_message(transcript[-1])
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)
1507
1529
  render_chat_tui_status(status, args, pending_context, last_report=last_report)
1508
1530
 
1509
1531
 
@@ -1543,9 +1565,9 @@ def build_chat_tui_screen(
1543
1565
  state: dict[str, Any] | None = None,
1544
1566
  ) -> str:
1545
1567
  width = chat_width()
1546
- height = max(24, shutil.get_terminal_size((width, 32)).lines)
1547
1568
  if (state or {}).get("scrollback_mode"):
1548
- 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)
1549
1571
  right_width = min(40, max(32, width // 3))
1550
1572
  left_width = max(46, width - right_width - 3)
1551
1573
  header = tui_header_lines(workspace, args, last_report, status=status, width=width)
@@ -1565,6 +1587,34 @@ def build_chat_tui_screen(
1565
1587
  return "\n".join(lines)
1566
1588
 
1567
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
+
1568
1618
  def tui_header_lines(
1569
1619
  workspace: Workspace,
1570
1620
  args: argparse.Namespace,
@@ -1858,8 +1908,10 @@ def print_chat_help() -> None:
1858
1908
  ("/model", "show primary and auxiliary model roles"),
1859
1909
  ("/config", "show setup and environment guidance"),
1860
1910
  ("/compact", "show the compact task brief used for resume context"),
1911
+ ("/commands", "list saved project and user slash commands"),
1861
1912
  ("/paste", "enter a multi-line task; finish with /end"),
1862
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"),
1863
1915
  ("!command", "run a policy-checked command and attach its summary"),
1864
1916
  ("/task", "print the current task session JSON"),
1865
1917
  ("/runs", "list recent agent runs"),
@@ -1893,6 +1945,100 @@ def print_recent_agent_runs(workspace: Workspace, *, limit: int) -> None:
1893
1945
  )
1894
1946
 
1895
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
+
1896
2042
  IGNORED_FILE_FINDER_DIRS = {
1897
2043
  ".git",
1898
2044
  ".hg",
@@ -1908,6 +2054,26 @@ IGNORED_FILE_FINDER_DIRS = {
1908
2054
  }
1909
2055
 
1910
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
+
1911
2077
  def attach_chat_file_command(
1912
2078
  workspace: Workspace,
1913
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,11 +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)
1360
- self.assertEqual(output.count("AKERNEL // READY"), 1)
1368
+ self.assertEqual(output.count("shortcuts /help /status /model /commands"), 1)
1361
1369
 
1362
1370
  def test_tui_screen_can_render_older_history_window(self) -> None:
1363
1371
  with tempfile.TemporaryDirectory() as tmp:
@@ -1427,9 +1435,48 @@ class RuntimeTests(unittest.TestCase):
1427
1435
 
1428
1436
  command_items = chat_completion_items(root, "/mo")
1429
1437
  file_items = chat_completion_items(root, "@run")
1438
+ inline_file_items = chat_completion_items(root, "please inspect @REA")
1430
1439
 
1431
1440
  self.assertIn(("/model", "show primary and auxiliary model roles"), command_items)
1432
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")
1433
1480
 
1434
1481
  def test_tui_screen_surfaces_task_plan_and_command_strip(self) -> None:
1435
1482
  with tempfile.TemporaryDirectory() as tmp:
File without changes
File without changes