induscode 0.1.0__py3-none-any.whl
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.
- induscode/__init__.py +56 -0
- induscode/addons/__init__.py +176 -0
- induscode/addons/contract.py +923 -0
- induscode/addons/dispatch/__init__.py +43 -0
- induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode/addons/host.py +469 -0
- induscode/addons/loader.py +314 -0
- induscode/addons/manifest.py +232 -0
- induscode/addons/surface.py +199 -0
- induscode/boot/__init__.py +108 -0
- induscode/boot/auth_vault.py +323 -0
- induscode/boot/boot.py +210 -0
- induscode/boot/contract.py +223 -0
- induscode/boot/invocation.py +117 -0
- induscode/boot/runners/__init__.py +42 -0
- induscode/boot/runners/link_runner.py +82 -0
- induscode/boot/runners/oneshot_runner.py +85 -0
- induscode/boot/runners/registry.py +46 -0
- induscode/boot/runners/repl_runner.py +340 -0
- induscode/boot/runners/session.py +549 -0
- induscode/boot/stages.py +198 -0
- induscode/boot/upgrade/__init__.py +36 -0
- induscode/boot/upgrade/apply.py +125 -0
- induscode/boot/upgrade/upgrades.py +136 -0
- induscode/briefing/__init__.py +115 -0
- induscode/briefing/compose.py +414 -0
- induscode/briefing/contract.py +528 -0
- induscode/briefing/macros.py +721 -0
- induscode/briefing/skills.py +417 -0
- induscode/capability_deck/__init__.py +233 -0
- induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode/capability_deck/builtin_bridge.py +358 -0
- induscode/capability_deck/cards/__init__.py +116 -0
- induscode/capability_deck/cards/bg_process.py +482 -0
- induscode/capability_deck/cards/memory.py +226 -0
- induscode/capability_deck/cards/saas.py +280 -0
- induscode/capability_deck/cards/task.py +256 -0
- induscode/capability_deck/cards/todo.py +312 -0
- induscode/capability_deck/contract.py +450 -0
- induscode/capability_deck/manifest.py +126 -0
- induscode/capability_deck/provision.py +217 -0
- induscode/channels/__init__.py +146 -0
- induscode/channels/contract.py +585 -0
- induscode/channels/framer.py +132 -0
- induscode/channels/link/__init__.py +50 -0
- induscode/channels/link/dialog.py +246 -0
- induscode/channels/link/driver.py +308 -0
- induscode/channels/link/server.py +217 -0
- induscode/channels/oneshot.py +178 -0
- induscode/channels/ops.py +140 -0
- induscode/channels/session_ops.py +172 -0
- induscode/conductor/__init__.py +240 -0
- induscode/conductor/catalog.py +309 -0
- induscode/conductor/conductor.py +1084 -0
- induscode/conductor/contract.py +1035 -0
- induscode/conductor/matcher.py +291 -0
- induscode/conductor/serialize.py +575 -0
- induscode/conductor/signal_hub.py +382 -0
- induscode/conductor/skill_parse.py +294 -0
- induscode/conductor/transcript_store.py +449 -0
- induscode/console/__init__.py +236 -0
- induscode/console/app.py +1677 -0
- induscode/console/components/__init__.py +62 -0
- induscode/console/components/banner.py +499 -0
- induscode/console/components/banner_sweep.py +188 -0
- induscode/console/components/emblem.py +181 -0
- induscode/console/components/status_bar.py +102 -0
- induscode/console/contract.py +836 -0
- induscode/console/input/__init__.py +107 -0
- induscode/console/input/chord.py +197 -0
- induscode/console/input/dir_reader.py +113 -0
- induscode/console/input/intents.py +258 -0
- induscode/console/input/providers.py +469 -0
- induscode/console/mount.py +137 -0
- induscode/console/overlays/__init__.py +94 -0
- induscode/console/overlays/auth.py +503 -0
- induscode/console/overlays/pickers.py +526 -0
- induscode/console/overlays/router.py +129 -0
- induscode/console/overlays/sessions.py +232 -0
- induscode/console/reducer.py +145 -0
- induscode/console/resume_picker.py +156 -0
- induscode/console/slash_commands/__init__.py +78 -0
- induscode/console/slash_commands/builtins.py +254 -0
- induscode/console/slash_commands/dynamic.py +217 -0
- induscode/console/slash_commands/integrations.py +949 -0
- induscode/console/slash_commands/transcript.py +404 -0
- induscode/console/slash_commands/workbench.py +430 -0
- induscode/console/startup.py +434 -0
- induscode/console/theme/__init__.py +44 -0
- induscode/console/theme/adapter.py +168 -0
- induscode/console/theme/palette.py +128 -0
- induscode/console/theme/resolve.py +123 -0
- induscode/console/theme/tokens.py +185 -0
- induscode/console_slash/__init__.py +111 -0
- induscode/console_slash/contract.py +185 -0
- induscode/console_slash/registry.py +140 -0
- induscode/console_slash/resolve.py +194 -0
- induscode/console_slash/shared.py +172 -0
- induscode/entry.py +108 -0
- induscode/insight/__init__.py +153 -0
- induscode/insight/collector.py +73 -0
- induscode/insight/replay.py +305 -0
- induscode/insight/wrapper.py +1115 -0
- induscode/kit/__init__.py +82 -0
- induscode/kit/clipboard_image.py +215 -0
- induscode/kit/external_editor.py +120 -0
- induscode/kit/image.py +188 -0
- induscode/kit/shell.py +89 -0
- induscode/kit/tool_fetch.py +288 -0
- induscode/launch/__init__.py +224 -0
- induscode/launch/catalog.py +310 -0
- induscode/launch/contract.py +569 -0
- induscode/launch/credentials.py +852 -0
- induscode/launch/invocation/__init__.py +39 -0
- induscode/launch/invocation/attachments.py +281 -0
- induscode/launch/invocation/flags.py +210 -0
- induscode/launch/invocation/read.py +369 -0
- induscode/launch/invocation/usage.py +110 -0
- induscode/launch/oauth.py +808 -0
- induscode/launch/packages.py +299 -0
- induscode/launch/pickers.py +291 -0
- induscode/py.typed +0 -0
- induscode/runtime_bridge/__init__.py +166 -0
- induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode/runtime_bridge/broker.py +397 -0
- induscode/runtime_bridge/contract.py +734 -0
- induscode/runtime_bridge/sink.py +351 -0
- induscode/sessions/__init__.py +25 -0
- induscode/sessions/contract.py +119 -0
- induscode/sessions/library.py +350 -0
- induscode/settings/__init__.py +47 -0
- induscode/settings/contract.py +313 -0
- induscode/settings/manager.py +268 -0
- induscode/transcript_export/__init__.py +109 -0
- induscode/transcript_export/contract.py +522 -0
- induscode/transcript_export/publish.py +455 -0
- induscode/transcript_export/sgr.py +566 -0
- induscode/transcript_export/template.py +319 -0
- induscode/transcript_export/theme_bridge.py +325 -0
- induscode/window_budget/__init__.py +76 -0
- induscode/window_budget/budget/__init__.py +26 -0
- induscode/window_budget/budget/estimate.py +273 -0
- induscode/window_budget/budget/gate.py +60 -0
- induscode/window_budget/budget/slice.py +145 -0
- induscode/window_budget/condenser.py +170 -0
- induscode/window_budget/contract.py +329 -0
- induscode/window_budget/summarize/__init__.py +33 -0
- induscode/window_budget/summarize/condense.py +212 -0
- induscode/window_budget/summarize/prompt.py +241 -0
- induscode/workspace/__init__.py +30 -0
- induscode/workspace/brand.py +96 -0
- induscode/workspace/locator.py +269 -0
- induscode-0.1.0.dist-info/METADATA +97 -0
- induscode-0.1.0.dist-info/RECORD +167 -0
- induscode-0.1.0.dist-info/WHEEL +4 -0
- induscode-0.1.0.dist-info/entry_points.txt +3 -0
- induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
- induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Transcript & session-control slash commands.
|
|
2
|
+
|
|
3
|
+
Port of TS ``src/console/slash/commands/transcript.ts``: the first topic
|
|
4
|
+
group of the console's slash catalog — the verbs that reset, rename, branch,
|
|
5
|
+
inspect, or leave the live session view. Every row is a thin
|
|
6
|
+
:class:`~induscode.console_slash.SlashCommand` whose ``run`` drives the
|
|
7
|
+
injected :class:`~induscode.console_slash.SlashContext` — a reducer event, a
|
|
8
|
+
conductor call, a modal, or an exit request — and settles to ``HANDLED``.
|
|
9
|
+
Nothing here reaches into the TUI, the filesystem, or a provider SDK
|
|
10
|
+
directly; backend work flows through the ``SessionConductor``.
|
|
11
|
+
|
|
12
|
+
The rename, stats, condense, and reload verbs are backed by real conductor
|
|
13
|
+
surface (``set_session_name`` / ``stats`` / ``condense``). Where the
|
|
14
|
+
conductor cannot accept a richer input (the condense path is parameterless
|
|
15
|
+
on this build), the closest real action is taken and the status line
|
|
16
|
+
explains precisely what was applied.
|
|
17
|
+
|
|
18
|
+
Async/fire-and-forget translation (the plan's locked cross-cutting rule 3):
|
|
19
|
+
every ``run`` here is ``async def`` and the dispatcher awaits it, so the TS
|
|
20
|
+
``void (async () => {...})()`` bodies of ``/summarize-context`` and
|
|
21
|
+
``/reload`` run *inline* — the busy→done status sequence becomes
|
|
22
|
+
deterministic. Only the conductor reset behind ``/clear`` / ``/new`` stays
|
|
23
|
+
fire-and-forget (it must not delay the success toast); it rides a tracked
|
|
24
|
+
:func:`asyncio.create_task` whose failure is swallowed into a status warn —
|
|
25
|
+
never a bare task, never a crash.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import json
|
|
32
|
+
import time
|
|
33
|
+
from typing import TYPE_CHECKING, Final
|
|
34
|
+
|
|
35
|
+
from indusagi.react_ink import StatusMessage, UiDisplayBlock
|
|
36
|
+
|
|
37
|
+
from induscode.console_slash import (
|
|
38
|
+
HANDLED,
|
|
39
|
+
SlashCommand,
|
|
40
|
+
SlashContext,
|
|
41
|
+
SlashOutcome,
|
|
42
|
+
info,
|
|
43
|
+
warn,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from induscode.conductor import SessionStats
|
|
48
|
+
|
|
49
|
+
__all__ = ["transcript_commands"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Block-id minting (TS `Date.now().toString(36)`)
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
_BASE36_DIGITS: Final = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _base36(value: int) -> str:
|
|
60
|
+
"""Render a non-negative int in base 36 (TS ``Number.toString(36)``)."""
|
|
61
|
+
if value <= 0:
|
|
62
|
+
return "0"
|
|
63
|
+
digits: list[str] = []
|
|
64
|
+
while value:
|
|
65
|
+
value, rem = divmod(value, 36)
|
|
66
|
+
digits.append(_BASE36_DIGITS[rem])
|
|
67
|
+
return "".join(reversed(digits))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _now_ms() -> int:
|
|
71
|
+
"""Epoch milliseconds (TS ``Date.now()``)."""
|
|
72
|
+
return int(time.time() * 1000)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Fire-and-forget task anchoring
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
#: Strong references to in-flight background tasks so they cannot be
|
|
80
|
+
#: garbage-collected mid-flight (the asyncio create_task anchoring pattern).
|
|
81
|
+
#: Each settled coroutine swallows its own failure into a status warn, so a
|
|
82
|
+
#: task here never raises. The M5 console host may later own this set on its
|
|
83
|
+
#: per-console holder; the anchor-and-discard discipline stays the same.
|
|
84
|
+
_BACKGROUND_TASKS: Final[set[asyncio.Task[None]]] = set()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _spawn(coro) -> None:
|
|
88
|
+
"""Run ``coro`` as a tracked background task (never a bare task)."""
|
|
89
|
+
task = asyncio.create_task(coro)
|
|
90
|
+
_BACKGROUND_TASKS.add(task)
|
|
91
|
+
task.add_done_callback(_BACKGROUND_TASKS.discard)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# View wipe + session reset
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _wipe_view(ctx: SlashContext) -> None:
|
|
100
|
+
"""Blank everything the console is rendering: the row list, the display
|
|
101
|
+
blocks, and the status line. This is the view half of a clear;
|
|
102
|
+
:func:`_start_new_session` pairs it with the conductor-side conversation
|
|
103
|
+
reset."""
|
|
104
|
+
ctx.dispatch({"type": "rows:set", "rows": []})
|
|
105
|
+
ctx.dispatch({"type": "blocks:clear"})
|
|
106
|
+
ctx.dispatch({"type": "status:clear"})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _start_new_session(ctx: SlashContext, toast: str) -> SlashOutcome:
|
|
110
|
+
"""Start a fresh session: wipe the rendered view, then drop the
|
|
111
|
+
conductor's conversation and open a new session id via ``new_session``.
|
|
112
|
+
|
|
113
|
+
The conductor reset is what actually empties the transcript — the
|
|
114
|
+
rendered conversation comes from ``conductor.messages()``, so clearing
|
|
115
|
+
only the view would leave every prior turn on screen (the old ``/clear``
|
|
116
|
+
bug). The reset is effectively synchronous internally; fire it without
|
|
117
|
+
blocking the success toast and only surface a status if it unexpectedly
|
|
118
|
+
rejects.
|
|
119
|
+
"""
|
|
120
|
+
_wipe_view(ctx)
|
|
121
|
+
ctx.set_status(info(toast))
|
|
122
|
+
|
|
123
|
+
async def settle() -> None:
|
|
124
|
+
try:
|
|
125
|
+
await ctx.conductor.new_session()
|
|
126
|
+
except Exception:
|
|
127
|
+
ctx.set_status(warn("Could not start a new session."))
|
|
128
|
+
|
|
129
|
+
_spawn(settle())
|
|
130
|
+
return HANDLED
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _run_clear(ctx: SlashContext) -> SlashOutcome:
|
|
134
|
+
"""Clear the conversation and begin a brand-new session."""
|
|
135
|
+
return _start_new_session(ctx, "Started a new session.")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def _run_new(ctx: SlashContext) -> SlashOutcome:
|
|
139
|
+
"""Begin a fresh session for a new line of work (same as ``/clear``)."""
|
|
140
|
+
return _start_new_session(ctx, "Started a new session.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# Condense (/summarize-context)
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def _run_compact(ctx: SlashContext) -> SlashOutcome:
|
|
149
|
+
"""Condense the live transcript to reclaim context window.
|
|
150
|
+
|
|
151
|
+
Shows a busy status, then drives the conductor's condense path. With
|
|
152
|
+
trailing instructions present the command first records them as a
|
|
153
|
+
session note via the shell-note path so they re-enter the agent's
|
|
154
|
+
context to steer the next condense, then runs the parameterless
|
|
155
|
+
``condense`` — the one condense entry point this build exposes. A
|
|
156
|
+
completion status reports what ran once the path settles.
|
|
157
|
+
"""
|
|
158
|
+
instructions = ctx.args.strip()
|
|
159
|
+
busy_text = (
|
|
160
|
+
f"Condensing with guidance: {instructions}"
|
|
161
|
+
if instructions
|
|
162
|
+
else "Condensing conversation context..."
|
|
163
|
+
)
|
|
164
|
+
ctx.set_status(StatusMessage(kind="busy", text=busy_text))
|
|
165
|
+
try:
|
|
166
|
+
if instructions:
|
|
167
|
+
# JSON quoting doubles as shell quoting here, exactly as the TS
|
|
168
|
+
# `JSON.stringify` interpolation did (ensure_ascii=False keeps
|
|
169
|
+
# non-ASCII guidance verbatim, like JSON.stringify).
|
|
170
|
+
quoted = json.dumps(
|
|
171
|
+
f"Compaction guidance: {instructions}", ensure_ascii=False
|
|
172
|
+
)
|
|
173
|
+
await ctx.conductor.execute_bash(f"printf '%s' {quoted}")
|
|
174
|
+
await ctx.conductor.condense()
|
|
175
|
+
ctx.set_status(
|
|
176
|
+
info(
|
|
177
|
+
"Context condensed (guidance recorded for the agent)."
|
|
178
|
+
if instructions
|
|
179
|
+
else "Context condensed."
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
except Exception:
|
|
183
|
+
ctx.set_status(
|
|
184
|
+
warn("Condense could not complete; the transcript is unchanged.")
|
|
185
|
+
)
|
|
186
|
+
return HANDLED
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
# Pickers (/resume, /branch, /timeline)
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def _run_resume(ctx: SlashContext) -> SlashOutcome:
|
|
195
|
+
"""Raise the session list to resume a persisted session."""
|
|
196
|
+
ctx.open_modal("sessions")
|
|
197
|
+
return HANDLED
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def _run_fork(ctx: SlashContext) -> SlashOutcome:
|
|
201
|
+
"""Raise the prior-user-turn picker to branch the transcript."""
|
|
202
|
+
ctx.open_modal("userTurns")
|
|
203
|
+
return HANDLED
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
async def _run_tree(ctx: SlashContext) -> SlashOutcome:
|
|
207
|
+
"""Raise the transcript-tree navigator."""
|
|
208
|
+
ctx.open_modal("tree")
|
|
209
|
+
return HANDLED
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# Session stats (/session)
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _stats_markdown(stats: SessionStats, name: str | None) -> str:
|
|
218
|
+
"""Render a session-stats tally as a Markdown table for the display
|
|
219
|
+
block."""
|
|
220
|
+
dollars = f"${stats.cost:.4f}"
|
|
221
|
+
t = stats.tokens
|
|
222
|
+
rows: tuple[tuple[str, str], ...] = (
|
|
223
|
+
("Session", f"{name} ({stats.sessionId})" if name else stats.sessionId),
|
|
224
|
+
("Messages", str(stats.totalMessages)),
|
|
225
|
+
("User / assistant", f"{stats.userMessages} / {stats.assistantMessages}"),
|
|
226
|
+
("Tool calls / results", f"{stats.toolCalls} / {stats.toolResults}"),
|
|
227
|
+
("Tokens (in / out)", f"{t.input} / {t.output}"),
|
|
228
|
+
("Tokens (cache r/w)", f"{t.cacheRead} / {t.cacheWrite}"),
|
|
229
|
+
("Tokens (total)", str(t.total)),
|
|
230
|
+
("Cost", dollars),
|
|
231
|
+
)
|
|
232
|
+
body = "\n".join(f"| {key} | {value} |" for key, value in rows)
|
|
233
|
+
return f"| Field | Value |\n| --- | --- |\n{body}"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def _run_session(ctx: SlashContext) -> SlashOutcome:
|
|
237
|
+
"""Append a session-statistics display block.
|
|
238
|
+
|
|
239
|
+
Reads a live ``SessionStats`` tally from the conductor and renders it as
|
|
240
|
+
a changelog-kind display block (the only block kind the surface
|
|
241
|
+
supports), plus a one-line info toast. Distinct from ``/resume``, which
|
|
242
|
+
opens the session picker.
|
|
243
|
+
"""
|
|
244
|
+
stats = ctx.conductor.stats()
|
|
245
|
+
name = ctx.conductor.session_name()
|
|
246
|
+
block = UiDisplayBlock(
|
|
247
|
+
id=f"session-stats-{_base36(_now_ms())}",
|
|
248
|
+
title=f"Session: {name}" if name else "Session statistics",
|
|
249
|
+
markdown=_stats_markdown(stats, name),
|
|
250
|
+
timestamp=_now_ms(),
|
|
251
|
+
)
|
|
252
|
+
ctx.append_block(block)
|
|
253
|
+
ctx.set_status(
|
|
254
|
+
info(
|
|
255
|
+
f"{stats.totalMessages} messages, {stats.tokens.total} tokens, "
|
|
256
|
+
f"${stats.cost:.4f}."
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
return HANDLED
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# Naming (/name)
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def _run_name(ctx: SlashContext) -> SlashOutcome:
|
|
268
|
+
"""Name the live session, or report its current name.
|
|
269
|
+
|
|
270
|
+
With an argument the command stores the label through the conductor's
|
|
271
|
+
``set_session_name`` and confirms it. Bare, it reads back the current
|
|
272
|
+
``session_name`` as a status line rather than warning, so the verb
|
|
273
|
+
doubles as an inspector.
|
|
274
|
+
"""
|
|
275
|
+
label = ctx.args.strip()
|
|
276
|
+
if len(label) == 0:
|
|
277
|
+
current = ctx.conductor.session_name()
|
|
278
|
+
ctx.set_status(
|
|
279
|
+
info("This session has no name. Set one with /name <label>.")
|
|
280
|
+
if current is None or len(current) == 0
|
|
281
|
+
else info(f"Current session name: {current}")
|
|
282
|
+
)
|
|
283
|
+
return HANDLED
|
|
284
|
+
ctx.conductor.set_session_name(label)
|
|
285
|
+
ctx.set_status(info(f'Session named "{label}".'))
|
|
286
|
+
return HANDLED
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
# Reload (/reload)
|
|
291
|
+
# ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
async def _run_reload(ctx: SlashContext) -> SlashOutcome:
|
|
295
|
+
"""Re-read the live session's resources.
|
|
296
|
+
|
|
297
|
+
Drives the conductor through a no-op tree navigation onto its current
|
|
298
|
+
leaf, which rebuilds and replays the active branch onto the agent — the
|
|
299
|
+
closest real "reload session resources" action this surface exposes —
|
|
300
|
+
then reports it. When the transcript is empty (no leaf yet) there is
|
|
301
|
+
nothing to rebuild, so the command reports that instead of issuing a
|
|
302
|
+
navigation.
|
|
303
|
+
"""
|
|
304
|
+
leaf = ctx.conductor.snapshot().head.leaf
|
|
305
|
+
if leaf is None:
|
|
306
|
+
ctx.set_status(info("Session is empty; nothing to reload."))
|
|
307
|
+
return HANDLED
|
|
308
|
+
ctx.set_status(StatusMessage(kind="busy", text="Reloading session resources..."))
|
|
309
|
+
try:
|
|
310
|
+
await ctx.conductor.navigate_tree(leaf)
|
|
311
|
+
ctx.set_status(info("Reloaded session resources."))
|
|
312
|
+
except Exception:
|
|
313
|
+
ctx.set_status(warn("Could not reload session resources."))
|
|
314
|
+
return HANDLED
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
# Leaving (/quit, /exit)
|
|
319
|
+
# ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
async def _run_quit(ctx: SlashContext) -> SlashOutcome:
|
|
323
|
+
"""Ask the host process to leave the interactive console."""
|
|
324
|
+
ctx.request_exit()
|
|
325
|
+
return HANDLED
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
async def _run_exit(ctx: SlashContext) -> SlashOutcome:
|
|
329
|
+
"""Alternate token for leaving the interactive console."""
|
|
330
|
+
ctx.request_exit()
|
|
331
|
+
return HANDLED
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
# The group, in listing order
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
#: The transcript / session-control group, in listing order. Appended into
|
|
339
|
+
#: the slash registry by the catalog integrator; this module owns only the
|
|
340
|
+
#: rows, never the wiring.
|
|
341
|
+
transcript_commands: list[SlashCommand] = [
|
|
342
|
+
SlashCommand(
|
|
343
|
+
name="clear",
|
|
344
|
+
summary="Clear the conversation and start a new session",
|
|
345
|
+
run=_run_clear,
|
|
346
|
+
aliases=("reset",),
|
|
347
|
+
),
|
|
348
|
+
SlashCommand(
|
|
349
|
+
name="new",
|
|
350
|
+
summary="Start a fresh session",
|
|
351
|
+
run=_run_new,
|
|
352
|
+
),
|
|
353
|
+
SlashCommand(
|
|
354
|
+
name="summarize-context",
|
|
355
|
+
summary="Condense the conversation context",
|
|
356
|
+
run=_run_compact,
|
|
357
|
+
aliases=("condense", "compact"),
|
|
358
|
+
takes_args=True,
|
|
359
|
+
),
|
|
360
|
+
SlashCommand(
|
|
361
|
+
name="resume",
|
|
362
|
+
summary="Resume a persisted session",
|
|
363
|
+
run=_run_resume,
|
|
364
|
+
aliases=("sessions",),
|
|
365
|
+
),
|
|
366
|
+
SlashCommand(
|
|
367
|
+
name="session",
|
|
368
|
+
summary="Show live session statistics (ids, counts, tokens, cost)",
|
|
369
|
+
run=_run_session,
|
|
370
|
+
),
|
|
371
|
+
SlashCommand(
|
|
372
|
+
name="branch",
|
|
373
|
+
summary="Branch the transcript from a prior turn",
|
|
374
|
+
run=_run_fork,
|
|
375
|
+
aliases=("fork",),
|
|
376
|
+
),
|
|
377
|
+
SlashCommand(
|
|
378
|
+
name="timeline",
|
|
379
|
+
summary="Navigate the transcript tree",
|
|
380
|
+
run=_run_tree,
|
|
381
|
+
aliases=("tree",),
|
|
382
|
+
),
|
|
383
|
+
SlashCommand(
|
|
384
|
+
name="name",
|
|
385
|
+
summary="Name the session, or show the current name",
|
|
386
|
+
run=_run_name,
|
|
387
|
+
takes_args=True,
|
|
388
|
+
),
|
|
389
|
+
SlashCommand(
|
|
390
|
+
name="reload",
|
|
391
|
+
summary="Reload session resources",
|
|
392
|
+
run=_run_reload,
|
|
393
|
+
),
|
|
394
|
+
SlashCommand(
|
|
395
|
+
name="quit",
|
|
396
|
+
summary="Leave the interactive console",
|
|
397
|
+
run=_run_quit,
|
|
398
|
+
),
|
|
399
|
+
SlashCommand(
|
|
400
|
+
name="exit",
|
|
401
|
+
summary="Leave the interactive console",
|
|
402
|
+
run=_run_exit,
|
|
403
|
+
),
|
|
404
|
+
]
|