@north-light/crouter 0.3.163 → 0.3.165
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.
- package/dist/build-root.d.ts +6 -6
- package/dist/build-root.js +12 -13
- package/dist/builtin-memory/00-runtime-base.md +3 -4
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
- package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
- package/dist/builtin-memory/advisor/council.md +40 -0
- package/dist/builtin-memory/design.md +8 -11
- package/dist/builtin-memory/development.md +6 -9
- package/dist/builtin-memory/internal/INDEX.md +5 -5
- package/dist/builtin-memory/internal/agent-shaping.md +5 -7
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/marketplaces.md +13 -14
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-memory/internal/plugins.md +61 -45
- package/dist/builtin-memory/planning.md +4 -7
- package/dist/builtin-memory/spec.md +9 -12
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
- package/dist/cli.js +1 -1
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
- package/dist/clients/attach/__tests__/context-message.test.js +31 -20
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +88 -89
- package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
- package/dist/clients/attach/render/condensed-history.js +60 -0
- package/dist/clients/attach/render/context-message.js +9 -14
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/session/bindings.d.ts +5 -3
- package/dist/clients/attach/session/bindings.js +7 -13
- package/dist/clients/attach/session/keys.d.ts +2 -0
- package/dist/clients/attach/session/keys.js +36 -37
- package/dist/clients/attach/session/profile-files.d.ts +8 -0
- package/dist/clients/attach/session/profile-files.js +157 -0
- package/dist/clients/attach/viewer.js +530 -528
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/node.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +6 -7
- package/dist/commands/pkg/plugin-manage.d.ts +1 -1
- package/dist/commands/pkg/plugin-manage.js +131 -19
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +6 -11
- package/dist/commands/profile/env.js +3 -3
- package/dist/commands/sys/config.js +17 -76
- package/dist/commands/sys/doctor.js +5 -91
- package/dist/commands/sys/setup-wizard.d.ts +9 -11
- package/dist/commands/sys/setup-wizard.js +47 -81
- package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
- package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
- package/dist/core/__tests__/command-plugins.test.js +36 -16
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -1
- package/dist/core/command-manifests/manifest.d.ts +24 -0
- package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
- package/dist/core/command-manifests/registry.d.ts +1 -2
- package/dist/core/command-manifests/registry.js +2 -2
- package/dist/core/command-manifests/schema.d.ts +11 -13
- package/dist/core/command-manifests/schema.js +53 -192
- package/dist/core/command-plugins/compose.d.ts +0 -6
- package/dist/core/command-plugins/compose.js +28 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +152 -259
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +48 -0
- package/dist/core/command-plugins/store.d.ts +16 -0
- package/dist/core/command-plugins/store.js +64 -0
- package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
- package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
- package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
- package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
- package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
- package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
- package/dist/core/command.d.ts +8 -9
- package/dist/core/command.js +6 -9
- package/dist/core/config.js +6 -10
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/io.d.ts +1 -1
- package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
- package/dist/core/keybindings/attach-control.d.ts +37 -0
- package/dist/core/keybindings/attach-control.js +38 -0
- package/dist/core/keybindings/catalog.d.ts +5 -4
- package/dist/core/keybindings/catalog.js +16 -8
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/keybindings/types.d.ts +1 -1
- package/dist/core/preview-registry.js +41 -74
- package/dist/core/runtime/bearings.d.ts +2 -5
- package/dist/core/runtime/bearings.js +2 -5
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +4 -3
- package/dist/core/runtime/situational-context.d.ts +1 -1
- package/dist/core/runtime/situational-context.js +1 -1
- package/dist/core/runtime/spawn.js +9 -9
- package/dist/core/runtime/tmux.js +29 -2
- package/dist/core/scope.d.ts +0 -5
- package/dist/core/scope.js +0 -10
- package/dist/core/user-settings.d.ts +193 -0
- package/dist/core/user-settings.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -2
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/dist/shared/generated-context.d.ts +34 -0
- package/dist/shared/generated-context.js +98 -0
- package/dist/types.d.ts +23 -16
- package/dist/types.js +3 -14
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
- package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/public-api.md +1 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
- package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
- package/dist/builtin-memory/internal/workflow-codification.md +0 -82
- package/dist/builtin-memory/product.md +0 -80
- package/dist/clients/attach/render/frozen-history.js +0 -100
- package/dist/commands/pkg/cli-inspect.d.ts +0 -17
- package/dist/commands/pkg/cli-inspect.js +0 -190
- package/dist/commands/pkg/cli-manage.d.ts +0 -3
- package/dist/commands/pkg/cli-manage.js +0 -206
- package/dist/commands/pkg/cli.d.ts +0 -1
- package/dist/commands/pkg/cli.js +0 -14
- package/dist/core/configured-clis/cache.d.ts +0 -16
- package/dist/core/configured-clis/cache.js +0 -57
- package/dist/core/configured-clis/compose.d.ts +0 -14
- package/dist/core/configured-clis/compose.js +0 -60
- package/dist/core/configured-clis/discovery.d.ts +0 -47
- package/dist/core/configured-clis/discovery.js +0 -173
- package/dist/core/configured-clis/manifest.d.ts +0 -24
- package/dist/core/configured-clis/registration.d.ts +0 -40
- package/dist/core/configured-clis/registration.js +0 -201
- package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// condensed-history.ts — lightweight rendering of a node's OLD cycles.
|
|
2
|
+
//
|
|
3
|
+
// A node that has yielded several times carries every prior cycle in its welcome
|
|
4
|
+
// snapshot (cycleAwareMessages, session-cycles.ts). Cycles older than the trailing
|
|
5
|
+
// `live_cycles` (config, default 2) are condensed to cycle dividers plus whichever
|
|
6
|
+
// messages `condensed_history` (config, default `none`) admits. Tool calls and
|
|
7
|
+
// results are excluded; across six measured sessions they accounted for 62.9% of
|
|
8
|
+
// rendered lines and 69% of transcript build time.
|
|
9
|
+
//
|
|
10
|
+
// Retained messages stay as components inside one nested container. Settled lines
|
|
11
|
+
// are cached at a stable pane width, while a width change rerenders the nested
|
|
12
|
+
// content so old prose wraps with the rest of the transcript.
|
|
13
|
+
//
|
|
14
|
+
// The full detail of a condensed cycle remains in the node's session file, and
|
|
15
|
+
// `/tree` navigates the real branches.
|
|
16
|
+
import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../../core/runtime/session-cycles.js';
|
|
17
|
+
/** Roles admitted by each `condensed_history` mode. `toolResult`,
|
|
18
|
+
* `bashExecution`, and every non-divider `custom` message (crtr output,
|
|
19
|
+
* injected context blocks) are never kept, nor are the tool-call components an
|
|
20
|
+
* assistant message would otherwise spawn — only its prose. The cycle dividers
|
|
21
|
+
* survive every mode, so `none` leaves the seams and nothing else. */
|
|
22
|
+
const ROLES_BY_MODE = {
|
|
23
|
+
none: new Set(),
|
|
24
|
+
user: new Set(['user']),
|
|
25
|
+
agent: new Set(['assistant', 'compactionSummary', 'branchSummary']),
|
|
26
|
+
both: new Set(['user', 'assistant', 'compactionSummary', 'branchSummary']),
|
|
27
|
+
};
|
|
28
|
+
export function isCycleDivider(message) {
|
|
29
|
+
return message.role === 'custom' && message.customType === CRTR_CYCLE_DIVIDER_CUSTOM_TYPE;
|
|
30
|
+
}
|
|
31
|
+
/** Does this message survive into the condensed region? */
|
|
32
|
+
export function keepWhenCondensed(message, mode) {
|
|
33
|
+
if (isCycleDivider(message))
|
|
34
|
+
return true;
|
|
35
|
+
return (ROLES_BY_MODE[mode] ?? ROLES_BY_MODE.none).has(message.role ?? '');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Index into `messages` at which the LIVE (fully rendered) region begins.
|
|
39
|
+
*
|
|
40
|
+
* The snapshot is `[cycle 0][divider][cycle 1][divider]…[current cycle]`, so a
|
|
41
|
+
* transcript with D dividers holds D+1 cycles. Keeping the trailing
|
|
42
|
+
* `liveCycles` of them means everything up to and including divider
|
|
43
|
+
* `D - liveCycles` is condensed. Returns 0 — condense nothing — when the node
|
|
44
|
+
* has not yielded often enough to have older cycles, so a fresh or
|
|
45
|
+
* single-yield node renders exactly as it always did.
|
|
46
|
+
*/
|
|
47
|
+
export function liveRegionStart(messages, liveCycles) {
|
|
48
|
+
const keep = Math.max(1, Math.floor(liveCycles));
|
|
49
|
+
const dividers = [];
|
|
50
|
+
for (let i = 0; i < messages.length; i++) {
|
|
51
|
+
if (isCycleDivider(messages[i]))
|
|
52
|
+
dividers.push(i);
|
|
53
|
+
}
|
|
54
|
+
const cycles = dividers.length + 1;
|
|
55
|
+
if (cycles <= keep)
|
|
56
|
+
return 0;
|
|
57
|
+
// The last divider that closes a condensed cycle; the live region opens just
|
|
58
|
+
// after it (the divider itself stays in the condensed region as the seam).
|
|
59
|
+
return dividers[cycles - keep - 1] + 1;
|
|
60
|
+
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
// render/context-message.ts — the viewer's foldable renderer for
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// situational context so both render consistently: collapsed → one dim line;
|
|
10
|
-
// expanded (Ctrl+O) → label + full wrapped body. It implements `setExpanded`,
|
|
11
|
-
// so the Ctrl+O tool-display cycle (ChatView.cycleToolDisplay →
|
|
12
|
-
// isExpandable) folds it alongside tool output.
|
|
1
|
+
// render/context-message.ts — the terminal viewer's foldable renderer for
|
|
2
|
+
// crouter-generated context. Session bearings, situational updates, fresh-revive
|
|
3
|
+
// kickoffs, context-size nudges, and node inbox digests retain their full model-visible body and
|
|
4
|
+
// runtime role, while this display-only component presents them consistently:
|
|
5
|
+
// collapsed → one dim line; expanded (Ctrl+O) → label + full wrapped body. It
|
|
6
|
+
// implements `setExpanded`, so the global tool-display cycle folds these blocks
|
|
7
|
+
// alongside tool output. Classification lives in shared/generated-context.ts;
|
|
8
|
+
// this component only renders the presentation it is handed.
|
|
13
9
|
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
14
|
-
/** Default label/hint for the `crtr-context` bearings block.
|
|
15
|
-
* extension's customType stamp. */
|
|
10
|
+
/** Default label/hint for the `crtr-context` bearings block. */
|
|
16
11
|
const LABEL = 'crtr context';
|
|
17
12
|
const COLLAPSED_HINT = ' orienting bearings — ctrl+o to expand';
|
|
18
13
|
/** Char-based wrap that never emits an over-width line (pi-tui hard-crashes on
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Box } from '@earendil-works/pi-tui';
|
|
2
|
+
import { FoldedToolCallController } from './tool-calls.js';
|
|
2
3
|
type Theme = {
|
|
3
4
|
fg: (name: string, text: string) => string;
|
|
4
5
|
bg: (name: string, text: string) => string;
|
|
@@ -44,5 +45,5 @@ export declare class CrtrOutputMessageComponent extends Box {
|
|
|
44
45
|
constructor(content: string, expanded: boolean, theme: Theme);
|
|
45
46
|
setExpanded(expanded: boolean): void;
|
|
46
47
|
}
|
|
47
|
-
export declare function createCrtrBashToolDefinition(args: unknown): unknown | undefined;
|
|
48
|
+
export declare function createCrtrBashToolDefinition(args: unknown, fold?: FoldedToolCallController): unknown | undefined;
|
|
48
49
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { truncateToVisualLines } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { Box, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
3
3
|
import { HELP_ICON, iconForPath, suppressOutputForPath, summarizePath, } from '../../../core/preview-registry.js';
|
|
4
|
-
import {
|
|
4
|
+
import { FoldedToolCallController } from './tool-calls.js';
|
|
5
5
|
// Hard cap on a collapsed crtr card's total rendered lines (rule 12,
|
|
6
6
|
// inline-ui-placement): help synthesis or a normal truncated result never
|
|
7
7
|
// shows more than this many lines total, blank spacer + status + body +
|
|
@@ -688,7 +688,7 @@ export class CrtrOutputMessageComponent extends Box {
|
|
|
688
688
|
this.addChild(this.body);
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
|
-
export function createCrtrBashToolDefinition(args) {
|
|
691
|
+
export function createCrtrBashToolDefinition(args, fold = new FoldedToolCallController()) {
|
|
692
692
|
const command = commandFromArgs(args);
|
|
693
693
|
if (!isCrtrBashCommand(command))
|
|
694
694
|
return undefined;
|
|
@@ -712,9 +712,9 @@ export function createCrtrBashToolDefinition(args) {
|
|
|
712
712
|
const prefix = isHelp ? `${icon} crtr help` : `${icon} crtr`;
|
|
713
713
|
const styledPrefix = theme.fg('accent', theme.bold(prefix));
|
|
714
714
|
const styledPath = path
|
|
715
|
-
? ` ${
|
|
715
|
+
? ` ${fold.folded ? styleFoldedCrtrTokens(pathTokens, theme) : theme.fg('toolTitle', theme.bold(path))}`
|
|
716
716
|
: '';
|
|
717
|
-
return new Text(`${styledPrefix}${styledPath}`, 0, 0);
|
|
717
|
+
return fold.capture(new Text(`${styledPrefix}${styledPath}`, 0, 0));
|
|
718
718
|
},
|
|
719
719
|
renderResult(result, options, theme, context) {
|
|
720
720
|
return new CrtrResultComponent(resultText(result), options, context, theme, command);
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { FoldedToolCallController } from './tool-calls.js';
|
|
2
|
+
type ToolResultLike = {
|
|
3
|
+
content?: Array<{
|
|
4
|
+
type?: unknown;
|
|
5
|
+
text?: unknown;
|
|
6
|
+
}>;
|
|
7
|
+
details?: {
|
|
8
|
+
diff?: unknown;
|
|
9
|
+
};
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type EditResultObserver = (result: ToolResultLike, isPartial: boolean) => void;
|
|
13
|
+
export type CrtrEditToolView = {
|
|
14
|
+
definition: unknown;
|
|
15
|
+
observeResult: EditResultObserver;
|
|
16
|
+
};
|
|
17
|
+
/** Crouter owns edit presentation while the imported tool owns execution. The
|
|
18
|
+
* card has two explicit phases: streamed argument diff, then executed result
|
|
19
|
+
* diff. No asynchronous preview can write after settlement. */
|
|
20
|
+
export declare function createCrtrEditToolDefinition(cwd: string, fold?: FoldedToolCallController): CrtrEditToolView;
|
|
21
|
+
export {};
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
// render/edit-diff.ts —
|
|
1
|
+
// render/edit-diff.ts — crouter-owned edit presentation for the attach viewer.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// the async preview computation (computeEditsDiff keyed off ctx.state) and
|
|
12
|
-
// the header/status-bg lifecycle — and only swap the rendered diff body.
|
|
13
|
-
// The raw diff string lives on the call component (`preview.diff`), so no
|
|
14
|
-
// ANSI re-parsing of pi's output is ever needed.
|
|
15
|
-
import { createEditToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
16
|
-
import { Container, Spacer, Text } from '@earendil-works/pi-tui';
|
|
17
|
-
import { TOOL_ICON } from '../../../core/preview-registry.js';
|
|
18
|
-
import { foldedFileCallLine, isFoldedContext, prefixToolText } from './tool-calls.js';
|
|
3
|
+
// The card renders replacement arguments synchronously while they stream, then
|
|
4
|
+
// switches to the executed tool result's full-file diff when the tool settles.
|
|
5
|
+
// Added and removed rows receive full-width tinted backgrounds, with the changed
|
|
6
|
+
// span inside a 1↔1 line modification emphasized in a brighter tint. The imported
|
|
7
|
+
// pi package remains responsible for edit execution, not viewer lifecycle.
|
|
8
|
+
import { generateDiffString } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import { Box, Container, Spacer, Text } from '@earendil-works/pi-tui';
|
|
10
|
+
import { FoldedToolCallController, foldedFileCallLine } from './tool-calls.js';
|
|
19
11
|
const FALLBACK_ADDED = { r: 181, g: 189, b: 104 }; // pi dark green
|
|
20
12
|
const FALLBACK_REMOVED = { r: 204, g: 102, b: 102 }; // pi dark red
|
|
21
13
|
const FALLBACK_CARD_BG = { r: 40, g: 50, b: 40 }; // pi dark toolSuccessBg
|
|
@@ -116,6 +108,30 @@ function countDiffLines(diff) {
|
|
|
116
108
|
}
|
|
117
109
|
return { added, removed };
|
|
118
110
|
}
|
|
111
|
+
/** Extract every complete replacement pair currently present in the streamed
|
|
112
|
+
* arguments. A pair becomes previewable as soon as both strings exist. */
|
|
113
|
+
function editPairs(args) {
|
|
114
|
+
const value = args;
|
|
115
|
+
if (Array.isArray(value?.edits)) {
|
|
116
|
+
return value.edits.flatMap((edit) => {
|
|
117
|
+
const pair = edit;
|
|
118
|
+
return typeof pair?.oldText === 'string' && typeof pair?.newText === 'string'
|
|
119
|
+
? [{ oldText: pair.oldText, newText: pair.newText }]
|
|
120
|
+
: [];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return typeof value?.oldText === 'string' && typeof value?.newText === 'string'
|
|
124
|
+
? [{ oldText: value.oldText, newText: value.newText }]
|
|
125
|
+
: [];
|
|
126
|
+
}
|
|
127
|
+
/** The pre-execution preview is deliberately replacement-local: it can update
|
|
128
|
+
* synchronously as arguments stream and never races filesystem mutation. */
|
|
129
|
+
function streamedArgumentDiff(args) {
|
|
130
|
+
const diffs = editPairs(args)
|
|
131
|
+
.map(({ oldText, newText }) => generateDiffString(oldText, newText).diff)
|
|
132
|
+
.filter((diff) => diff.trim().length > 0);
|
|
133
|
+
return diffs.length > 0 ? diffs.join('\n') : undefined;
|
|
134
|
+
}
|
|
119
135
|
function replaceTabs(text) {
|
|
120
136
|
return text.replace(/\t/g, ' ');
|
|
121
137
|
}
|
|
@@ -216,91 +232,74 @@ function buildDiffBody(diffText, theme, restoreBg) {
|
|
|
216
232
|
}
|
|
217
233
|
return container;
|
|
218
234
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// (mirrors pi's getEditHeaderBg), so tinted lines restore to that color.
|
|
236
|
-
box.addChild(buildDiffBody(preview.diff, theme, theme.getBgAnsi('toolSuccessBg')));
|
|
235
|
+
class EditCallComponent extends Box {
|
|
236
|
+
constructor() {
|
|
237
|
+
super(1, 1, (text) => text);
|
|
238
|
+
}
|
|
239
|
+
update(header, diff, state, theme) {
|
|
240
|
+
const background = state.settled
|
|
241
|
+
? state.isError ? 'toolErrorBg' : 'toolSuccessBg'
|
|
242
|
+
: 'toolPendingBg';
|
|
243
|
+
this.setBgFn((text) => theme.bg(background, text));
|
|
244
|
+
this.clear();
|
|
245
|
+
this.addChild(new Text(header, 0, 0));
|
|
246
|
+
if (!diff)
|
|
247
|
+
return;
|
|
248
|
+
this.addChild(new Spacer(1));
|
|
249
|
+
this.addChild(buildDiffBody(diff, theme, theme.getBgAnsi(background)));
|
|
250
|
+
}
|
|
237
251
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
function prefixEditHeaderIcon(box, theme) {
|
|
242
|
-
prefixToolText(box.children[0], `${theme.fg('accent', TOOL_ICON.edit)} `);
|
|
252
|
+
function editPath(args) {
|
|
253
|
+
const value = args;
|
|
254
|
+
return typeof value?.file_path === 'string' ? value.file_path : typeof value?.path === 'string' ? value.path : '';
|
|
243
255
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
function errorResultText(result) {
|
|
257
|
+
return (result.content ?? [])
|
|
258
|
+
.filter((part) => part.type === 'text' && typeof part.text === 'string')
|
|
259
|
+
.map((part) => part.text)
|
|
260
|
+
.join('\n');
|
|
261
|
+
}
|
|
262
|
+
/** Crouter owns edit presentation while the imported tool owns execution. The
|
|
263
|
+
* card has two explicit phases: streamed argument diff, then executed result
|
|
264
|
+
* diff. No asynchronous preview can write after settlement. */
|
|
265
|
+
export function createCrtrEditToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
266
|
+
const state = { settled: false, isError: false };
|
|
267
|
+
const call = new EditCallComponent();
|
|
268
|
+
const observeResult = (result, isPartial) => {
|
|
269
|
+
if (isPartial)
|
|
270
|
+
return;
|
|
271
|
+
state.settled = true;
|
|
272
|
+
state.isError = result.isError === true;
|
|
273
|
+
const resultDiff = result.details?.diff;
|
|
274
|
+
state.diff = !state.isError && typeof resultDiff === 'string' ? resultDiff : undefined;
|
|
275
|
+
};
|
|
276
|
+
const definition = {
|
|
252
277
|
name: 'edit',
|
|
253
278
|
label: 'edit',
|
|
254
|
-
description: 'Render edit
|
|
279
|
+
description: 'Render streamed edit arguments and the executed full-file diff in the attach viewer.',
|
|
255
280
|
parameters: {},
|
|
256
281
|
renderShell: 'self',
|
|
257
282
|
renderCall(args, theme, context) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const changes = context.isError
|
|
266
|
-
? undefined
|
|
267
|
-
: context.state.settledChanges ?? (preview && !('error' in preview) ? countDiffLines(preview.diff) : undefined);
|
|
268
|
-
return new Text(foldedFileCallLine('edit', raw, theme, context.cwd ?? cwd, changes), 0, 0);
|
|
269
|
-
}
|
|
270
|
-
const component = builtin.renderCall(args, theme, context);
|
|
271
|
-
const callBox = context.state.callComponent;
|
|
272
|
-
if (callBox) {
|
|
273
|
-
restyleEditCallBox(callBox, theme);
|
|
274
|
-
prefixEditHeaderIcon(callBox, theme);
|
|
275
|
-
}
|
|
276
|
-
return component;
|
|
283
|
+
const diff = state.settled ? state.diff : streamedArgumentDiff(args);
|
|
284
|
+
const changes = state.settled && !state.isError && state.diff ? countDiffLines(state.diff) : undefined;
|
|
285
|
+
const header = foldedFileCallLine('edit', editPath(args), theme, context.cwd ?? cwd, changes);
|
|
286
|
+
if (fold.folded)
|
|
287
|
+
return fold.capture(new Text(header, 0, 0));
|
|
288
|
+
call.update(header, diff, state, theme);
|
|
289
|
+
return fold.capture(call);
|
|
277
290
|
},
|
|
278
|
-
renderResult(result,
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
context.state.settledChanges = countDiffLines(resultDiff);
|
|
283
|
-
else
|
|
284
|
-
delete context.state.settledChanges;
|
|
285
|
-
}
|
|
286
|
-
const component = builtin.renderResult(result, options, theme, context);
|
|
287
|
-
const callBox = context.state.callComponent;
|
|
288
|
-
if (callBox) {
|
|
289
|
-
// builtin renderResult re-ran buildEditCallComponent (pi styling) —
|
|
290
|
-
// restyle the refreshed body.
|
|
291
|
-
restyleEditCallBox(callBox, theme);
|
|
292
|
-
prefixEditHeaderIcon(callBox, theme);
|
|
291
|
+
renderResult(result, _options, theme, context) {
|
|
292
|
+
const component = context.lastComponent instanceof Container ? context.lastComponent : new Container();
|
|
293
|
+
component.clear();
|
|
294
|
+
if (!context.isError)
|
|
293
295
|
return component;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const container = new Container();
|
|
299
|
-
container.addChild(new Spacer(1));
|
|
300
|
-
container.addChild(buildDiffBody(resultDiff, theme, '\x1b[49m'));
|
|
301
|
-
return container;
|
|
296
|
+
const output = errorResultText(result);
|
|
297
|
+
if (output) {
|
|
298
|
+
component.addChild(new Spacer(1));
|
|
299
|
+
component.addChild(new Text(theme.fg('error', output), 1, 0));
|
|
302
300
|
}
|
|
303
301
|
return component;
|
|
304
302
|
},
|
|
305
303
|
};
|
|
304
|
+
return { definition, observeResult };
|
|
306
305
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { type Component } from '@earendil-works/pi-tui';
|
|
1
2
|
type Theme = {
|
|
2
3
|
fg: (name: string, text: string) => string;
|
|
3
4
|
bold: (text: string) => string;
|
|
4
5
|
};
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
/** Per-tool fold state owned by the attach viewer. Tool definitions capture the
|
|
7
|
+
* call component they render while folded, so the viewer never reaches into
|
|
8
|
+
* `ToolExecutionComponent`'s private renderer fields to recover it. */
|
|
9
|
+
export declare class FoldedToolCallController {
|
|
10
|
+
folded: boolean;
|
|
11
|
+
private component;
|
|
12
|
+
setFolded(folded: boolean): void;
|
|
13
|
+
capture<T extends Component>(component: T): T;
|
|
14
|
+
callComponent(): Component | undefined;
|
|
15
|
+
}
|
|
14
16
|
export type FileLineChanges = {
|
|
15
17
|
added: number;
|
|
16
18
|
removed: number;
|
|
@@ -18,20 +20,14 @@ export type FileLineChanges = {
|
|
|
18
20
|
/** The one-line folded form of a file tool's call: `<icon> <label> <path>`,
|
|
19
21
|
* optionally followed by its green/red changed-line counts. */
|
|
20
22
|
export declare function foldedFileCallLine(tool: 'read' | 'write' | 'edit', rawPath: string, theme: Theme, cwd: string, changes?: FileLineChanges): string;
|
|
21
|
-
/** Prefix a pi-built `Text` in place (its `text` field is TS-private but a plain
|
|
22
|
-
* runtime property). Used where the header line is baked into the same string as
|
|
23
|
-
* a body we want to keep verbatim — write's syntax-highlighted content preview,
|
|
24
|
-
* edit's header inside its own card. Silently no-ops if pi's Text ever stops
|
|
25
|
-
* looking like this, so the worst case is a missing glyph, never a crash. */
|
|
26
|
-
export declare function prefixToolText(component: unknown, prefix: string): void;
|
|
27
23
|
/** Attach-viewer `read`: an icon + the path (plus any `:offset-limit` range).
|
|
28
24
|
* The call is a single line by construction, so a folded read shows exactly
|
|
29
25
|
* what it read and nothing else. */
|
|
30
|
-
export declare function createCrtrReadToolDefinition(cwd: string): unknown;
|
|
26
|
+
export declare function createCrtrReadToolDefinition(cwd: string, fold?: FoldedToolCallController): unknown;
|
|
31
27
|
/** Attach-viewer `write`: pi's builtin call renderer (it owns the
|
|
32
28
|
* syntax-highlighted content preview and its expand hint) with the icon
|
|
33
29
|
* prefixed onto its header line. Folded, only that header survives. */
|
|
34
|
-
export declare function createCrtrWriteToolDefinition(cwd: string): unknown;
|
|
30
|
+
export declare function createCrtrWriteToolDefinition(cwd: string, fold?: FoldedToolCallController): unknown;
|
|
35
31
|
/** Attach-viewer `bash` for anything that is not a `crtr` invocation. */
|
|
36
|
-
export declare function createCrtrPlainBashToolDefinition(cwd: string): unknown;
|
|
32
|
+
export declare function createCrtrPlainBashToolDefinition(cwd: string, fold?: FoldedToolCallController): unknown;
|
|
37
33
|
export {};
|
|
@@ -19,14 +19,22 @@ import { TOOL_ICON } from '../../../core/preview-registry.js';
|
|
|
19
19
|
function str(value) {
|
|
20
20
|
return typeof value === 'string' ? value : '';
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
/** Per-tool fold state owned by the attach viewer. Tool definitions capture the
|
|
23
|
+
* call component they render while folded, so the viewer never reaches into
|
|
24
|
+
* `ToolExecutionComponent`'s private renderer fields to recover it. */
|
|
25
|
+
export class FoldedToolCallController {
|
|
26
|
+
folded = false;
|
|
27
|
+
component;
|
|
28
|
+
setFolded(folded) {
|
|
29
|
+
this.folded = folded;
|
|
30
|
+
}
|
|
31
|
+
capture(component) {
|
|
32
|
+
this.component = component;
|
|
33
|
+
return component;
|
|
34
|
+
}
|
|
35
|
+
callComponent() {
|
|
36
|
+
return this.component;
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
/** Count file lines the way a diff does: a trailing newline terminates the last
|
|
32
40
|
* line instead of creating an extra empty one. */
|
|
@@ -36,7 +44,7 @@ function countFileLines(content) {
|
|
|
36
44
|
const newlines = content.match(/\n/g)?.length ?? 0;
|
|
37
45
|
return newlines + (content.endsWith('\n') ? 0 : 1);
|
|
38
46
|
}
|
|
39
|
-
/** The green/red line
|
|
47
|
+
/** The green/red changed-line suffix shown after a folded edit path. */
|
|
40
48
|
function lineChangesDisplay(changes, theme) {
|
|
41
49
|
if (!changes)
|
|
42
50
|
return '';
|
|
@@ -47,6 +55,12 @@ function lineChangesDisplay(changes, theme) {
|
|
|
47
55
|
counts.push(theme.fg('toolDiffRemoved', `-${changes.removed}`));
|
|
48
56
|
return counts.length > 0 ? ` ${counts.join(' ')}` : '';
|
|
49
57
|
}
|
|
58
|
+
/** A write has no before/after diff, so report its output size neutrally. */
|
|
59
|
+
function lineCountDisplay(lines, theme) {
|
|
60
|
+
if (lines === undefined)
|
|
61
|
+
return '';
|
|
62
|
+
return ` ${theme.fg('muted', `${lines} ${lines === 1 ? 'line' : 'lines'}`)}`;
|
|
63
|
+
}
|
|
50
64
|
/** The one-line folded form of a file tool's call: `<icon> <label> <path>`,
|
|
51
65
|
* optionally followed by its green/red changed-line counts. */
|
|
52
66
|
export function foldedFileCallLine(tool, rawPath, theme, cwd, changes) {
|
|
@@ -72,18 +86,29 @@ function pathDisplay(rawPath, theme, cwd) {
|
|
|
72
86
|
function header(icon, label, theme) {
|
|
73
87
|
return `${theme.fg('accent', icon)} ${theme.fg('toolTitle', theme.bold(label))}`;
|
|
74
88
|
}
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
/** Add an icon to a tool call without mutating the imported renderer's private
|
|
90
|
+
* text storage. The imported component remains responsible for its own body. */
|
|
91
|
+
class IconPrefixedToolCall {
|
|
92
|
+
child;
|
|
93
|
+
prefix;
|
|
94
|
+
constructor(child, prefix) {
|
|
95
|
+
this.child = child;
|
|
96
|
+
this.prefix = prefix;
|
|
97
|
+
}
|
|
98
|
+
update(child, prefix) {
|
|
99
|
+
this.child = child;
|
|
100
|
+
this.prefix = prefix;
|
|
101
|
+
}
|
|
102
|
+
invalidate() {
|
|
103
|
+
this.child.invalidate();
|
|
104
|
+
}
|
|
105
|
+
render(width) {
|
|
106
|
+
const prefixWidth = visibleWidth(this.prefix);
|
|
107
|
+
const lines = this.child.render(Math.max(1, width - prefixWidth));
|
|
108
|
+
if (lines.length === 0)
|
|
109
|
+
return [truncateToWidth(this.prefix.trimEnd(), width, '')];
|
|
110
|
+
return lines.map((line, index) => index === 0 ? truncateToWidth(`${this.prefix}${line}`, width, '') : line);
|
|
111
|
+
}
|
|
87
112
|
}
|
|
88
113
|
// ---------------------------------------------------------------------------
|
|
89
114
|
// read
|
|
@@ -91,7 +116,7 @@ export function prefixToolText(component, prefix) {
|
|
|
91
116
|
/** Attach-viewer `read`: an icon + the path (plus any `:offset-limit` range).
|
|
92
117
|
* The call is a single line by construction, so a folded read shows exactly
|
|
93
118
|
* what it read and nothing else. */
|
|
94
|
-
export function createCrtrReadToolDefinition(cwd) {
|
|
119
|
+
export function createCrtrReadToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
95
120
|
const builtin = createReadToolDefinition(cwd);
|
|
96
121
|
return {
|
|
97
122
|
name: 'read',
|
|
@@ -110,7 +135,7 @@ export function createCrtrReadToolDefinition(cwd) {
|
|
|
110
135
|
const line = `${header(TOOL_ICON.read, 'read', theme)} ${pathDisplay(raw, theme, context.cwd ?? cwd)}${range}`;
|
|
111
136
|
const component = context.lastComponent ?? new Text('', 0, 0);
|
|
112
137
|
component.setText(line);
|
|
113
|
-
return component;
|
|
138
|
+
return fold.capture(component);
|
|
114
139
|
},
|
|
115
140
|
renderResult(result, options, theme, context) {
|
|
116
141
|
return builtin.renderResult(result, options, theme, context);
|
|
@@ -123,30 +148,30 @@ export function createCrtrReadToolDefinition(cwd) {
|
|
|
123
148
|
/** Attach-viewer `write`: pi's builtin call renderer (it owns the
|
|
124
149
|
* syntax-highlighted content preview and its expand hint) with the icon
|
|
125
150
|
* prefixed onto its header line. Folded, only that header survives. */
|
|
126
|
-
export function createCrtrWriteToolDefinition(cwd) {
|
|
151
|
+
export function createCrtrWriteToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
127
152
|
const builtin = createWriteToolDefinition(cwd);
|
|
153
|
+
let builtinCall;
|
|
154
|
+
let decoratedCall;
|
|
128
155
|
return {
|
|
129
156
|
name: 'write',
|
|
130
157
|
label: 'write',
|
|
131
158
|
description: 'Render write calls with a crtr icon header in the attach viewer.',
|
|
132
159
|
parameters: {},
|
|
133
160
|
renderCall(args, theme, context) {
|
|
134
|
-
if (
|
|
161
|
+
if (fold.folded) {
|
|
135
162
|
const a = args;
|
|
136
|
-
const
|
|
137
|
-
const line = foldedFileCallLine('write', str(a?.file_path ?? a?.path), theme, context.cwd ?? cwd
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const last = context.lastComponent;
|
|
141
|
-
if (last && typeof last.setText === 'function') {
|
|
142
|
-
last.setText(line);
|
|
143
|
-
return last;
|
|
144
|
-
}
|
|
145
|
-
return new Text(line, 0, 0);
|
|
163
|
+
const lines = context.isError ? undefined : countFileLines(str(a?.content));
|
|
164
|
+
const line = foldedFileCallLine('write', str(a?.file_path ?? a?.path), theme, context.cwd ?? cwd) +
|
|
165
|
+
lineCountDisplay(lines, theme);
|
|
166
|
+
return fold.capture(new Text(line, 0, 0));
|
|
146
167
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
builtinCall = builtin.renderCall(args, theme, { ...context, lastComponent: builtinCall });
|
|
169
|
+
const prefix = `${theme.fg('accent', TOOL_ICON.write)} `;
|
|
170
|
+
if (decoratedCall)
|
|
171
|
+
decoratedCall.update(builtinCall, prefix);
|
|
172
|
+
else
|
|
173
|
+
decoratedCall = new IconPrefixedToolCall(builtinCall, prefix);
|
|
174
|
+
return fold.capture(decoratedCall);
|
|
150
175
|
},
|
|
151
176
|
renderResult(result, options, theme, context) {
|
|
152
177
|
return builtin.renderResult(result, options, theme, context);
|
|
@@ -242,7 +267,7 @@ class BashCallComponent {
|
|
|
242
267
|
}
|
|
243
268
|
}
|
|
244
269
|
/** Attach-viewer `bash` for anything that is not a `crtr` invocation. */
|
|
245
|
-
export function createCrtrPlainBashToolDefinition(cwd) {
|
|
270
|
+
export function createCrtrPlainBashToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
246
271
|
const builtin = createBashToolDefinition(cwd);
|
|
247
272
|
return {
|
|
248
273
|
name: 'bash',
|
|
@@ -262,8 +287,8 @@ export function createCrtrPlainBashToolDefinition(cwd) {
|
|
|
262
287
|
const component = context.lastComponent instanceof BashCallComponent ? context.lastComponent : new BashCallComponent();
|
|
263
288
|
component.update(theme, str(a?.command),
|
|
264
289
|
// Folded wins over ctrl+o expansion: a folded bash is always its one-line form.
|
|
265
|
-
Boolean(context.expanded) && !
|
|
266
|
-
return component;
|
|
290
|
+
Boolean(context.expanded) && !fold.folded, typeof a?.timeout === 'number' ? a.timeout : undefined);
|
|
291
|
+
return fold.capture(component);
|
|
267
292
|
},
|
|
268
293
|
renderResult(result, options, theme, context) {
|
|
269
294
|
return builtin.renderResult(result, options, theme, context);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type BindingId, type BindingResolution } from '../../../core/keybindings/index.js';
|
|
2
|
-
export type AttachBindingId
|
|
1
|
+
import { type AttachBindingId, type BindingId, type BindingResolution } from '../../../core/keybindings/index.js';
|
|
2
|
+
export type { AttachBindingId } from '../../../core/keybindings/index.js';
|
|
3
3
|
export type AttachBindings = BindingResolution<BindingId>;
|
|
4
|
+
export type AttachActions = Record<AttachBindingId, () => void>;
|
|
4
5
|
export { inboxOpenHint } from '../../../core/keybindings/index.js';
|
|
5
6
|
export declare function matchesAttachBinding(bindings: AttachBindings, id: AttachBindingId, data: string): boolean;
|
|
6
|
-
export declare function
|
|
7
|
+
export declare function dispatchAttachAction(id: AttachBindingId, actions: AttachActions): void;
|
|
8
|
+
export declare function dispatchAttachBinding(bindings: AttachBindings, data: string, actions: AttachActions): boolean;
|
|
7
9
|
export declare function detachHint(bindings: AttachBindings): string | undefined;
|
|
8
10
|
/** Re-resolve the user's binding catalog and republish it to every consumer:
|
|
9
11
|
* the graph overlay's own snapshot and (in tmux) the crouter-owned tmux
|