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
induscode/boot/stages.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Boot stage pipeline — the ordered list of :class:`~.contract.Stage`
|
|
2
|
+
transforms that turn a bare :class:`~.contract.BootContext` (argv + workspace
|
|
3
|
+
+ brand) into a fully-resolved one (parsed invocation, materialised
|
|
4
|
+
directories, applied upgrades, resolved startup resources, selected runner).
|
|
5
|
+
|
|
6
|
+
Port of TS ``src/boot/stages.ts``. The pipeline is data, not control flow:
|
|
7
|
+
:data:`STAGES` lists each step in order, and :func:`run_stages` folds an
|
|
8
|
+
*immutable* context through them — every stage receives a context and returns
|
|
9
|
+
its successor (built with :func:`dataclasses.replace`, never by mutating). A
|
|
10
|
+
stage may return an awaitable; the fold awaits each in turn so ordering and
|
|
11
|
+
side-effect sequencing are deterministic.
|
|
12
|
+
|
|
13
|
+
Stage order and intent:
|
|
14
|
+
|
|
15
|
+
1. ``locate-workspace`` — materialise the resolved workspace on disk
|
|
16
|
+
(:func:`~induscode.workspace.ensure_dirs`). Pure path computation already
|
|
17
|
+
happened when the initial context was built; this stage only ``mkdir``\\ s.
|
|
18
|
+
2. ``apply-upgrades`` — fold the idempotent
|
|
19
|
+
:func:`~.upgrade.apply_upgrades` registry over the workspace (a no-op on
|
|
20
|
+
an already-current profile).
|
|
21
|
+
3. ``build-invocation`` — parse ``argv`` into the typed invocation.
|
|
22
|
+
4. ``resolve-resources`` — best-effort construct the framework
|
|
23
|
+
settings/auth/model graph; degrade to a minimal object if a framework
|
|
24
|
+
piece is unavailable.
|
|
25
|
+
5. ``select-runner`` — no-op transform that exists for symmetry and
|
|
26
|
+
tracing (the actual dispatch happens in :mod:`~.boot` after the pipeline,
|
|
27
|
+
so the selected runner can own the exit code).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import inspect
|
|
33
|
+
from collections.abc import Mapping, Sequence
|
|
34
|
+
from dataclasses import replace
|
|
35
|
+
from typing import Final
|
|
36
|
+
|
|
37
|
+
from indusagi.shell_app import Settings
|
|
38
|
+
|
|
39
|
+
from induscode.conductor import CatalogSource, ModelCatalog
|
|
40
|
+
from induscode.workspace import ensure_dirs
|
|
41
|
+
|
|
42
|
+
from .contract import BootContext, Stage, StartupResources
|
|
43
|
+
from .invocation import tokenize_invocation
|
|
44
|
+
from .upgrade import apply_upgrades
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"STAGES",
|
|
48
|
+
"build_invocation",
|
|
49
|
+
"locate_workspace",
|
|
50
|
+
"resolve_resources",
|
|
51
|
+
"run_stages",
|
|
52
|
+
"select_runner_stage",
|
|
53
|
+
"upgrade_stage",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Stages
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _locate_workspace(ctx: BootContext) -> BootContext:
|
|
63
|
+
"""Materialise the resolved workspace directories on disk. The workspace
|
|
64
|
+
record was already computed (pure) when the initial context was
|
|
65
|
+
assembled; this stage only ensures the directory subset exists. Returns
|
|
66
|
+
the same context — only the filesystem side effects happen here."""
|
|
67
|
+
ensure_dirs(ctx.workspace)
|
|
68
|
+
return ctx
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
#: Stage 1 — materialise the workspace directories.
|
|
72
|
+
locate_workspace: Final[Stage] = Stage(name="locate-workspace", apply=_locate_workspace)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def _apply_upgrades(ctx: BootContext) -> BootContext:
|
|
76
|
+
"""Run any pending one-time profile upgrades, idempotently. The driver is
|
|
77
|
+
non-fatal: a step that fails is reported and retried on a later launch,
|
|
78
|
+
never aborting boot. The context is returned unchanged (upgrades touch
|
|
79
|
+
the filesystem, not the context)."""
|
|
80
|
+
await apply_upgrades(ctx.workspace)
|
|
81
|
+
return ctx
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#: Stage 2 — fold the upgrade registry over the workspace.
|
|
85
|
+
#:
|
|
86
|
+
#: Port note: TS exported this stage as ``upgrade``; in Python that name
|
|
87
|
+
#: would shadow the :mod:`induscode.boot.upgrade` subpackage attribute on the
|
|
88
|
+
#: parent package, so it is exported as ``upgrade_stage``.
|
|
89
|
+
upgrade_stage: Final[Stage] = Stage(name="apply-upgrades", apply=_apply_upgrades)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_invocation(ctx: BootContext) -> BootContext:
|
|
93
|
+
"""Parse ``argv`` into the typed invocation and thread it onto the
|
|
94
|
+
context. The initial context carries a pre-parsed invocation (the
|
|
95
|
+
bootstrapper parses once up front for the meta short-circuits); this
|
|
96
|
+
stage replaces it with a fresh parse of the same argv."""
|
|
97
|
+
return replace(ctx, invocation=tokenize_invocation(ctx.argv))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#: Stage 3 — re-parse the command line.
|
|
101
|
+
build_invocation: Final[Stage] = Stage(name="build-invocation", apply=_build_invocation)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_default_settings() -> Settings | Mapping[str, object]:
|
|
105
|
+
"""Resolve the framework's baseline settings. Prefers the framework's
|
|
106
|
+
published ``DEFAULT_SETTINGS``; a load failure degrades to an empty
|
|
107
|
+
mapping without failing boot (the *shape* is what later phases rely on)."""
|
|
108
|
+
try:
|
|
109
|
+
from indusagi.shell_app import DEFAULT_SETTINGS
|
|
110
|
+
|
|
111
|
+
return DEFAULT_SETTINGS
|
|
112
|
+
except Exception:
|
|
113
|
+
return {}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_model_catalog() -> ModelCatalog:
|
|
117
|
+
"""Resolve the model catalog: a fresh conductor
|
|
118
|
+
:class:`~induscode.conductor.ModelCatalog` over the live framework
|
|
119
|
+
registry (constructed fresh so per-process custom-model loading does not
|
|
120
|
+
leak across runs); degrades to an empty catalog when the framework
|
|
121
|
+
registry is unavailable."""
|
|
122
|
+
try:
|
|
123
|
+
return ModelCatalog()
|
|
124
|
+
except Exception:
|
|
125
|
+
return ModelCatalog(
|
|
126
|
+
CatalogSource(providers=lambda: [], models=lambda provider: [])
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _build_startup_resources() -> StartupResources:
|
|
131
|
+
"""Construct the :class:`~.contract.StartupResources` graph, preferring
|
|
132
|
+
framework-owned pieces and falling back to minimal placeholders. The
|
|
133
|
+
credential graph stays an empty placeholder bag — the concrete vault is
|
|
134
|
+
consulted directly by the session assembly."""
|
|
135
|
+
return StartupResources(
|
|
136
|
+
settings=_load_default_settings(),
|
|
137
|
+
auth={},
|
|
138
|
+
models=_load_model_catalog(),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _resolve_resources(ctx: BootContext) -> BootContext:
|
|
143
|
+
"""Best-effort assembly of the startup resource graph."""
|
|
144
|
+
return replace(ctx, resources=_build_startup_resources())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
#: Stage 4 — resolve the settings/auth/model graph (degrade-to-empty).
|
|
148
|
+
resolve_resources: Final[Stage] = Stage(name="resolve-resources", apply=_resolve_resources)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _select_runner(ctx: BootContext) -> BootContext:
|
|
152
|
+
"""Marker / tracing stage for runner selection. The real dispatch is
|
|
153
|
+
performed by :mod:`~.boot` after the pipeline so the chosen runner can
|
|
154
|
+
own the process exit code; this stage keeps the ordered pipeline a
|
|
155
|
+
faithful description of the launch sequence."""
|
|
156
|
+
return ctx
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
#: Stage 5 — tracing no-op for runner selection.
|
|
160
|
+
select_runner_stage: Final[Stage] = Stage(name="select-runner", apply=_select_runner)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# The pipeline + fold
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
#: The launch pipeline, in execution order. Folded by :func:`run_stages`.
|
|
168
|
+
STAGES: Final[tuple[Stage, ...]] = (
|
|
169
|
+
locate_workspace,
|
|
170
|
+
upgrade_stage,
|
|
171
|
+
build_invocation,
|
|
172
|
+
resolve_resources,
|
|
173
|
+
select_runner_stage,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def run_stages(
|
|
178
|
+
initial: BootContext,
|
|
179
|
+
stages: Sequence[Stage] = STAGES,
|
|
180
|
+
) -> BootContext:
|
|
181
|
+
"""Fold an ordered list of :class:`~.contract.Stage` transforms over an
|
|
182
|
+
immutable :class:`~.contract.BootContext`.
|
|
183
|
+
|
|
184
|
+
Each stage receives the current context and returns its successor; the
|
|
185
|
+
result of one stage is the input to the next. Awaits every stage result
|
|
186
|
+
so async ordering is deterministic. The input ``initial`` is never
|
|
187
|
+
mutated — stages produce new contexts via :func:`dataclasses.replace`.
|
|
188
|
+
|
|
189
|
+
:param initial: the seed context (argv + workspace + brand + placeholder
|
|
190
|
+
fields)
|
|
191
|
+
:param stages: the ordered transforms to apply; defaults to :data:`STAGES`
|
|
192
|
+
:returns: the fully-resolved context after every stage has run
|
|
193
|
+
"""
|
|
194
|
+
ctx = initial
|
|
195
|
+
for stage in stages:
|
|
196
|
+
result = stage.apply(ctx)
|
|
197
|
+
ctx = await result if inspect.isawaitable(result) else result
|
|
198
|
+
return ctx
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Upgrade subsystem — public barrel (port of TS ``src/boot/upgrade``).
|
|
2
|
+
|
|
3
|
+
Surfaces the ordered, idempotent profile-upgrade registry and its driver.
|
|
4
|
+
Boot consumers import :func:`apply_upgrades` to run pending one-time
|
|
5
|
+
migrations and the :data:`UPGRADES` registry / :class:`Upgrade` shape for
|
|
6
|
+
inspection and testing. The marker-file bookkeeping is an internal detail of
|
|
7
|
+
the driver and is not re-exported.
|
|
8
|
+
|
|
9
|
+
Port note: the TS barrel also exported ``projectTranscriptDirName`` (the
|
|
10
|
+
per-cwd transcript-dir encoder step 2 used). The Python steps are documented
|
|
11
|
+
no-ops on the fresh ``~/.pindusagi`` root (see :mod:`.upgrades`), so the
|
|
12
|
+
encoder has no consumer and is not ported.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .apply import UpgradeReport, apply_upgrades
|
|
18
|
+
from .upgrades import (
|
|
19
|
+
UPGRADES,
|
|
20
|
+
Upgrade,
|
|
21
|
+
fold_credentials,
|
|
22
|
+
relocate_binaries,
|
|
23
|
+
rename_prompt_dir,
|
|
24
|
+
reshelve_transcripts,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"UPGRADES",
|
|
29
|
+
"Upgrade",
|
|
30
|
+
"UpgradeReport",
|
|
31
|
+
"apply_upgrades",
|
|
32
|
+
"fold_credentials",
|
|
33
|
+
"relocate_binaries",
|
|
34
|
+
"rename_prompt_dir",
|
|
35
|
+
"reshelve_transcripts",
|
|
36
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Upgrade driver — folds the ordered :data:`~.upgrades.UPGRADES` registry
|
|
2
|
+
over a :class:`~induscode.workspace.Workspace`, exactly once per step.
|
|
3
|
+
|
|
4
|
+
Port of TS ``src/boot/upgrade/apply.ts``. The driver is the only place that
|
|
5
|
+
knows about the *marker file*: a small JSON record under the profile
|
|
6
|
+
directory listing the ids of upgrades that have already run
|
|
7
|
+
(``.upgrade-state.json`` with the ``{"appliedIds": [...]}`` shape — kept
|
|
8
|
+
byte-compatible with the TS app since this app owns the file). Each registry
|
|
9
|
+
step is skipped if its id is present in the marker; otherwise it is applied
|
|
10
|
+
and, on success, its id is appended and the marker is persisted. A step that
|
|
11
|
+
raises is *not* recorded (so it is retried next launch) and is reported as a
|
|
12
|
+
warning rather than aborting the remaining steps — one bad migration must
|
|
13
|
+
never wedge startup.
|
|
14
|
+
|
|
15
|
+
The result is purely informational: :attr:`UpgradeReport.applied` lists ids
|
|
16
|
+
run *this* invocation (empty on an already-current profile), and
|
|
17
|
+
:attr:`UpgradeReport.warnings` carries human-readable notes for any step that
|
|
18
|
+
failed. Callers typically log both and continue.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Final
|
|
27
|
+
|
|
28
|
+
from induscode.workspace import Workspace
|
|
29
|
+
|
|
30
|
+
from .upgrades import UPGRADES
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"UpgradeReport",
|
|
34
|
+
"apply_upgrades",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
#: Basename of the marker file recording which upgrade ids have been applied.
|
|
38
|
+
_MARKER_FILENAME: Final[str] = ".upgrade-state.json"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class UpgradeReport:
|
|
43
|
+
"""The outcome of an :func:`apply_upgrades` pass.
|
|
44
|
+
|
|
45
|
+
- ``applied`` — ids of upgrades that ran successfully *this* invocation,
|
|
46
|
+
in apply order. Empty when the profile was already current.
|
|
47
|
+
- ``warnings`` — one entry per step that raised, naming the step and its
|
|
48
|
+
error. Non-fatal: applying continues past a failed step.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
#: Ids successfully applied during this call, in order.
|
|
52
|
+
applied: list[str]
|
|
53
|
+
#: Human-readable notes for steps that failed (non-fatal).
|
|
54
|
+
warnings: list[str]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _marker_path(ws: Workspace) -> Path:
|
|
58
|
+
"""Absolute path of the marker file inside the profile directory."""
|
|
59
|
+
return Path(ws.profile_dir) / _MARKER_FILENAME
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _read_applied_ids(ws: Workspace) -> set[str]:
|
|
63
|
+
"""Read the set of already-applied ids from the marker file. A missing,
|
|
64
|
+
unreadable, or malformed marker is treated as "nothing applied yet" so a
|
|
65
|
+
corrupted marker degrades to re-attempting (idempotent) steps rather than
|
|
66
|
+
crashing."""
|
|
67
|
+
try:
|
|
68
|
+
parsed = json.loads(_marker_path(ws).read_text(encoding="utf-8"))
|
|
69
|
+
ids = parsed.get("appliedIds") if isinstance(parsed, dict) else None
|
|
70
|
+
if not isinstance(ids, list):
|
|
71
|
+
return set()
|
|
72
|
+
return {item for item in ids if isinstance(item, str)}
|
|
73
|
+
except Exception:
|
|
74
|
+
return set()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _write_applied_ids(ws: Workspace, applied_ids: set[str]) -> None:
|
|
78
|
+
"""Persist the marker file with the given applied ids, creating parents
|
|
79
|
+
if needed. Order is the registry order for the ids present, so the file
|
|
80
|
+
reads as the apply history."""
|
|
81
|
+
ordered = [step.id for step in UPGRADES if step.id in applied_ids]
|
|
82
|
+
# Carry through any ids the registry no longer knows (forward compat).
|
|
83
|
+
ordered.extend(sorted(applied_ids - set(ordered)))
|
|
84
|
+
state = {"appliedIds": ordered}
|
|
85
|
+
path = _marker_path(ws)
|
|
86
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
path.write_text(f"{json.dumps(state, indent=2)}\n", encoding="utf-8")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _describe_error(error: BaseException) -> str:
|
|
91
|
+
"""Render a raised value as a single-line message for a warning."""
|
|
92
|
+
text = str(error)
|
|
93
|
+
return text if text else type(error).__name__
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def apply_upgrades(ws: Workspace) -> UpgradeReport:
|
|
97
|
+
"""Apply every not-yet-applied upgrade in :data:`UPGRADES`, in registry
|
|
98
|
+
order, recording each success in the marker file so it never runs again.
|
|
99
|
+
|
|
100
|
+
Steps already named in the marker are skipped. A step that raises is
|
|
101
|
+
recorded as a warning, left unmarked (so it is retried on a later
|
|
102
|
+
launch), and does not block the remaining steps. The marker is rewritten
|
|
103
|
+
after each success, so a crash mid-pass still preserves the progress made
|
|
104
|
+
so far.
|
|
105
|
+
|
|
106
|
+
:param ws: the resolved, absolute on-disk layout to upgrade
|
|
107
|
+
:returns: the ids applied this pass and any non-fatal warnings
|
|
108
|
+
"""
|
|
109
|
+
already_applied = _read_applied_ids(ws)
|
|
110
|
+
applied: list[str] = []
|
|
111
|
+
warnings: list[str] = []
|
|
112
|
+
|
|
113
|
+
for upgrade in UPGRADES:
|
|
114
|
+
if upgrade.id in already_applied:
|
|
115
|
+
continue
|
|
116
|
+
try:
|
|
117
|
+
await upgrade.apply(ws)
|
|
118
|
+
already_applied.add(upgrade.id)
|
|
119
|
+
applied.append(upgrade.id)
|
|
120
|
+
# Persist after each success so partial progress survives a crash.
|
|
121
|
+
_write_applied_ids(ws, already_applied)
|
|
122
|
+
except Exception as error: # noqa: BLE001 — reported, never fatal
|
|
123
|
+
warnings.append(f'upgrade "{upgrade.id}" failed: {_describe_error(error)}')
|
|
124
|
+
|
|
125
|
+
return UpgradeReport(applied=applied, warnings=warnings)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Upgrade registry — the ordered, idempotent catalog of one-time profile
|
|
2
|
+
migrations.
|
|
3
|
+
|
|
4
|
+
Port of TS ``src/boot/upgrade/upgrades.ts``, under the port plan's locked
|
|
5
|
+
migration decision: the Python build's profile root (``~/.pindusagi``)
|
|
6
|
+
**starts empty** — no TS-era legacy layout (``oauth.json`` + embedded
|
|
7
|
+
``apiKeys``, loose ``*.jsonl`` transcripts, a ``tools/`` binary dir, a
|
|
8
|
+
``commands/`` template dir) can ever exist under it. The migration
|
|
9
|
+
*mechanism* is therefore kept fully exercisable (registry → marker-file
|
|
10
|
+
driver → idempotence pinned by the boot tests), while the four TS steps are
|
|
11
|
+
registered as **documented no-ops**:
|
|
12
|
+
|
|
13
|
+
- their :attr:`Upgrade.id` strings are preserved **verbatim** so the ids stay
|
|
14
|
+
reserved — a future step can never accidentally reuse one with different
|
|
15
|
+
semantics, and a marker file written by this build remains meaningful;
|
|
16
|
+
- their bodies perform no filesystem work (each detects "nothing legacy can
|
|
17
|
+
exist here" by construction and returns).
|
|
18
|
+
|
|
19
|
+
Idempotence contract (every step must honor it, no-op or not):
|
|
20
|
+
|
|
21
|
+
- Detect "already done" cheaply and return early without side effects.
|
|
22
|
+
- Treat a *missing* source (the thing being migrated from) as success.
|
|
23
|
+
- Never destroy data.
|
|
24
|
+
|
|
25
|
+
Append new (real) steps to the end of :data:`UPGRADES` — never reorder or
|
|
26
|
+
rename existing ids, as that would re-run already-applied migrations.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from collections.abc import Awaitable, Callable
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
|
|
34
|
+
from induscode.workspace import Workspace
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"UPGRADES",
|
|
38
|
+
"Upgrade",
|
|
39
|
+
"fold_credentials",
|
|
40
|
+
"relocate_binaries",
|
|
41
|
+
"rename_prompt_dir",
|
|
42
|
+
"reshelve_transcripts",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# The Upgrade shape
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class Upgrade:
|
|
53
|
+
"""One ordered, idempotent profile-layout upgrade.
|
|
54
|
+
|
|
55
|
+
An upgrade is pure data plus one async effect. The registry order is the
|
|
56
|
+
apply order; :attr:`id` is the durable key recorded in the marker file
|
|
57
|
+
once :attr:`apply` completes, so it must be stable across releases
|
|
58
|
+
(renaming an id re-runs the step). :attr:`apply` must be safe to invoke
|
|
59
|
+
against a profile in any state — fresh, partially-migrated, or
|
|
60
|
+
fully-migrated.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
#: Durable identifier recorded once this step has run. Never rename.
|
|
64
|
+
id: str
|
|
65
|
+
#: One-line human summary for logs and verbose output.
|
|
66
|
+
describe: str
|
|
67
|
+
#: Perform the upgrade against the resolved workspace (idempotent).
|
|
68
|
+
apply: Callable[[Workspace], Awaitable[None]]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# The four TS migrations, registered as documented no-ops
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def _noop(ws: Workspace) -> None:
|
|
77
|
+
"""The shared no-op body: the legacy source this step migrated from
|
|
78
|
+
belongs to the TS-era ``~/.indusagi/agent`` layout and cannot exist under
|
|
79
|
+
the fresh ``~/.pindusagi`` root, so there is nothing to detect and
|
|
80
|
+
nothing to move. (Idempotent by construction.)"""
|
|
81
|
+
del ws
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#: TS step 1 — folded a legacy split credential layout (a standalone
|
|
85
|
+
#: ``oauth.json`` plus an ``apiKeys`` block embedded in ``settings.json``)
|
|
86
|
+
#: into the single consolidated 0600 ``auth.json``. No-op here: the Python
|
|
87
|
+
#: profile root never carried the split layout; the disk vault
|
|
88
|
+
#: (:mod:`induscode.boot.auth_vault`) writes the consolidated shape from day
|
|
89
|
+
#: one.
|
|
90
|
+
fold_credentials = Upgrade(
|
|
91
|
+
id="fold-credentials-into-secure-auth-file",
|
|
92
|
+
describe="Consolidate legacy oauth.json + settings.apiKeys into a 0600 auth.json (no-op on the fresh root)",
|
|
93
|
+
apply=_noop,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
#: TS step 2 — relocated loose ``*.jsonl`` transcripts written directly under
|
|
97
|
+
#: the profile root into the per-cwd ``sessions/`` tree. No-op here: the
|
|
98
|
+
#: conductor's filesystem backend has always written ``.ndjson`` transcripts
|
|
99
|
+
#: under the cwd-scoped sessions directory.
|
|
100
|
+
reshelve_transcripts = Upgrade(
|
|
101
|
+
id="reshelve-loose-transcripts-into-sessions-dir",
|
|
102
|
+
describe="Move loose session .jsonl files under the profile root into sessions/<cwd>/ (no-op on the fresh root)",
|
|
103
|
+
apply=_noop,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
#: TS step 3 — moved the managed ``fd`` / ``rg`` helper binaries from the
|
|
107
|
+
#: legacy ``tools/`` directory into ``bin/``. No-op here: the Python layout
|
|
108
|
+
#: provisions helpers under ``bin/`` directly (``tools_dir`` == ``bin_dir``).
|
|
109
|
+
relocate_binaries = Upgrade(
|
|
110
|
+
id="relocate-managed-helper-binaries-to-bin",
|
|
111
|
+
describe="Move managed fd/rg helper binaries from the legacy tools/ dir into bin/ (no-op on the fresh root)",
|
|
112
|
+
apply=_noop,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
#: TS step 4 — renamed the legacy ``commands/`` template directory to
|
|
116
|
+
#: ``prompts/``. No-op here: the Python layout creates ``prompts/`` directly.
|
|
117
|
+
rename_prompt_dir = Upgrade(
|
|
118
|
+
id="rename-legacy-commands-dir-to-prompts",
|
|
119
|
+
describe="Rename the legacy commands/ template directory to prompts/ (no-op on the fresh root)",
|
|
120
|
+
apply=_noop,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# The ordered registry
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
#: The ordered list of upgrades. Apply order is array order; ids are the
|
|
129
|
+
#: durable marker keys. Append new steps to the end — never reorder or rename
|
|
130
|
+
#: existing ids.
|
|
131
|
+
UPGRADES: tuple[Upgrade, ...] = (
|
|
132
|
+
fold_credentials,
|
|
133
|
+
reshelve_transcripts,
|
|
134
|
+
relocate_binaries,
|
|
135
|
+
rename_prompt_dir,
|
|
136
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Briefing subsystem — public barrel.
|
|
2
|
+
|
|
3
|
+
Re-exports the FROZEN prompt contract: the declarative system-prompt pipeline
|
|
4
|
+
(:class:`BriefingSection` over a :class:`BriefingContext`, composed by
|
|
5
|
+
:func:`compose_briefing`), the single-pass ``$arg`` macro model
|
|
6
|
+
(:class:`Macro`, :class:`MacroScope`, :data:`MacroToken`), and the
|
|
7
|
+
Agent-Skills capability cards (:class:`SkillCard` parsed from a ``SKILL.md``).
|
|
8
|
+
|
|
9
|
+
Port note: the TS barrel additionally re-exported the table-driven SGR
|
|
10
|
+
machine, the HTML-transcript color layer (``ExportTheme`` / ``ThemeBridge`` /
|
|
11
|
+
``LuminanceLut``), and the publish surface from ``briefing/contract``. In the
|
|
12
|
+
Python build those types belong to ``induscode.transcript_export`` (ported
|
|
13
|
+
separately); this barrel carries only the briefing-owned vocabulary plus the
|
|
14
|
+
shared :class:`BriefingFault`. Consumers import the briefing surface from
|
|
15
|
+
``induscode.briefing`` rather than reaching into individual modules.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .compose import BRIEFING_SECTIONS, compose_briefing
|
|
19
|
+
from .contract import (
|
|
20
|
+
SKILL_DESCRIPTION_LIMIT,
|
|
21
|
+
SKILL_NAME_LIMIT,
|
|
22
|
+
AgentState,
|
|
23
|
+
AgentTool,
|
|
24
|
+
AllToken,
|
|
25
|
+
Briefing,
|
|
26
|
+
BriefingContext,
|
|
27
|
+
BriefingFault,
|
|
28
|
+
BriefingFaultKind,
|
|
29
|
+
BriefingInputs,
|
|
30
|
+
BriefingSection,
|
|
31
|
+
ContextDoc,
|
|
32
|
+
ImageContent,
|
|
33
|
+
LiteralToken,
|
|
34
|
+
Macro,
|
|
35
|
+
MacroOrigin,
|
|
36
|
+
MacroScope,
|
|
37
|
+
MacroToken,
|
|
38
|
+
MacroTokenKind,
|
|
39
|
+
PositionalToken,
|
|
40
|
+
SkillCard,
|
|
41
|
+
SkillDiagnostic,
|
|
42
|
+
SkillFrontmatter,
|
|
43
|
+
SkillLoad,
|
|
44
|
+
SkillOutcomeKind,
|
|
45
|
+
SliceToken,
|
|
46
|
+
SubagentBrief,
|
|
47
|
+
TextContent,
|
|
48
|
+
briefing_fault,
|
|
49
|
+
)
|
|
50
|
+
from .macros import (
|
|
51
|
+
FrontmatterSplit,
|
|
52
|
+
apply_macros,
|
|
53
|
+
build_macro_scope,
|
|
54
|
+
expand_invocation,
|
|
55
|
+
load_macros,
|
|
56
|
+
read_macro_file,
|
|
57
|
+
resolve_tokens,
|
|
58
|
+
scan_macro_body,
|
|
59
|
+
set_legacy_macro_reporter,
|
|
60
|
+
split_frontmatter,
|
|
61
|
+
)
|
|
62
|
+
from .skills import (
|
|
63
|
+
SkillRoot,
|
|
64
|
+
gather_skill_cards,
|
|
65
|
+
load_skill_cards,
|
|
66
|
+
model_invocable_cards,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"AgentState",
|
|
71
|
+
"AgentTool",
|
|
72
|
+
"AllToken",
|
|
73
|
+
"BRIEFING_SECTIONS",
|
|
74
|
+
"Briefing",
|
|
75
|
+
"BriefingContext",
|
|
76
|
+
"BriefingFault",
|
|
77
|
+
"BriefingFaultKind",
|
|
78
|
+
"BriefingInputs",
|
|
79
|
+
"BriefingSection",
|
|
80
|
+
"ContextDoc",
|
|
81
|
+
"FrontmatterSplit",
|
|
82
|
+
"ImageContent",
|
|
83
|
+
"LiteralToken",
|
|
84
|
+
"Macro",
|
|
85
|
+
"MacroOrigin",
|
|
86
|
+
"MacroScope",
|
|
87
|
+
"MacroToken",
|
|
88
|
+
"MacroTokenKind",
|
|
89
|
+
"PositionalToken",
|
|
90
|
+
"SKILL_DESCRIPTION_LIMIT",
|
|
91
|
+
"SKILL_NAME_LIMIT",
|
|
92
|
+
"SkillCard",
|
|
93
|
+
"SkillDiagnostic",
|
|
94
|
+
"SkillFrontmatter",
|
|
95
|
+
"SkillLoad",
|
|
96
|
+
"SkillOutcomeKind",
|
|
97
|
+
"SkillRoot",
|
|
98
|
+
"SliceToken",
|
|
99
|
+
"SubagentBrief",
|
|
100
|
+
"TextContent",
|
|
101
|
+
"apply_macros",
|
|
102
|
+
"briefing_fault",
|
|
103
|
+
"build_macro_scope",
|
|
104
|
+
"compose_briefing",
|
|
105
|
+
"expand_invocation",
|
|
106
|
+
"gather_skill_cards",
|
|
107
|
+
"load_macros",
|
|
108
|
+
"load_skill_cards",
|
|
109
|
+
"model_invocable_cards",
|
|
110
|
+
"read_macro_file",
|
|
111
|
+
"resolve_tokens",
|
|
112
|
+
"scan_macro_body",
|
|
113
|
+
"set_legacy_macro_reporter",
|
|
114
|
+
"split_frontmatter",
|
|
115
|
+
]
|