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.
- akernel_runtime-0.1.9/.github/release-notes/v0.1.8.md +14 -0
- akernel_runtime-0.1.9/.github/release-notes/v0.1.9.md +14 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CHANGELOG.md +22 -0
- {akernel_runtime-0.1.7/src/akernel_runtime.egg-info → akernel_runtime-0.1.9}/PKG-INFO +5 -3
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/README.md +4 -2
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/package.json +1 -1
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/pyproject.toml +1 -1
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9/src/akernel_runtime.egg-info}/PKG-INFO +5 -3
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/SOURCES.txt +2 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/__init__.py +1 -1
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/cli.py +272 -60
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/tests/test_runtime.py +50 -2
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.env.example +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/pull_request_template.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.0.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.1.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.2.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.3.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.4.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.5.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.6.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/release-notes/v0.1.7.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/workflows/ci.yml +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/.github/workflows/release.yml +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CODE_OF_CONDUCT.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/CONTRIBUTING.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/LICENSE +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/MANIFEST.in +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/NOTICE +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/SECURITY.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/00-vision.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/01-architecture.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/02-execution-plan.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/03-cli-mvp.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/04-evaluation.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/05-local-wake.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/06-skill-compiler.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/07-release-and-ci.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/08-open-source-plan.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/09-product-roadmap.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/10-benchmark-evidence.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/docs/11-publishing-setup.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/01-routing.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/02-memory.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/03-budget-profiles.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/01-context-pressure.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/02-agent-editing.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/03-global-memory-marketplace.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/evals/phase2.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/marketplace/skills/index.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/context_budget.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/edit_file.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/skills/markdown/context_budget.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/README.md +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/packages/npm/akernel/bin/akernel.js +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/scripts/install_remote.ps1 +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/scripts/release_check.ps1 +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.cfg +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.cmd +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/setup.ps1 +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/dependency_links.txt +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/entry_points.txt +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/requires.txt +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/top_level.txt +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/__main__.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/agent_reports.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/benchmarks.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/budget.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/context.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/evals.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/global_memory.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/loop.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_budget.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/context_compaction.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/edit_file.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/index.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/long_task_planning.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/marketplace_data/skills/multi_file_bugfix.json +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/memory.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/models.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/planner.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/policy.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/project.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/providers.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/report_costs.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/runner.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/skills.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/state_writer.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/storage.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tasks.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/text.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tokenizer.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/tools.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/context_kernel/verifier.py +0 -0
- {akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/wake.cmd +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
Metadata-Version: 2.4
|
|
2
2
|
Name: akernel-runtime
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
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
|
|
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
|
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
1468
|
+
builtins = [
|
|
1456
1469
|
(command, description)
|
|
1457
1470
|
for command, description in CHAT_COMMANDS
|
|
1458
1471
|
if command.casefold().startswith(query)
|
|
1459
|
-
]
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/phase2/03-budget-profiles.json
RENAMED
|
File without changes
|
{akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/01-context-pressure.json
RENAMED
|
File without changes
|
{akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/examples/benchmarks/scale/02-agent-editing.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{akernel_runtime-0.1.7 → akernel_runtime-0.1.9}/src/akernel_runtime.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|