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