akernel-runtime 0.1.5__tar.gz → 0.1.6__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.6/.github/release-notes/v0.1.6.md +33 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/CHANGELOG.md +13 -0
- {akernel_runtime-0.1.5/src/akernel_runtime.egg-info → akernel_runtime-0.1.6}/PKG-INFO +1 -1
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/packages/npm/akernel/package.json +1 -1
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/pyproject.toml +1 -1
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6/src/akernel_runtime.egg-info}/PKG-INFO +1 -1
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/SOURCES.txt +1 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/__init__.py +1 -1
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/cli.py +222 -54
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/tests/test_runtime.py +66 -7
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.env.example +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/pull_request_template.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.0.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.1.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.2.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.3.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.4.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/release-notes/v0.1.5.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/workflows/ci.yml +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/.github/workflows/release.yml +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/CODE_OF_CONDUCT.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/CONTRIBUTING.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/LICENSE +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/MANIFEST.in +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/NOTICE +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/README.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/SECURITY.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/00-vision.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/01-architecture.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/02-execution-plan.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/03-cli-mvp.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/04-evaluation.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/05-local-wake.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/06-skill-compiler.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/07-release-and-ci.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/08-open-source-plan.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/09-product-roadmap.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/10-benchmark-evidence.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/docs/11-publishing-setup.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/phase2/01-routing.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/phase2/02-memory.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/phase2/03-budget-profiles.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/scale/01-context-pressure.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/scale/02-agent-editing.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/scale/03-global-memory-marketplace.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/evals/phase2.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/marketplace/skills/index.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/skills/context_budget.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/skills/edit_file.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/skills/markdown/context_budget.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/packages/npm/akernel/README.md +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/packages/npm/akernel/bin/akernel.js +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/scripts/install_remote.ps1 +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/scripts/release_check.ps1 +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/setup.cfg +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/setup.cmd +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/setup.ps1 +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/dependency_links.txt +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/entry_points.txt +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/requires.txt +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/top_level.txt +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/__main__.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/agent_reports.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/benchmarks.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/budget.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/context.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/evals.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/global_memory.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/loop.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/context_budget.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/context_compaction.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/edit_file.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/index.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/long_task_planning.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/marketplace_data/skills/multi_file_bugfix.json +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/memory.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/models.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/planner.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/policy.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/project.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/providers.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/report_costs.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/runner.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/skills.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/state_writer.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/storage.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/tasks.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/text.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/tokenizer.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/tools.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/src/context_kernel/verifier.py +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/wake.cmd +0 -0
- {akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/wake.ps1 +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Context Kernel v0.1.6
|
|
2
|
+
|
|
3
|
+
This release improves the interactive `akernel` experience with a cleaner terminal workspace, scrollback-friendly history, and file search through `@`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Python:
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
python -m pip install --user --upgrade akernel-runtime
|
|
11
|
+
akernel setup
|
|
12
|
+
akernel
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
npm launcher:
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
npm install -g @context-akernel/akernel
|
|
19
|
+
akernel setup
|
|
20
|
+
akernel
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Highlights
|
|
24
|
+
|
|
25
|
+
- Default TUI no longer takes over the terminal alternate screen, so normal terminal scrollback works.
|
|
26
|
+
- Added `/up`, `/down`, and `/latest` for keyboard-driven transcript viewport control.
|
|
27
|
+
- Added `@` file search: use `@`, `@query`, then `@1`, `@2`, etc. to attach a listed file.
|
|
28
|
+
- Refined the TUI layout with cleaner sections, lighter visual density, and better CJK/wide-character alignment.
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
|
|
32
|
+
- Added regression tests for TUI history viewport rendering and numbered `@` file attachment.
|
|
33
|
+
- Ran the full Python test suite.
|
|
@@ -8,6 +8,19 @@ 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.6 - 2026-05-14
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added TUI history viewport controls with `/up`, `/down`, and `/latest`.
|
|
16
|
+
- Added `@` file search for the current workspace, including numbered follow-up attachment commands such as `@1`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Redesigned the interactive TUI into a cleaner, lower-density terminal workspace.
|
|
21
|
+
- Made the default TUI scrollback-friendly by avoiding alt-screen takeover unless `AKERNEL_ALT_SCREEN=1` is set.
|
|
22
|
+
- Improved CJK/wide-character alignment in TUI rows and truncation.
|
|
23
|
+
|
|
11
24
|
## 0.1.5 - 2026-05-12
|
|
12
25
|
|
|
13
26
|
### Fixed
|
|
@@ -9,6 +9,7 @@ import os
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
import shutil
|
|
11
11
|
import sys
|
|
12
|
+
import unicodedata
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
14
15
|
from .agent_reports import build_agent_cost_report, load_agent_report, render_agent_cost_report
|
|
@@ -1065,7 +1066,7 @@ def cmd_chat(args: argparse.Namespace) -> None:
|
|
|
1065
1066
|
task_id = task["id"]
|
|
1066
1067
|
|
|
1067
1068
|
last_report: dict[str, Any] | None = None
|
|
1068
|
-
state: dict[str, Any] = {"last_report": None}
|
|
1069
|
+
state: dict[str, Any] = {"last_report": None, "file_matches": []}
|
|
1069
1070
|
pending_context: list[str] = []
|
|
1070
1071
|
if resolve_chat_ui(args) == "tui":
|
|
1071
1072
|
run_chat_loop_tui(workspace, tasks, task_id, args)
|
|
@@ -1101,7 +1102,7 @@ def cmd_chat(args: argparse.Namespace) -> None:
|
|
|
1101
1102
|
continue
|
|
1102
1103
|
request = pasted
|
|
1103
1104
|
elif request.startswith("@"):
|
|
1104
|
-
|
|
1105
|
+
attach_chat_file_command(workspace, tasks, task_id, request[1:].strip(), pending_context, state)
|
|
1105
1106
|
continue
|
|
1106
1107
|
elif request.startswith("!"):
|
|
1107
1108
|
run_chat_command(workspace, tasks, task_id, request[1:].strip(), pending_context)
|
|
@@ -1214,20 +1215,25 @@ def run_chat_loop_tui(
|
|
|
1214
1215
|
args: argparse.Namespace,
|
|
1215
1216
|
) -> None:
|
|
1216
1217
|
last_report: dict[str, Any] | None = None
|
|
1217
|
-
state: dict[str, Any] = {"last_report": None}
|
|
1218
|
+
state: dict[str, Any] = {"last_report": None, "scroll_offset": 0, "file_matches": []}
|
|
1218
1219
|
pending_context: list[str] = []
|
|
1219
1220
|
transcript: list[dict[str, str]] = [
|
|
1220
1221
|
{
|
|
1221
1222
|
"role": "system",
|
|
1222
1223
|
"title": "Welcome",
|
|
1223
|
-
"text": "Describe a task,
|
|
1224
|
+
"text": "Describe a task, search files with @query, run safe commands with !command, or type /help.",
|
|
1224
1225
|
}
|
|
1225
1226
|
]
|
|
1226
|
-
use_alt_screen =
|
|
1227
|
+
use_alt_screen = (
|
|
1228
|
+
sys.stdout.isatty()
|
|
1229
|
+
and os.environ.get("AKERNEL_ALT_SCREEN", "").strip().lower() in {"1", "true", "yes"}
|
|
1230
|
+
and not os.environ.get("AKERNEL_NO_ALT_SCREEN")
|
|
1231
|
+
)
|
|
1227
1232
|
if use_alt_screen:
|
|
1228
1233
|
print("\033[?1049h", end="")
|
|
1234
|
+
state["scrollback_mode"] = not use_alt_screen
|
|
1229
1235
|
try:
|
|
1230
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready")
|
|
1236
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
|
|
1231
1237
|
while True:
|
|
1232
1238
|
try:
|
|
1233
1239
|
request = input(tui_prompt(args)).strip()
|
|
@@ -1235,17 +1241,18 @@ def run_chat_loop_tui(
|
|
|
1235
1241
|
break
|
|
1236
1242
|
except KeyboardInterrupt:
|
|
1237
1243
|
transcript.append({"role": "system", "title": "Interrupted", "text": "Keyboard interrupt received."})
|
|
1238
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="interrupted")
|
|
1244
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="interrupted", state=state, clear=use_alt_screen)
|
|
1239
1245
|
break
|
|
1240
1246
|
if not request:
|
|
1241
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready")
|
|
1247
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
|
|
1242
1248
|
continue
|
|
1243
1249
|
lowered = request.lower()
|
|
1244
1250
|
if lowered in {"/exit", "/quit", "exit", "quit"}:
|
|
1245
1251
|
break
|
|
1246
1252
|
if lowered == "/clear":
|
|
1247
1253
|
transcript.clear()
|
|
1248
|
-
|
|
1254
|
+
state["scroll_offset"] = 0
|
|
1255
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="cleared", state=state, clear=use_alt_screen)
|
|
1249
1256
|
continue
|
|
1250
1257
|
state["last_report"] = last_report
|
|
1251
1258
|
if handle_tui_command(
|
|
@@ -1260,13 +1267,14 @@ def run_chat_loop_tui(
|
|
|
1260
1267
|
state=state,
|
|
1261
1268
|
):
|
|
1262
1269
|
last_report = state.get("last_report")
|
|
1263
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready")
|
|
1270
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
|
|
1264
1271
|
continue
|
|
1265
1272
|
|
|
1266
1273
|
request_for_agent = merge_pending_context(request, pending_context)
|
|
1267
1274
|
pending_context.clear()
|
|
1275
|
+
state["scroll_offset"] = 0
|
|
1268
1276
|
transcript.append({"role": "user", "title": "You", "text": request_for_agent})
|
|
1269
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="running")
|
|
1277
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="running", state=state, clear=use_alt_screen)
|
|
1270
1278
|
last_report = AgentLoop(workspace).run(
|
|
1271
1279
|
request_for_agent,
|
|
1272
1280
|
provider_name=args.provider,
|
|
@@ -1284,7 +1292,7 @@ def run_chat_loop_tui(
|
|
|
1284
1292
|
expect_json=args.expect_json,
|
|
1285
1293
|
)
|
|
1286
1294
|
transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(last_report)})
|
|
1287
|
-
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready")
|
|
1295
|
+
render_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status="ready", state=state, clear=use_alt_screen)
|
|
1288
1296
|
finally:
|
|
1289
1297
|
if use_alt_screen:
|
|
1290
1298
|
print("\033[?1049l", end="")
|
|
@@ -1304,6 +1312,15 @@ def handle_tui_command(
|
|
|
1304
1312
|
state: dict[str, Any],
|
|
1305
1313
|
) -> bool:
|
|
1306
1314
|
last_report = state.get("last_report")
|
|
1315
|
+
if lowered in {"/up", "/pageup", "/pgup"}:
|
|
1316
|
+
state["scroll_offset"] = int(state.get("scroll_offset", 0)) + 12
|
|
1317
|
+
return True
|
|
1318
|
+
if lowered in {"/down", "/pagedown", "/pgdn"}:
|
|
1319
|
+
state["scroll_offset"] = max(0, int(state.get("scroll_offset", 0)) - 12)
|
|
1320
|
+
return True
|
|
1321
|
+
if lowered in {"/latest", "/bottom"}:
|
|
1322
|
+
state["scroll_offset"] = 0
|
|
1323
|
+
return True
|
|
1307
1324
|
if lowered == "/help":
|
|
1308
1325
|
transcript.append({"role": "system", "title": "Help", "text": format_chat_help_text()})
|
|
1309
1326
|
return True
|
|
@@ -1356,7 +1373,7 @@ def handle_tui_command(
|
|
|
1356
1373
|
transcript.append({"role": "assistant", "title": "Assistant", "text": format_tui_report(report)})
|
|
1357
1374
|
return True
|
|
1358
1375
|
if request.startswith("@"):
|
|
1359
|
-
text = capture_chat_output(lambda:
|
|
1376
|
+
text = capture_chat_output(lambda: attach_chat_file_command(workspace, tasks, task_id, request[1:].strip(), pending_context, state))
|
|
1360
1377
|
transcript.append({"role": "system", "title": "Attach File", "text": text})
|
|
1361
1378
|
return True
|
|
1362
1379
|
if request.startswith("!"):
|
|
@@ -1386,8 +1403,12 @@ def format_chat_help_text() -> str:
|
|
|
1386
1403
|
("/task", "print the current task session JSON"),
|
|
1387
1404
|
("/runs", "list recent agent runs"),
|
|
1388
1405
|
("/cost", "print the last agent run cost report"),
|
|
1406
|
+
("/up", "show older transcript lines in the viewport"),
|
|
1407
|
+
("/down", "move the viewport back toward latest messages"),
|
|
1408
|
+
("/latest", "jump to the newest transcript lines"),
|
|
1389
1409
|
("/clear", "clear the transcript"),
|
|
1390
1410
|
("/exit", "leave the interactive session"),
|
|
1411
|
+
("@query", "search current workspace files; use @1, @2... to attach a listed match"),
|
|
1391
1412
|
]
|
|
1392
1413
|
return "\n".join(f"{name:<10} {description}" for name, description in rows)
|
|
1393
1414
|
|
|
@@ -1405,9 +1426,12 @@ def render_chat_tui_screen(
|
|
|
1405
1426
|
pending_context: list[str],
|
|
1406
1427
|
*,
|
|
1407
1428
|
status: str,
|
|
1429
|
+
state: dict[str, Any] | None = None,
|
|
1430
|
+
clear: bool = True,
|
|
1408
1431
|
) -> None:
|
|
1409
|
-
screen = build_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status)
|
|
1410
|
-
|
|
1432
|
+
screen = build_chat_tui_screen(workspace, task_id, args, transcript, last_report, pending_context, status=status, state=state)
|
|
1433
|
+
prefix = "\033[2J\033[H" if clear else "\n"
|
|
1434
|
+
print(prefix + screen + ("\n" if not clear else ""), end="")
|
|
1411
1435
|
|
|
1412
1436
|
|
|
1413
1437
|
def build_chat_tui_screen(
|
|
@@ -1419,9 +1443,12 @@ def build_chat_tui_screen(
|
|
|
1419
1443
|
pending_context: list[str],
|
|
1420
1444
|
*,
|
|
1421
1445
|
status: str,
|
|
1446
|
+
state: dict[str, Any] | None = None,
|
|
1422
1447
|
) -> str:
|
|
1423
1448
|
width = chat_width()
|
|
1424
1449
|
height = max(24, shutil.get_terminal_size((width, 32)).lines)
|
|
1450
|
+
if (state or {}).get("scrollback_mode"):
|
|
1451
|
+
height = min(height, 22)
|
|
1425
1452
|
right_width = min(40, max(32, width // 3))
|
|
1426
1453
|
left_width = max(46, width - right_width - 3)
|
|
1427
1454
|
header = tui_header_lines(workspace, args, last_report, status=status, width=width)
|
|
@@ -1430,12 +1457,13 @@ def build_chat_tui_screen(
|
|
|
1430
1457
|
lines = header
|
|
1431
1458
|
body = tui_body_lines(transcript, left_width, status=status)
|
|
1432
1459
|
side = tui_sidebar_lines(workspace, task_id, args, last_report, pending_context, right_width)
|
|
1433
|
-
|
|
1460
|
+
scroll_offset = max(0, int((state or {}).get("scroll_offset", 0)))
|
|
1461
|
+
body = slice_tui_body(body, body_height, scroll_offset, left_width)
|
|
1434
1462
|
side = side[:body_height]
|
|
1435
1463
|
for index in range(body_height):
|
|
1436
1464
|
left = body[index] if index < len(body) else ""
|
|
1437
1465
|
right = side[index] if index < len(side) else ""
|
|
1438
|
-
lines.append(f"{left
|
|
1466
|
+
lines.append(f"{pad_display(left, left_width)} {chat_color('|', 'dim')} {pad_display(right, right_width)}")
|
|
1439
1467
|
lines.extend(footer)
|
|
1440
1468
|
return "\n".join(lines)
|
|
1441
1469
|
|
|
@@ -1451,10 +1479,10 @@ def tui_header_lines(
|
|
|
1451
1479
|
status_label = status.upper()
|
|
1452
1480
|
status_color = "green" if status == "ready" else "yellow" if status == "running" else "cyan"
|
|
1453
1481
|
tokens = 0 if not last_report else last_report.get("totals", {}).get("total_tokens", 0)
|
|
1454
|
-
title = f"
|
|
1482
|
+
title = f" AKERNEL // {status_label} "
|
|
1455
1483
|
subtitle = (
|
|
1456
|
-
f"{compact_path(workspace.root)}
|
|
1457
|
-
f"primary
|
|
1484
|
+
f"{compact_path(workspace.root)} | provider {args.provider} | "
|
|
1485
|
+
f"primary {primary_model(args)} | aux {auxiliary_model(args)} | tokens {tokens}"
|
|
1458
1486
|
)
|
|
1459
1487
|
return [
|
|
1460
1488
|
chat_color(tui_rule(title, width), status_color, bold=True),
|
|
@@ -1466,13 +1494,13 @@ def tui_header_lines(
|
|
|
1466
1494
|
def tui_footer_lines(width: int) -> list[str]:
|
|
1467
1495
|
return [
|
|
1468
1496
|
tui_rule(" Input ", width),
|
|
1469
|
-
truncate_line("Type a task.
|
|
1497
|
+
truncate_line("Type a task. @ finds files, @1 attaches a match, /up and /down move transcript view, /latest returns to now.", width),
|
|
1470
1498
|
"",
|
|
1471
1499
|
]
|
|
1472
1500
|
|
|
1473
1501
|
|
|
1474
1502
|
def tui_command_strip(width: int) -> str:
|
|
1475
|
-
commands = " /help /status /model /compact /runs /cost @file !cmd "
|
|
1503
|
+
commands = " /help /status /model /compact /runs /cost /up /down @file !cmd "
|
|
1476
1504
|
return chat_color(truncate_line(commands.center(width, "-"), width), "dim")
|
|
1477
1505
|
|
|
1478
1506
|
|
|
@@ -1480,25 +1508,24 @@ def tui_body_lines(transcript: list[dict[str, str]], width: int, *, status: str
|
|
|
1480
1508
|
if not transcript:
|
|
1481
1509
|
return ["No messages yet. Start with one concrete task."]
|
|
1482
1510
|
lines: list[str] = []
|
|
1483
|
-
lines.append(f"
|
|
1484
|
-
lines.append("-" * min(width,
|
|
1511
|
+
lines.append(f"Conversation [{status}]")
|
|
1512
|
+
lines.append("-" * min(width, 24))
|
|
1485
1513
|
for item in transcript:
|
|
1486
1514
|
title = item.get("title", item.get("role", "message"))
|
|
1487
1515
|
role = item.get("role", "system")
|
|
1488
1516
|
label = tui_role_label(role, title)
|
|
1489
1517
|
lines.append("")
|
|
1490
|
-
lines.append(truncate_line(f"
|
|
1491
|
-
prefix = "
|
|
1518
|
+
lines.append(truncate_line(f"{label}", width))
|
|
1519
|
+
prefix = " " if role != "user" else "> "
|
|
1492
1520
|
for line in wrap_plain(item.get("text", ""), width=max(20, width - len(prefix))).splitlines():
|
|
1493
1521
|
lines.append(truncate_line(prefix + line, width))
|
|
1494
|
-
lines.append("")
|
|
1495
1522
|
return lines
|
|
1496
1523
|
|
|
1497
1524
|
|
|
1498
1525
|
def tui_role_label(role: str, title: str) -> str:
|
|
1499
1526
|
labels = {
|
|
1500
1527
|
"user": "YOU",
|
|
1501
|
-
"assistant": "
|
|
1528
|
+
"assistant": "AKERNEL",
|
|
1502
1529
|
"system": "SYSTEM",
|
|
1503
1530
|
}
|
|
1504
1531
|
base = labels.get(role, role.upper())
|
|
@@ -1513,23 +1540,23 @@ def tui_sidebar_lines(
|
|
|
1513
1540
|
pending_context: list[str],
|
|
1514
1541
|
width: int,
|
|
1515
1542
|
) -> list[str]:
|
|
1516
|
-
rows = tui_section("
|
|
1543
|
+
rows = tui_section("Session", width)
|
|
1517
1544
|
rows.extend(
|
|
1518
1545
|
[
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1546
|
+
tui_kv("provider", args.provider, width),
|
|
1547
|
+
tui_kv("profile", getattr(args, "profile", DEFAULT_PROFILE), width),
|
|
1548
|
+
tui_kv("routing", getattr(args, "model_routing", "auto"), width),
|
|
1549
|
+
tui_kv("steps", getattr(args, "max_steps", "?"), width),
|
|
1550
|
+
tui_kv("attached", len(pending_context), width),
|
|
1524
1551
|
"",
|
|
1525
1552
|
]
|
|
1526
1553
|
)
|
|
1527
|
-
rows.extend(tui_section("
|
|
1554
|
+
rows.extend(tui_section("Models", width))
|
|
1528
1555
|
rows.extend(
|
|
1529
1556
|
[
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1557
|
+
tui_kv("primary", primary_model(args), width),
|
|
1558
|
+
tui_kv("aux", auxiliary_model(args), width),
|
|
1559
|
+
tui_kv("review", getattr(args, "aux_review", "auto"), width),
|
|
1533
1560
|
"",
|
|
1534
1561
|
]
|
|
1535
1562
|
)
|
|
@@ -1549,22 +1576,26 @@ def tui_sidebar_lines(
|
|
|
1549
1576
|
|
|
1550
1577
|
|
|
1551
1578
|
def tui_section(title: str, width: int) -> list[str]:
|
|
1552
|
-
label = f"
|
|
1553
|
-
return [label, "-" * min(width, len(label) +
|
|
1579
|
+
label = f"{title}"
|
|
1580
|
+
return [label, "-" * min(width, max(10, len(label) + 4))]
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
def tui_kv(key: str, value: Any, width: int) -> str:
|
|
1584
|
+
return truncate_line(f"{key:<9} {value}", width)
|
|
1554
1585
|
|
|
1555
1586
|
|
|
1556
1587
|
def tui_task_panel(workspace: Workspace, task_id: str, width: int) -> list[str]:
|
|
1557
|
-
rows = tui_section("
|
|
1588
|
+
rows = tui_section("Task", width)
|
|
1558
1589
|
try:
|
|
1559
1590
|
task = TaskStore(workspace).get(task_id)
|
|
1560
1591
|
except (KeyError, FileNotFoundError):
|
|
1561
|
-
rows.extend([
|
|
1592
|
+
rows.extend([tui_kv("id", task_id, width), tui_kv("status", "unknown", width), ""])
|
|
1562
1593
|
return rows
|
|
1563
1594
|
rows.extend(
|
|
1564
1595
|
[
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1596
|
+
tui_kv("id", task.get("id", task_id), width),
|
|
1597
|
+
tui_kv("status", task.get("status", "unknown"), width),
|
|
1598
|
+
tui_kv("title", task.get("title", ""), width),
|
|
1568
1599
|
]
|
|
1569
1600
|
)
|
|
1570
1601
|
plan = task.get("plan")
|
|
@@ -1572,26 +1603,26 @@ def tui_task_panel(workspace: Workspace, task_id: str, width: int) -> list[str]:
|
|
|
1572
1603
|
progress = plan.get("milestones", [])
|
|
1573
1604
|
completed = sum(1 for item in progress if item.get("status") == "completed")
|
|
1574
1605
|
active = next((item for item in progress if item.get("status") == "active"), None)
|
|
1575
|
-
rows.append(f"
|
|
1606
|
+
rows.append(tui_kv("plan", f"{completed}/{len(progress)} done", width))
|
|
1576
1607
|
if active:
|
|
1577
|
-
rows.append(
|
|
1608
|
+
rows.append(tui_kv("active", f"{active.get('id')} {active.get('title', '')}", width))
|
|
1578
1609
|
rows.append("")
|
|
1579
1610
|
return rows
|
|
1580
1611
|
|
|
1581
1612
|
|
|
1582
1613
|
def tui_last_run_panel(report: dict[str, Any], width: int) -> list[str]:
|
|
1583
|
-
rows = tui_section("Last Run
|
|
1614
|
+
rows = tui_section("Last Run", width)
|
|
1584
1615
|
rows.extend(
|
|
1585
1616
|
[
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1617
|
+
tui_kv("id", report.get("id"), width),
|
|
1618
|
+
tui_kv("status", report.get("status"), width),
|
|
1619
|
+
tui_kv("tokens", report.get("totals", {}).get("total_tokens", 0), width),
|
|
1589
1620
|
]
|
|
1590
1621
|
)
|
|
1591
1622
|
steps = report.get("steps", [])
|
|
1592
1623
|
if steps:
|
|
1593
1624
|
compact_actions = " -> ".join(str((step.get("action") or {}).get("action") or "none") for step in steps)
|
|
1594
|
-
rows.append(
|
|
1625
|
+
rows.append(tui_kv("actions", compact_actions, width))
|
|
1595
1626
|
for step in steps[:4]:
|
|
1596
1627
|
action = str((step.get("action") or {}).get("action") or "none")
|
|
1597
1628
|
ok = "ok" if step.get("verifier_ok", True) else "check"
|
|
@@ -1601,6 +1632,18 @@ def tui_last_run_panel(report: dict[str, Any], width: int) -> list[str]:
|
|
|
1601
1632
|
return rows
|
|
1602
1633
|
|
|
1603
1634
|
|
|
1635
|
+
def slice_tui_body(lines: list[str], height: int, scroll_offset: int, width: int) -> list[str]:
|
|
1636
|
+
if len(lines) <= height:
|
|
1637
|
+
return lines
|
|
1638
|
+
offset = max(0, min(scroll_offset, len(lines) - height))
|
|
1639
|
+
end = len(lines) - offset
|
|
1640
|
+
start = max(0, end - height)
|
|
1641
|
+
window = lines[start:end]
|
|
1642
|
+
if offset:
|
|
1643
|
+
window = [truncate_line(f"History view: {offset} line(s) above latest. Use /down or /latest.", width)] + window[1:]
|
|
1644
|
+
return window
|
|
1645
|
+
|
|
1646
|
+
|
|
1604
1647
|
def format_tui_report(report: dict[str, Any]) -> str:
|
|
1605
1648
|
actions = " -> ".join(str((step.get("action") or {}).get("action") or "none") for step in report.get("steps", []))
|
|
1606
1649
|
parts = [
|
|
@@ -1635,7 +1678,34 @@ def wrap_plain(text: str, *, width: int) -> str:
|
|
|
1635
1678
|
|
|
1636
1679
|
def truncate_line(text: str, width: int) -> str:
|
|
1637
1680
|
value = str(text)
|
|
1638
|
-
|
|
1681
|
+
if display_width(value) <= width:
|
|
1682
|
+
return value
|
|
1683
|
+
if width <= 3:
|
|
1684
|
+
return "." * max(0, width)
|
|
1685
|
+
result = ""
|
|
1686
|
+
used = 0
|
|
1687
|
+
for char in value:
|
|
1688
|
+
char_width = char_display_width(char)
|
|
1689
|
+
if used + char_width > width - 3:
|
|
1690
|
+
break
|
|
1691
|
+
result += char
|
|
1692
|
+
used += char_width
|
|
1693
|
+
return result + "..."
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def pad_display(text: str, width: int) -> str:
|
|
1697
|
+
value = truncate_line(text, width)
|
|
1698
|
+
return value + " " * max(0, width - display_width(value))
|
|
1699
|
+
|
|
1700
|
+
|
|
1701
|
+
def display_width(text: str) -> int:
|
|
1702
|
+
return sum(char_display_width(char) for char in str(text))
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
def char_display_width(char: str) -> int:
|
|
1706
|
+
if unicodedata.combining(char):
|
|
1707
|
+
return 0
|
|
1708
|
+
return 2 if unicodedata.east_asian_width(char) in {"F", "W"} else 1
|
|
1639
1709
|
|
|
1640
1710
|
|
|
1641
1711
|
def print_chat_turn_start(request: str, args: argparse.Namespace) -> None:
|
|
@@ -1692,11 +1762,14 @@ def print_chat_help() -> None:
|
|
|
1692
1762
|
("/config", "show setup and environment guidance"),
|
|
1693
1763
|
("/compact", "show the compact task brief used for resume context"),
|
|
1694
1764
|
("/paste", "enter a multi-line task; finish with /end"),
|
|
1695
|
-
("@
|
|
1765
|
+
("@query", "search workspace files; use @1, @2... to attach a listed match"),
|
|
1696
1766
|
("!command", "run a policy-checked command and attach its summary"),
|
|
1697
1767
|
("/task", "print the current task session JSON"),
|
|
1698
1768
|
("/runs", "list recent agent runs"),
|
|
1699
1769
|
("/cost", "print the last agent run cost report"),
|
|
1770
|
+
("/up", "show older transcript lines in the TUI viewport"),
|
|
1771
|
+
("/down", "move the TUI viewport back toward latest messages"),
|
|
1772
|
+
("/latest", "jump the TUI viewport to the latest messages"),
|
|
1700
1773
|
("/clear", "clear and redraw the session header"),
|
|
1701
1774
|
("/exit", "leave the interactive session"),
|
|
1702
1775
|
],
|
|
@@ -1723,6 +1796,101 @@ def print_recent_agent_runs(workspace: Workspace, *, limit: int) -> None:
|
|
|
1723
1796
|
)
|
|
1724
1797
|
|
|
1725
1798
|
|
|
1799
|
+
IGNORED_FILE_FINDER_DIRS = {
|
|
1800
|
+
".git",
|
|
1801
|
+
".hg",
|
|
1802
|
+
".svn",
|
|
1803
|
+
".akernel",
|
|
1804
|
+
".venv",
|
|
1805
|
+
"venv",
|
|
1806
|
+
"node_modules",
|
|
1807
|
+
"__pycache__",
|
|
1808
|
+
".pytest_cache",
|
|
1809
|
+
"dist",
|
|
1810
|
+
"build",
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
|
|
1814
|
+
def attach_chat_file_command(
|
|
1815
|
+
workspace: Workspace,
|
|
1816
|
+
tasks: TaskStore,
|
|
1817
|
+
task_id: str,
|
|
1818
|
+
query: str,
|
|
1819
|
+
pending_context: list[str],
|
|
1820
|
+
state: dict[str, Any] | None = None,
|
|
1821
|
+
) -> None:
|
|
1822
|
+
state = state if state is not None else {}
|
|
1823
|
+
query = query.strip().strip('"').strip("'")
|
|
1824
|
+
if query.isdigit() and state.get("file_matches"):
|
|
1825
|
+
matches = list(state.get("file_matches") or [])
|
|
1826
|
+
index = int(query) - 1
|
|
1827
|
+
if 0 <= index < len(matches):
|
|
1828
|
+
attach_chat_file(workspace, tasks, task_id, str(matches[index]), pending_context)
|
|
1829
|
+
return
|
|
1830
|
+
chat_notice("File Search", f"No cached match @{query}. Use @ to list files again.")
|
|
1831
|
+
return
|
|
1832
|
+
|
|
1833
|
+
if query and (workspace.root / query).is_file():
|
|
1834
|
+
attach_chat_file(workspace, tasks, task_id, query, pending_context)
|
|
1835
|
+
return
|
|
1836
|
+
|
|
1837
|
+
matches = find_workspace_files(workspace.root, query, limit=12)
|
|
1838
|
+
state["file_matches"] = matches
|
|
1839
|
+
if not matches:
|
|
1840
|
+
hint = "Try @readme, @pyproject, or a filename fragment."
|
|
1841
|
+
chat_notice("File Search", f"No files matched `{query or '*'}`. {hint}")
|
|
1842
|
+
return
|
|
1843
|
+
if query and len(matches) == 1:
|
|
1844
|
+
attach_chat_file(workspace, tasks, task_id, matches[0], pending_context)
|
|
1845
|
+
return
|
|
1846
|
+
|
|
1847
|
+
print("")
|
|
1848
|
+
print(chat_color("[ File Search ]", "cyan", bold=True))
|
|
1849
|
+
print(wrap_chat_text("Type @1, @2, ... to attach a result, or keep typing a narrower @query.", indent=" "))
|
|
1850
|
+
for index, path in enumerate(matches, start=1):
|
|
1851
|
+
print(f" @{index:<2} {path}")
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def find_workspace_files(root: Path, query: str, *, limit: int = 12, max_scan: int = 2500) -> list[str]:
|
|
1855
|
+
normalized_query = query.casefold().replace("\\", "/")
|
|
1856
|
+
candidates: list[tuple[int, str]] = []
|
|
1857
|
+
scanned = 0
|
|
1858
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
1859
|
+
dirnames[:] = [
|
|
1860
|
+
name
|
|
1861
|
+
for name in dirnames
|
|
1862
|
+
if name not in IGNORED_FILE_FINDER_DIRS and not name.startswith(".mypy_cache")
|
|
1863
|
+
]
|
|
1864
|
+
for filename in filenames:
|
|
1865
|
+
scanned += 1
|
|
1866
|
+
if scanned > max_scan:
|
|
1867
|
+
break
|
|
1868
|
+
path = Path(dirpath) / filename
|
|
1869
|
+
try:
|
|
1870
|
+
relative = path.relative_to(root).as_posix()
|
|
1871
|
+
except ValueError:
|
|
1872
|
+
continue
|
|
1873
|
+
haystack = relative.casefold()
|
|
1874
|
+
name = filename.casefold()
|
|
1875
|
+
if normalized_query and normalized_query not in haystack:
|
|
1876
|
+
continue
|
|
1877
|
+
score = 0
|
|
1878
|
+
if normalized_query:
|
|
1879
|
+
if name == normalized_query:
|
|
1880
|
+
score -= 40
|
|
1881
|
+
elif name.startswith(normalized_query):
|
|
1882
|
+
score -= 25
|
|
1883
|
+
elif haystack.startswith(normalized_query):
|
|
1884
|
+
score -= 15
|
|
1885
|
+
score += haystack.find(normalized_query)
|
|
1886
|
+
score += relative.count("/") * 2
|
|
1887
|
+
score += len(relative) // 20
|
|
1888
|
+
candidates.append((score, relative))
|
|
1889
|
+
if scanned > max_scan:
|
|
1890
|
+
break
|
|
1891
|
+
return [path for _, path in sorted(candidates, key=lambda item: (item[0], item[1]))[:limit]]
|
|
1892
|
+
|
|
1893
|
+
|
|
1726
1894
|
def attach_chat_file(
|
|
1727
1895
|
workspace: Workspace,
|
|
1728
1896
|
tasks: TaskStore,
|
|
@@ -1323,10 +1323,10 @@ class RuntimeTests(unittest.TestCase):
|
|
|
1323
1323
|
status="ready",
|
|
1324
1324
|
)
|
|
1325
1325
|
|
|
1326
|
-
self.assertIn("
|
|
1327
|
-
self.assertIn("provider
|
|
1326
|
+
self.assertIn("AKERNEL // READY", screen)
|
|
1327
|
+
self.assertIn("provider mock", screen)
|
|
1328
1328
|
self.assertIn("Last Run", screen)
|
|
1329
|
-
self.assertIn("actions
|
|
1329
|
+
self.assertIn("actions respond", screen)
|
|
1330
1330
|
|
|
1331
1331
|
def test_tui_chat_runs_agent_loop_after_user_message(self) -> None:
|
|
1332
1332
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -1353,11 +1353,70 @@ class RuntimeTests(unittest.TestCase):
|
|
|
1353
1353
|
reports = list(workspace.agent_runs_dir.glob("*.json"))
|
|
1354
1354
|
|
|
1355
1355
|
self.assertEqual(len(reports), 1)
|
|
1356
|
-
self.assertIn("
|
|
1356
|
+
self.assertIn("AKERNEL // READY", output)
|
|
1357
1357
|
self.assertIn("Assistant", output)
|
|
1358
1358
|
self.assertIn("Mock agent response", output)
|
|
1359
1359
|
self.assertIn("bye", output)
|
|
1360
1360
|
|
|
1361
|
+
def test_tui_screen_can_render_older_history_window(self) -> None:
|
|
1362
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
1363
|
+
workspace = Workspace(Path(tmp))
|
|
1364
|
+
workspace.init()
|
|
1365
|
+
args = type(
|
|
1366
|
+
"Args",
|
|
1367
|
+
(),
|
|
1368
|
+
{
|
|
1369
|
+
"provider": "mock",
|
|
1370
|
+
"model": None,
|
|
1371
|
+
"aux_model": "gpt-5.3-codex",
|
|
1372
|
+
"profile": "balanced",
|
|
1373
|
+
"max_steps": 3,
|
|
1374
|
+
"model_routing": "auto",
|
|
1375
|
+
"aux_review": "auto",
|
|
1376
|
+
},
|
|
1377
|
+
)()
|
|
1378
|
+
transcript = [
|
|
1379
|
+
{"role": "user", "title": "You", "text": f"message {index}"}
|
|
1380
|
+
for index in range(20)
|
|
1381
|
+
]
|
|
1382
|
+
|
|
1383
|
+
screen = build_chat_tui_screen(
|
|
1384
|
+
workspace,
|
|
1385
|
+
"task123",
|
|
1386
|
+
args,
|
|
1387
|
+
transcript,
|
|
1388
|
+
None,
|
|
1389
|
+
[],
|
|
1390
|
+
status="ready",
|
|
1391
|
+
state={"scroll_offset": 10},
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
self.assertIn("History view", screen)
|
|
1395
|
+
self.assertIn("/down or /latest", screen)
|
|
1396
|
+
|
|
1397
|
+
def test_chat_file_search_lists_and_attaches_numbered_match(self) -> None:
|
|
1398
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
1399
|
+
root = Path(tmp)
|
|
1400
|
+
(root / "alpha.txt").write_text("alpha context works", encoding="utf-8")
|
|
1401
|
+
(root / "beta.txt").write_text("beta context", encoding="utf-8")
|
|
1402
|
+
workspace = Workspace(root)
|
|
1403
|
+
workspace.init()
|
|
1404
|
+
|
|
1405
|
+
with patch("builtins.input", side_effect=["@", "@1", "Use the attached file", "/exit"]):
|
|
1406
|
+
with patch("sys.stdout", new=io.StringIO()) as stdout:
|
|
1407
|
+
main(["--workspace", str(workspace.root), "chat", "--provider", "mock", "--max-steps", "1"])
|
|
1408
|
+
|
|
1409
|
+
output = stdout.getvalue()
|
|
1410
|
+
reports = list(workspace.agent_runs_dir.glob("*.json"))
|
|
1411
|
+
tool_traces = list(workspace.tool_traces_dir.glob("*.json"))
|
|
1412
|
+
|
|
1413
|
+
self.assertEqual(len(reports), 1)
|
|
1414
|
+
self.assertGreaterEqual(len(tool_traces), 1)
|
|
1415
|
+
self.assertIn("File Search", output)
|
|
1416
|
+
self.assertIn("@1", output)
|
|
1417
|
+
self.assertIn("Attached File", output)
|
|
1418
|
+
self.assertIn("Mock agent response", output)
|
|
1419
|
+
|
|
1361
1420
|
def test_tui_screen_surfaces_task_plan_and_command_strip(self) -> None:
|
|
1362
1421
|
with tempfile.TemporaryDirectory() as tmp:
|
|
1363
1422
|
workspace = Workspace(Path(tmp))
|
|
@@ -1393,9 +1452,9 @@ class RuntimeTests(unittest.TestCase):
|
|
|
1393
1452
|
|
|
1394
1453
|
self.assertIn("AKERNEL // READY", screen)
|
|
1395
1454
|
self.assertIn("/compact", screen)
|
|
1396
|
-
self.assertIn("
|
|
1397
|
-
self.assertIn("plan
|
|
1398
|
-
self.assertIn("active
|
|
1455
|
+
self.assertIn("Task", screen)
|
|
1456
|
+
self.assertIn("plan", screen)
|
|
1457
|
+
self.assertIn("active", screen)
|
|
1399
1458
|
|
|
1400
1459
|
def test_bare_akernel_starts_chat_and_initializes_default_workspace(self) -> None:
|
|
1401
1460
|
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
|
{akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/phase2/03-budget-profiles.json
RENAMED
|
File without changes
|
{akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/examples/benchmarks/scale/01-context-pressure.json
RENAMED
|
File without changes
|
{akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/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.5 → akernel_runtime-0.1.6}/src/akernel_runtime.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{akernel_runtime-0.1.5 → akernel_runtime-0.1.6}/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
|