@north-light/crouter 0.3.164 → 0.3.166
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 +4 -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/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 +62 -22
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +40 -54
- 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.d.ts +1 -1
- package/dist/clients/attach/render/context-message.js +18 -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 +528 -529
- package/dist/commands/node-context.js +3 -2
- 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 +75 -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 +28 -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 +32 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +171 -261
- 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/broker.js +3 -2
- 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/lifecycle.js +2 -2
- 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/prompts/review.js +4 -2
- 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-BgLGlZ3D.css +2 -0
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-CmoNqcCv.js} +19 -19
- 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 +1 -1
- package/runtime.lock.json +2 -2
- 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
|
@@ -26,21 +26,17 @@ import { attachMarkdownTheme } from '../config.js';
|
|
|
26
26
|
import { ContextMessageComponent } from './context-message.js';
|
|
27
27
|
import { CRTR_OUTPUT_CUSTOM_TYPE, CrtrOutputMessageComponent, createCrtrBashToolDefinition, } from './crtr-output.js';
|
|
28
28
|
import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../../core/runtime/session-cycles.js';
|
|
29
|
-
import {
|
|
29
|
+
import { generatedContextPresentation } from '../../../shared/generated-context.js';
|
|
30
30
|
import { transformMessageDiagrams } from './diagram.js';
|
|
31
31
|
import { styleAttachMarkdownHeadings, styleAttachMessageMarkdown, styleAttachSummaryMarkdown, } from './markdown-headings.js';
|
|
32
32
|
import { createCrtrEditToolDefinition } from './edit-diff.js';
|
|
33
33
|
import { FoldedToolCallController, createCrtrPlainBashToolDefinition, createCrtrReadToolDefinition, createCrtrWriteToolDefinition, } from './tool-calls.js';
|
|
34
34
|
import { ringCompletionBell } from '../chrome/completion-bell.js';
|
|
35
35
|
import { extractFilePaths } from '../visible-paths.js';
|
|
36
|
-
import {
|
|
36
|
+
import { keepWhenCondensed, liveRegionStart } from './condensed-history.js';
|
|
37
37
|
import { ViewerAssistantMessageComponent } from './assistant-message.js';
|
|
38
38
|
import { readConfig, updateRawConfigAtomically } from '../../../core/config.js';
|
|
39
39
|
import { DEFAULT_CONDENSED_HISTORY, DEFAULT_LIVE_CYCLES } from '../../../types.js';
|
|
40
|
-
/** customType stamped on the injected `<crtr-context>` bearings block (mirrors
|
|
41
|
-
* CONTEXT_INTRO_CUSTOM_TYPE in canvas-context-intro.ts — kept as a local literal
|
|
42
|
-
* so the viewer never imports the in-process extension). */
|
|
43
|
-
const CONTEXT_INTRO_CUSTOM_TYPE = 'crtr-context';
|
|
44
40
|
/** Theme-derived chrome colors (spinner / dim / error) are supplied by the
|
|
45
41
|
* attach command via `ChatViewOptions.palette` (built from the live theme in
|
|
46
42
|
* config.ts). pi does not re-export the raw `theme` instance, so when no
|
|
@@ -257,8 +253,8 @@ export class ChatView {
|
|
|
257
253
|
/** Activity/status area below history. Holds working/compaction/retry loaders. */
|
|
258
254
|
statusContainer = new Container();
|
|
259
255
|
/** Where `append()` puts a component. Normally `historyContainer`; retargeted to
|
|
260
|
-
* a
|
|
261
|
-
* message-rendering path serves both. */
|
|
256
|
+
* a nested container while condensed history is being built, so the same
|
|
257
|
+
* message-rendering path serves both regions. */
|
|
262
258
|
appendTarget = this.historyContainer;
|
|
263
259
|
/** Trailing cycles kept at full fidelity (config `live_cycles`). */
|
|
264
260
|
liveCycles;
|
|
@@ -372,18 +368,18 @@ export class ChatView {
|
|
|
372
368
|
const renderedPendingTools = new Map();
|
|
373
369
|
const messages = snapshot.messages;
|
|
374
370
|
const lastIndex = messages.length - 1;
|
|
375
|
-
// Cycles older than the trailing `live_cycles` are condensed
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
371
|
+
// Cycles older than the trailing `live_cycles` are condensed into one nested
|
|
372
|
+
// container before the live region is built. It retains only configured prose
|
|
373
|
+
// and cycle seams, avoiding old tool components while still reflowing on pane
|
|
374
|
+
// resize. `liveStart` is 0 until the node has yielded past the configured
|
|
375
|
+
// window, so a fresh node renders at full fidelity.
|
|
380
376
|
const liveStart = liveRegionStart(messages, this.liveCycles);
|
|
381
377
|
if (liveStart > 0) {
|
|
382
|
-
const
|
|
378
|
+
const condensed = await this.buildCondensedBlock(messages.slice(0, liveStart), generation);
|
|
383
379
|
if (generation !== this.snapshotGeneration)
|
|
384
380
|
return;
|
|
385
|
-
if (
|
|
386
|
-
this.append(
|
|
381
|
+
if (condensed)
|
|
382
|
+
this.append(condensed);
|
|
387
383
|
}
|
|
388
384
|
for (let i = liveStart; i < messages.length; i++) {
|
|
389
385
|
// Yield to the event loop every SNAPSHOT_YIELD_EVERY messages so queued
|
|
@@ -483,20 +479,20 @@ export class ChatView {
|
|
|
483
479
|
this.replayDeferredFrames();
|
|
484
480
|
}
|
|
485
481
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
482
|
+
* Build the condensed form of the already-finished cycles as a nested component
|
|
483
|
+
* container, or return undefined when nothing survives condensation.
|
|
488
484
|
*
|
|
489
|
-
* Reuses `addMessageToChat`
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* this generation.
|
|
485
|
+
* Reuses `addMessageToChat` by retargeting `append()` at the nested container, so
|
|
486
|
+
* condensed history uses the same renderer as live history with tool traffic
|
|
487
|
+
* filtered out. The outer history cache keeps stable-width frames cheap, while
|
|
488
|
+
* preserving the components lets pane-width changes reflow their content. The
|
|
489
|
+
* build yields on the live cadence and abandons itself when a newer welcome
|
|
490
|
+
* supersedes this generation.
|
|
495
491
|
*/
|
|
496
|
-
async
|
|
497
|
-
const
|
|
492
|
+
async buildCondensedBlock(messages, generation) {
|
|
493
|
+
const condensed = new Container();
|
|
498
494
|
const previousTarget = this.appendTarget;
|
|
499
|
-
this.appendTarget =
|
|
495
|
+
this.appendTarget = condensed;
|
|
500
496
|
try {
|
|
501
497
|
let built = 0;
|
|
502
498
|
for (const message of messages) {
|
|
@@ -518,10 +514,7 @@ export class ChatView {
|
|
|
518
514
|
finally {
|
|
519
515
|
this.appendTarget = previousTarget;
|
|
520
516
|
}
|
|
521
|
-
|
|
522
|
-
const lines = scratch.render(width);
|
|
523
|
-
scratch.clear();
|
|
524
|
-
return lines.length > 0 ? new FrozenHistoryComponent(lines, width) : undefined;
|
|
517
|
+
return condensed.children.length > 0 ? condensed : undefined;
|
|
525
518
|
}
|
|
526
519
|
/** Defer a live broker frame when a snapshot build is in flight. Returns true
|
|
527
520
|
* (and enqueues the replay closure) while building so the caller returns
|
|
@@ -564,8 +557,8 @@ export class ChatView {
|
|
|
564
557
|
// -------------------------------------------------------------------------
|
|
565
558
|
/** Ctrl+O / app.tools.expand — flip global tool-output expansion and apply it to
|
|
566
559
|
* every rendered component that supports it. Returns the new expanded state so
|
|
567
|
-
* the caller can surface a notice. Condensed cycles hold no tool components
|
|
568
|
-
*
|
|
560
|
+
* the caller can surface a notice. Condensed cycles hold no tool components, so
|
|
561
|
+
* they are unaffected by design. */
|
|
569
562
|
toggleToolsExpanded() {
|
|
570
563
|
this.toolOutputExpanded = !this.toolOutputExpanded;
|
|
571
564
|
this.applyToolDisplay();
|
|
@@ -870,6 +863,18 @@ export class ChatView {
|
|
|
870
863
|
// Rebuild path (ports addMessageToChat, interactive-mode.js:2483).
|
|
871
864
|
// -------------------------------------------------------------------------
|
|
872
865
|
addMessageToChat(message) {
|
|
866
|
+
// Crouter-generated context retains its runtime role (user vs custom) so
|
|
867
|
+
// delivery to the model is unchanged, but every attached view presents it
|
|
868
|
+
// through one compact disclosure contract. The shared classifier uses the
|
|
869
|
+
// producers' exact custom types/sentinels, so ordinary user prompts cannot
|
|
870
|
+
// accidentally acquire this treatment and saved transcripts gain it too.
|
|
871
|
+
const generated = generatedContextPresentation(message);
|
|
872
|
+
if (generated !== null &&
|
|
873
|
+
(message.role !== 'custom' || message.display === true)) {
|
|
874
|
+
this.pushTranscriptText(generated.body);
|
|
875
|
+
this.append(new ContextMessageComponent(generated.body, this.toolOutputExpanded, this.dimStyle, (s) => this.spinnerStyle(this.labelStyle(s)), generated.label, ` ${generated.summary} — ctrl+o to expand`));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
873
878
|
switch (message.role) {
|
|
874
879
|
case 'bashExecution': {
|
|
875
880
|
this.pushTranscriptText(message.output ?? '');
|
|
@@ -888,25 +893,6 @@ export class ChatView {
|
|
|
888
893
|
case 'custom': {
|
|
889
894
|
if (message.display) {
|
|
890
895
|
this.pushTranscriptText(this.userMessageText(message));
|
|
891
|
-
// The <crtr-context> bearings block folds to a one-liner under Ctrl+O
|
|
892
|
-
// (the broker runs its renderer extension, so reimplement it natively).
|
|
893
|
-
if (message.customType === CONTEXT_INTRO_CUSTOM_TYPE) {
|
|
894
|
-
this.append(new ContextMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.dimStyle,
|
|
895
|
-
// Bold + active accent, so the [crtr context] label reads as a
|
|
896
|
-
// distinct color against the dim rest of the line.
|
|
897
|
-
(s) => this.spinnerStyle(this.labelStyle(s))));
|
|
898
|
-
break;
|
|
899
|
-
}
|
|
900
|
-
// The hidden ambient `<situational-context>` update (canvas-inbox-watcher's
|
|
901
|
-
// ~non-birth wake path). Rendered collapsed exactly like `crtr-context`
|
|
902
|
-
// (same component, distinct label) so an existing-node ambient update is
|
|
903
|
-
// never more prominent in the transcript than the bearings block it rides
|
|
904
|
-
// alongside on fresh birth — the LLM sees the full content either way; only
|
|
905
|
-
// the viewer's display treatment differs.
|
|
906
|
-
if (message.customType === SITUATIONAL_CONTEXT_CUSTOM_TYPE) {
|
|
907
|
-
this.append(new ContextMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.dimStyle, (s) => this.spinnerStyle(this.labelStyle(s)), 'situational context', ' ambient context update — ctrl+o to expand'));
|
|
908
|
-
break;
|
|
909
|
-
}
|
|
910
896
|
if (message.customType === CRTR_OUTPUT_CUSTOM_TYPE) {
|
|
911
897
|
this.append(new CrtrOutputMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.crtrOutputTheme()));
|
|
912
898
|
break;
|
|
@@ -1047,7 +1033,7 @@ export class ChatView {
|
|
|
1047
1033
|
return component;
|
|
1048
1034
|
}
|
|
1049
1035
|
/** Append conversation content above the independent live status area — or into
|
|
1050
|
-
* the
|
|
1036
|
+
* the nested container while condensed history is under construction. */
|
|
1051
1037
|
append(child) {
|
|
1052
1038
|
this.appendTarget.addChild(child);
|
|
1053
1039
|
}
|
|
@@ -1058,7 +1044,7 @@ export class ChatView {
|
|
|
1058
1044
|
return Math.max(40, process.stdout.columns ?? 80);
|
|
1059
1045
|
}
|
|
1060
1046
|
/** Number of rendered conversation components in the container currently being
|
|
1061
|
-
* appended to (history
|
|
1047
|
+
* appended to (live history or the nested condensed region). */
|
|
1062
1048
|
chatChildCount() {
|
|
1063
1049
|
return this.appendTarget.children.length;
|
|
1064
1050
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type Component } from '@earendil-works/pi-tui';
|
|
2
1
|
import type { CondensedHistoryMode } from '../../../types.js';
|
|
3
2
|
/** A message from the broker snapshot, structurally. */
|
|
4
3
|
type Message = {
|
|
@@ -6,7 +5,7 @@ type Message = {
|
|
|
6
5
|
customType?: string;
|
|
7
6
|
};
|
|
8
7
|
export declare function isCycleDivider(message: Message): boolean;
|
|
9
|
-
/** Does this message survive
|
|
8
|
+
/** Does this message survive into the condensed region? */
|
|
10
9
|
export declare function keepWhenCondensed(message: Message, mode: CondensedHistoryMode): boolean;
|
|
11
10
|
/**
|
|
12
11
|
* Index into `messages` at which the LIVE (fully rendered) region begins.
|
|
@@ -19,20 +18,4 @@ export declare function keepWhenCondensed(message: Message, mode: CondensedHisto
|
|
|
19
18
|
* single-yield node renders exactly as it always did.
|
|
20
19
|
*/
|
|
21
20
|
export declare function liveRegionStart(messages: readonly Message[], liveCycles: number): number;
|
|
22
|
-
/**
|
|
23
|
-
* A settled block of pre-rendered transcript lines. Renders in O(1) at the width
|
|
24
|
-
* it was frozen at, and clips (never reflows) when the pane is narrower. Ctrl+O /
|
|
25
|
-
* Ctrl+T do not reach it: it holds no components to expand, only text.
|
|
26
|
-
*/
|
|
27
|
-
export declare class FrozenHistoryComponent implements Component {
|
|
28
|
-
private readonly lines;
|
|
29
|
-
private readonly frozenWidth;
|
|
30
|
-
/** Last clip result, so a stable narrow pane clips once rather than per frame. */
|
|
31
|
-
private clipped;
|
|
32
|
-
constructor(lines: readonly string[], frozenWidth: number);
|
|
33
|
-
get lineCount(): number;
|
|
34
|
-
/** Nothing to recompute — the block is text, already laid out. */
|
|
35
|
-
invalidate(): void;
|
|
36
|
-
render(width: number): string[];
|
|
37
|
-
}
|
|
38
21
|
export {};
|
|
@@ -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,11 +1,11 @@
|
|
|
1
1
|
import { type Component } from '@earendil-works/pi-tui';
|
|
2
2
|
export declare class ContextMessageComponent implements Component {
|
|
3
|
-
private readonly body;
|
|
4
3
|
private readonly dim;
|
|
5
4
|
private readonly label;
|
|
6
5
|
private readonly labelText;
|
|
7
6
|
private readonly collapsedHint;
|
|
8
7
|
private expanded;
|
|
8
|
+
private readonly expandedBody;
|
|
9
9
|
constructor(body: string, expanded: boolean, dim: (s: string) => string, label: (s: string) => string, labelText?: string, collapsedHint?: string);
|
|
10
10
|
/** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
|
|
11
11
|
setExpanded(expanded: boolean): void;
|
|
@@ -1,54 +1,33 @@
|
|
|
1
|
-
// render/context-message.ts — the viewer's foldable renderer for
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
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) → the ordinary user-message
|
|
6
|
+
// surface, regardless of the message's runtime role. It implements
|
|
7
|
+
// `setExpanded`, so the global tool-display cycle folds these blocks alongside
|
|
8
|
+
// tool output. Classification lives in shared/generated-context.ts; this
|
|
9
|
+
// component only renders the presentation it is handed.
|
|
10
|
+
import { UserMessageComponent } from '@earendil-works/pi-coding-agent';
|
|
13
11
|
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import { attachMarkdownTheme } from '../config.js';
|
|
13
|
+
import { styleAttachMarkdownHeadings } from './markdown-headings.js';
|
|
14
|
+
/** Default label/hint for the `crtr-context` bearings block. */
|
|
16
15
|
const LABEL = 'crtr context';
|
|
17
16
|
const COLLAPSED_HINT = ' orienting bearings — ctrl+o to expand';
|
|
18
|
-
/** Char-based wrap that never emits an over-width line (pi-tui hard-crashes on
|
|
19
|
-
* one). Visible-width aware so wide glyphs in the bearings tree count right. */
|
|
20
|
-
function wrapLine(line, width) {
|
|
21
|
-
if (line === '')
|
|
22
|
-
return [''];
|
|
23
|
-
const out = [];
|
|
24
|
-
let cur = '';
|
|
25
|
-
for (const ch of line) {
|
|
26
|
-
if (cur !== '' && visibleWidth(cur + ch) > width) {
|
|
27
|
-
out.push(cur);
|
|
28
|
-
cur = ch;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
cur += ch;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (cur !== '')
|
|
35
|
-
out.push(cur);
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
17
|
export class ContextMessageComponent {
|
|
39
|
-
body;
|
|
40
18
|
dim;
|
|
41
19
|
label;
|
|
42
20
|
labelText;
|
|
43
21
|
collapsedHint;
|
|
44
22
|
expanded;
|
|
23
|
+
expandedBody;
|
|
45
24
|
constructor(body, expanded, dim, label, labelText = LABEL, collapsedHint = COLLAPSED_HINT) {
|
|
46
|
-
this.body = body;
|
|
47
25
|
this.dim = dim;
|
|
48
26
|
this.label = label;
|
|
49
27
|
this.labelText = labelText;
|
|
50
28
|
this.collapsedHint = collapsedHint;
|
|
51
29
|
this.expanded = expanded;
|
|
30
|
+
this.expandedBody = new UserMessageComponent(styleAttachMarkdownHeadings(body), attachMarkdownTheme());
|
|
52
31
|
}
|
|
53
32
|
/** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
|
|
54
33
|
setExpanded(expanded) {
|
|
@@ -68,11 +47,9 @@ export class ContextMessageComponent {
|
|
|
68
47
|
return [this.label(truncateToWidth(labelTag, w, '…'))];
|
|
69
48
|
return [this.label(labelTag) + this.dim(truncateToWidth(rest, w - labelW, '…'))];
|
|
70
49
|
}
|
|
71
|
-
const lines =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
lines.push(seg);
|
|
75
|
-
}
|
|
50
|
+
const lines = this.expandedBody.render(w);
|
|
51
|
+
if (lines.at(-1) !== '')
|
|
52
|
+
lines.push('');
|
|
76
53
|
return lines;
|
|
77
54
|
}
|
|
78
55
|
invalidate() {
|
|
@@ -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
|
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
// tmux installer binds from (`core/keybindings`), never from ad-hoc key
|
|
6
6
|
// literals, so a user's rebind is honored by the viewer and tmux alike.
|
|
7
7
|
import { matchesKey } from '@earendil-works/pi-tui';
|
|
8
|
-
import { formatBinding, matchesPiTuiInput, } from '../../../core/keybindings/index.js';
|
|
8
|
+
import { ATTACH_CONTROL_BINDINGS, formatBinding, matchesPiTuiInput, } from '../../../core/keybindings/index.js';
|
|
9
9
|
export { inboxOpenHint } from '../../../core/keybindings/index.js';
|
|
10
10
|
export function matchesAttachBinding(bindings, id, data) {
|
|
11
11
|
return matchesPiTuiInput(bindings, id, data, (input, gesture) => matchesKey(input, gesture));
|
|
12
12
|
}
|
|
13
|
+
export function dispatchAttachAction(id, actions) {
|
|
14
|
+
actions[id]();
|
|
15
|
+
}
|
|
13
16
|
export function dispatchAttachBinding(bindings, data, actions) {
|
|
14
|
-
for (const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'crtr.mode.cycle',
|
|
18
|
-
'crtr.attach.graph.toggle',
|
|
19
|
-
'crtr.attach.model-ladder.next',
|
|
20
|
-
'crtr.attach.model-ladder.previous',
|
|
21
|
-
'crtr.attach.command.inspect',
|
|
22
|
-
'crtr.attach.file-review',
|
|
23
|
-
]) {
|
|
24
|
-
if (matchesAttachBinding(bindings, id, data)) {
|
|
25
|
-
actions[id]();
|
|
17
|
+
for (const { actionId } of ATTACH_CONTROL_BINDINGS) {
|
|
18
|
+
if (matchesAttachBinding(bindings, actionId, data)) {
|
|
19
|
+
dispatchAttachAction(actionId, actions);
|
|
26
20
|
return true;
|
|
27
21
|
}
|
|
28
22
|
}
|
|
@@ -17,6 +17,8 @@ export interface KeyHooks {
|
|
|
17
17
|
inspectLoadedCommand: () => void;
|
|
18
18
|
/** The file-review chord — open the transcript-file picker + review surface. */
|
|
19
19
|
openFileReview: () => void;
|
|
20
|
+
/** Search the current node's local profile project files. */
|
|
21
|
+
openProfileFiles: () => void;
|
|
20
22
|
}
|
|
21
23
|
/** Install the listener; returns the remover, which teardown MUST call. */
|
|
22
24
|
export declare function installGlobalKeys(ctx: Pick<AttachSession, 'tui' | 'editor'>, hooks: KeyHooks): () => void;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// A TUI input listener fires BEFORE the focused component and before any dialog
|
|
4
4
|
// overlay, so the configured attach actions work regardless of what has focus.
|
|
5
5
|
// That privilege is also the hazard: anything consumed here never reaches the
|
|
6
|
-
// editor. Only
|
|
7
|
-
// roster while it holds the cursor, and a configured crtr binding
|
|
8
|
-
// returns
|
|
9
|
-
import {
|
|
6
|
+
// editor. Only terminal focus reports, a private crouter control input, the
|
|
7
|
+
// roster while it holds the cursor, and a configured crtr binding may claim a
|
|
8
|
+
// chunk; each returns ordinary editor input untouched when it does not apply.
|
|
9
|
+
import { extractAttachControlInput } from '../../../core/keybindings/index.js';
|
|
10
|
+
import { dispatchAttachAction, dispatchAttachBinding } from './bindings.js';
|
|
10
11
|
import { FOCUS_IN, FOCUS_OUT } from './focus-events.js';
|
|
11
12
|
/** Press the clear-or-detach binding twice inside this window to detach. */
|
|
12
13
|
const CLEAR_OR_DETACH_MS = 1000;
|
|
@@ -14,47 +15,45 @@ const CLEAR_OR_DETACH_MS = 1000;
|
|
|
14
15
|
export function installGlobalKeys(ctx, hooks) {
|
|
15
16
|
const { tui, editor } = ctx;
|
|
16
17
|
let lastClearOrDetachAt = 0;
|
|
18
|
+
const actions = {
|
|
19
|
+
'crtr.attach.detach': hooks.detach,
|
|
20
|
+
'crtr.attach.clear-or-detach': () => {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (now - lastClearOrDetachAt < CLEAR_OR_DETACH_MS) {
|
|
23
|
+
lastClearOrDetachAt = 0;
|
|
24
|
+
hooks.detach();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
lastClearOrDetachAt = now;
|
|
28
|
+
editor.setText('');
|
|
29
|
+
hooks.setNotice(`Editor cleared — press ${hooks.detachHint()} again to detach`);
|
|
30
|
+
tui.requestRender();
|
|
31
|
+
},
|
|
32
|
+
'crtr.mode.cycle': hooks.cycleMode,
|
|
33
|
+
'crtr.attach.graph.toggle': hooks.toggleGraph,
|
|
34
|
+
// A plain broker frame, so this works locally and remotely alike; the broker gates observers on controller role.
|
|
35
|
+
'crtr.attach.model-ladder.next': () => hooks.cycleModelLadder('forward'),
|
|
36
|
+
'crtr.attach.model-ladder.previous': () => hooks.cycleModelLadder('backward'),
|
|
37
|
+
'crtr.attach.command.inspect': hooks.inspectLoadedCommand,
|
|
38
|
+
'crtr.attach.file-review': hooks.openFileReview,
|
|
39
|
+
'crtr.attach.profile-files': hooks.openProfileFiles,
|
|
40
|
+
};
|
|
17
41
|
return tui.addInputListener((data) => {
|
|
18
|
-
// Terminal focus reporting (DECSET 1004, enabled after tui.start; tmux
|
|
19
|
-
// forwards it per-pane with `focus-events on`). Pane focus drives the
|
|
20
|
-
// editor's fake cursor — solid block focused, hollow outline blurred — so a
|
|
21
|
-
// wall of attach panes shows exactly which one keys go to. The events are
|
|
22
|
-
// stripped rather than blanket-consumed in case the chunk coalesced them
|
|
23
|
-
// with real keystrokes.
|
|
42
|
+
// Terminal focus reporting (DECSET 1004, enabled after tui.start; tmux forwards it per-pane with `focus-events on`). Pane focus drives the editor's fake cursor — solid block focused, hollow outline blurred — so a wall of attach panes shows exactly which one keys go to. The events are stripped rather than blanket-consumed in case the chunk coalesced them with real keystrokes.
|
|
24
43
|
if (data.includes(FOCUS_IN) || data.includes(FOCUS_OUT)) {
|
|
25
44
|
hooks.setPaneFocused(data.lastIndexOf(FOCUS_IN) > data.lastIndexOf(FOCUS_OUT));
|
|
26
45
|
tui.requestRender();
|
|
27
46
|
const rest = data.replaceAll(FOCUS_IN, '').replaceAll(FOCUS_OUT, '');
|
|
28
47
|
return rest.length > 0 ? { data: rest } : { consume: true };
|
|
29
48
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
const control = extractAttachControlInput(data);
|
|
50
|
+
if (control !== undefined) {
|
|
51
|
+
dispatchAttachAction(control.actionId, actions);
|
|
52
|
+
return control.remaining.length > 0 ? { data: control.remaining } : { consume: true };
|
|
53
|
+
}
|
|
54
|
+
// Inline subagent scroll+swap: once the editor hands off at its ↓ boundary, ↑/↓ move over the manager/report roster and Enter dives into a node. An inactive roster never intercepts editor input.
|
|
33
55
|
if (hooks.rosterHandleKey(data))
|
|
34
56
|
return { consume: true };
|
|
35
|
-
|
|
36
|
-
'crtr.attach.detach': hooks.detach,
|
|
37
|
-
'crtr.attach.clear-or-detach': () => {
|
|
38
|
-
const now = Date.now();
|
|
39
|
-
if (now - lastClearOrDetachAt < CLEAR_OR_DETACH_MS) {
|
|
40
|
-
lastClearOrDetachAt = 0;
|
|
41
|
-
hooks.detach();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
lastClearOrDetachAt = now;
|
|
45
|
-
editor.setText('');
|
|
46
|
-
hooks.setNotice(`Editor cleared — press ${hooks.detachHint()} again to detach`);
|
|
47
|
-
tui.requestRender();
|
|
48
|
-
},
|
|
49
|
-
'crtr.mode.cycle': hooks.cycleMode,
|
|
50
|
-
'crtr.attach.graph.toggle': hooks.toggleGraph,
|
|
51
|
-
// A plain broker frame, so this works locally and remotely alike; the
|
|
52
|
-
// broker gates observers on controller role.
|
|
53
|
-
'crtr.attach.model-ladder.next': () => hooks.cycleModelLadder('forward'),
|
|
54
|
-
'crtr.attach.model-ladder.previous': () => hooks.cycleModelLadder('backward'),
|
|
55
|
-
'crtr.attach.command.inspect': hooks.inspectLoadedCommand,
|
|
56
|
-
'crtr.attach.file-review': hooks.openFileReview,
|
|
57
|
-
});
|
|
58
|
-
return dispatched ? { consume: true } : undefined;
|
|
57
|
+
return dispatchAttachBinding(hooks.bindings(), data, actions) ? { consume: true } : undefined;
|
|
59
58
|
});
|
|
60
59
|
}
|