@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2958 -430
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/sayknow-pet-widget.ts +360 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnimationRegistration,
|
|
3
|
+
buildSayknowPixelFrames,
|
|
4
|
+
type Component,
|
|
5
|
+
type Container,
|
|
6
|
+
getCellDimensions,
|
|
7
|
+
ImageProtocol,
|
|
8
|
+
PARA_PARA_STEPS,
|
|
9
|
+
PET_SKINS,
|
|
10
|
+
type PetMode,
|
|
11
|
+
type PetSkinId,
|
|
12
|
+
petBurstDurationMs,
|
|
13
|
+
petBurstFrame,
|
|
14
|
+
registerAnimationCallback,
|
|
15
|
+
type SayknowPixelFrameName,
|
|
16
|
+
type SayknowPixelFrames,
|
|
17
|
+
TERMINAL,
|
|
18
|
+
type TUI,
|
|
19
|
+
} from "@sayknow-cli/tui";
|
|
20
|
+
import type { CustomEditor } from "./custom-editor";
|
|
21
|
+
|
|
22
|
+
/** Re-exported from the tui skin registry so widget-relative imports stay valid. */
|
|
23
|
+
export type { PetMode, PetSkinId };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Empty columns on each side of the pet: an explicit inset from the right edge,
|
|
27
|
+
* with the composer's own right gutter (setRightGutterWidth(1)) as the left gap.
|
|
28
|
+
*/
|
|
29
|
+
const PET_SIDE_MARGIN = 1;
|
|
30
|
+
/** Sub-cell drop after the one-row safety lift, preserving a small bottom gap. */
|
|
31
|
+
const PET_SIXEL_DROP_PX = 9;
|
|
32
|
+
/**
|
|
33
|
+
* Kitty sub-cell drop below the one-row safety lift, as a fraction of the live cell
|
|
34
|
+
* height so it scales with the font. `floor` keeps it inside the cell; the value is
|
|
35
|
+
* clamped to the cell height.
|
|
36
|
+
*/
|
|
37
|
+
const KITTY_DROP_FRACTION = 0.45;
|
|
38
|
+
const petKittyDropPx = (cellHeightPx: number): number =>
|
|
39
|
+
Math.min(Math.max(0, cellHeightPx - 1), Math.floor(cellHeightPx * KITTY_DROP_FRACTION));
|
|
40
|
+
const PET_RAISE_ROWS = 1;
|
|
41
|
+
/** Working animation: the shared para-para beats looped end to end. */
|
|
42
|
+
const WORK_LOOP_TOTAL = PARA_PARA_STEPS.reduce((sum, [, ms]) => sum + ms, 0);
|
|
43
|
+
/** Random gap between automatic claw flexes (fires while idle AND working). */
|
|
44
|
+
const AUTO_FLEX_MIN_GAP_MS = 12_000;
|
|
45
|
+
const AUTO_FLEX_MAX_GAP_MS = 40_000;
|
|
46
|
+
// Deterministic idle loop: gaze around with a rare visor flicker.
|
|
47
|
+
const IDLE_LOOP: Array<[SayknowPixelFrameName, number]> = [
|
|
48
|
+
["base", 1100],
|
|
49
|
+
["gazeL", 350],
|
|
50
|
+
["base", 500],
|
|
51
|
+
["gazeR", 350],
|
|
52
|
+
["base", 800],
|
|
53
|
+
["flicker", 150],
|
|
54
|
+
];
|
|
55
|
+
const IDLE_LOOP_TOTAL = IDLE_LOOP.reduce((sum, [, ms]) => sum + ms, 0);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Selector preview: fire the first burst this soon after a skin is previewed, so the
|
|
59
|
+
* pet shows one idle eye-roll (base -> gazeL -> base -> gazeR) and then its signature
|
|
60
|
+
* flex/cry. Live use keeps the random AUTO_FLEX gap; only preview forces this demo.
|
|
61
|
+
*/
|
|
62
|
+
const PREVIEW_INTRO_MS = 2300;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Wraps the composer editor, reserving a right-side area beside it where the
|
|
66
|
+
* real-pixel pet is drawn. The editor just renders narrower; the pet pixels
|
|
67
|
+
* are emitted separately as an absolute-positioned overlay.
|
|
68
|
+
*/
|
|
69
|
+
export class PetFramedEditor implements Component {
|
|
70
|
+
#editor: CustomEditor;
|
|
71
|
+
#reserve = 0;
|
|
72
|
+
|
|
73
|
+
constructor(editor: CustomEditor) {
|
|
74
|
+
this.#editor = editor;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setReserve(columns: number): void {
|
|
78
|
+
this.#reserve = columns;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
canFit(width: number): boolean {
|
|
82
|
+
return this.#reserve > 0 && width > this.#reserve + 8;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
invalidate(): void {
|
|
86
|
+
this.#editor.invalidate?.();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(width: number): string[] {
|
|
90
|
+
if (!this.canFit(width)) {
|
|
91
|
+
return this.#editor.render(width);
|
|
92
|
+
}
|
|
93
|
+
return this.#editor.render(width - this.#reserve);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The sayknow pet: a 16x16 real-pixel sprite living in a reserved area beside
|
|
99
|
+
* the composer. It is nearest-neighbor scaled to the two terminal rows occupied
|
|
100
|
+
* by an empty one-line composer and lifted one row so its feet meet the input
|
|
101
|
+
* box's bottom edge.
|
|
102
|
+
*
|
|
103
|
+
* Rendering has two paths that share one payload builder:
|
|
104
|
+
* - a post-render emitter re-draws the sprite after every TUI write (line
|
|
105
|
+
* renders clear the pet cells, so the overlay must be re-applied), and
|
|
106
|
+
* - frame advances write the payload directly to the terminal, because the
|
|
107
|
+
* TUI skips writes entirely when no component line changed.
|
|
108
|
+
*
|
|
109
|
+
* Requires a sixel- or kitty-graphics terminal (`pixelProtocol()`).
|
|
110
|
+
*/
|
|
111
|
+
export class SayknowPetWidget {
|
|
112
|
+
#ui: TUI;
|
|
113
|
+
#editor: CustomEditor;
|
|
114
|
+
#editorContainer: Container;
|
|
115
|
+
#floorContainer: Container;
|
|
116
|
+
#framedEditor: PetFramedEditor;
|
|
117
|
+
#isWorking: () => boolean;
|
|
118
|
+
#getComposerBottomOffset: () => number;
|
|
119
|
+
#mode: PetMode = "off";
|
|
120
|
+
#pixel: SayknowPixelFrames | undefined;
|
|
121
|
+
#frame: SayknowPixelFrameName = "base";
|
|
122
|
+
#animation: AnimationRegistration | undefined;
|
|
123
|
+
#flexUntil = 0;
|
|
124
|
+
#nextAutoFlexAt = 0;
|
|
125
|
+
#autoFlexGapMs: [number, number] | null;
|
|
126
|
+
#forcedProtocol: "sixel" | "kitty" | undefined;
|
|
127
|
+
/** Cell metrics the current frames were built for; a change triggers a rebuild. */
|
|
128
|
+
#builtCellW = 0;
|
|
129
|
+
#builtCellH = 0;
|
|
130
|
+
|
|
131
|
+
constructor(options: {
|
|
132
|
+
ui: TUI;
|
|
133
|
+
editor: CustomEditor;
|
|
134
|
+
editorContainer: Container;
|
|
135
|
+
floorContainer: Container;
|
|
136
|
+
isWorking: () => boolean;
|
|
137
|
+
/** Rows rendered below the composer box (pet floor + hook widgets). */
|
|
138
|
+
getComposerBottomOffset: () => number;
|
|
139
|
+
forcePixelProtocol?: "sixel" | "kitty";
|
|
140
|
+
/** Random [min, max] ms between auto-flexes; null disables. */
|
|
141
|
+
autoFlexGapMs?: [number, number] | null;
|
|
142
|
+
}) {
|
|
143
|
+
this.#ui = options.ui;
|
|
144
|
+
this.#editor = options.editor;
|
|
145
|
+
this.#editorContainer = options.editorContainer;
|
|
146
|
+
this.#floorContainer = options.floorContainer;
|
|
147
|
+
this.#framedEditor = new PetFramedEditor(options.editor);
|
|
148
|
+
this.#isWorking = options.isWorking;
|
|
149
|
+
this.#getComposerBottomOffset = options.getComposerBottomOffset;
|
|
150
|
+
this.#forcedProtocol = options.forcePixelProtocol;
|
|
151
|
+
this.#autoFlexGapMs =
|
|
152
|
+
options.autoFlexGapMs === undefined ? [AUTO_FLEX_MIN_GAP_MS, AUTO_FLEX_MAX_GAP_MS] : options.autoFlexGapMs;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Protocol available for the real-pixel pet, if any. */
|
|
156
|
+
static pixelProtocol(): "sixel" | "kitty" | null {
|
|
157
|
+
if (TERMINAL.imageProtocol === ImageProtocol.Kitty) return "kitty";
|
|
158
|
+
if (TERMINAL.imageProtocol === ImageProtocol.Sixel) return "sixel";
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get mode(): PetMode {
|
|
163
|
+
return this.#mode;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get isFlexing(): boolean {
|
|
167
|
+
return performance.now() < this.#flexUntil;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setMode(mode: PetMode): void {
|
|
171
|
+
this.#applyMode(mode, true);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Live preview during a selector: change the sprite without re-mounting the
|
|
175
|
+
* composer editor (that would tear down the open overlay). After a short idle
|
|
176
|
+
* eye-roll it fires the signature burst once (RedSayknow flex, BlueSayknow para-para
|
|
177
|
+
* then sob) so the selector demos the animation instead of waiting the random gap. */
|
|
178
|
+
previewMode(mode: PetMode): void {
|
|
179
|
+
this.#applyMode(mode, false);
|
|
180
|
+
if (mode !== "off" && this.#autoFlexGapMs) {
|
|
181
|
+
this.#nextAutoFlexAt = performance.now() + PREVIEW_INTRO_MS;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#applyMode(mode: PetMode, mountComposer: boolean): void {
|
|
186
|
+
if (mode === this.#mode) return;
|
|
187
|
+
|
|
188
|
+
if (mode === "off") {
|
|
189
|
+
this.#mode = "off";
|
|
190
|
+
this.#animation?.unregister();
|
|
191
|
+
this.#animation = undefined;
|
|
192
|
+
this.#ui.setPostRenderEmitter(undefined);
|
|
193
|
+
this.#floorContainer.clear();
|
|
194
|
+
this.#pixel = undefined;
|
|
195
|
+
this.#framedEditor.setReserve(0);
|
|
196
|
+
if (mountComposer) this.#mountEditor(false);
|
|
197
|
+
this.#ui.requestRender(true);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const protocol = this.#forcedProtocol ?? SayknowPetWidget.pixelProtocol();
|
|
202
|
+
if (!protocol) return;
|
|
203
|
+
this.#mode = mode;
|
|
204
|
+
this.#frame = "base";
|
|
205
|
+
this.#flexUntil = 0;
|
|
206
|
+
this.#nextAutoFlexAt = 0;
|
|
207
|
+
this.#buildPixel(protocol);
|
|
208
|
+
if (mountComposer) this.#mountEditor(true);
|
|
209
|
+
// The pet overlays the composer's bottom rows; no floor row is reserved, so
|
|
210
|
+
// the composer stays pinned to the terminal bottom.
|
|
211
|
+
this.#floorContainer.clear();
|
|
212
|
+
this.#ui.setPostRenderEmitter(() => this.#overlayPayload());
|
|
213
|
+
this.#animation ??= registerAnimationCallback(now => this.#tick(now), 80);
|
|
214
|
+
this.#ui.requestRender(true);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** (Re)build the encoded frames for the current terminal cell metrics. */
|
|
218
|
+
#buildPixel(protocol: "sixel" | "kitty"): void {
|
|
219
|
+
const cell = getCellDimensions();
|
|
220
|
+
this.#builtCellW = cell.widthPx;
|
|
221
|
+
this.#builtCellH = cell.heightPx;
|
|
222
|
+
const skin: PetSkinId = this.#mode === "off" ? "red" : this.#mode;
|
|
223
|
+
this.#pixel = buildSayknowPixelFrames({
|
|
224
|
+
protocol,
|
|
225
|
+
skin,
|
|
226
|
+
cellWidthPx: cell.widthPx,
|
|
227
|
+
cellHeightPx: cell.heightPx,
|
|
228
|
+
targetRows: 2,
|
|
229
|
+
sixelTopPaddingPx: protocol === "sixel" ? PET_SIXEL_DROP_PX : 0,
|
|
230
|
+
kittyCellYOffsetPx: protocol === "kitty" ? petKittyDropPx(cell.heightPx) : 0,
|
|
231
|
+
});
|
|
232
|
+
this.#framedEditor.setReserve(this.#pixel.columns + PET_SIDE_MARGIN);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
dispose(): void {
|
|
236
|
+
this.#animation?.unregister();
|
|
237
|
+
this.#animation = undefined;
|
|
238
|
+
this.#ui.setPostRenderEmitter(undefined);
|
|
239
|
+
this.#floorContainer.clear();
|
|
240
|
+
this.#framedEditor.setReserve(0);
|
|
241
|
+
this.#mountEditor(false);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#mountEditor(framed: boolean): void {
|
|
245
|
+
this.#editorContainer.clear();
|
|
246
|
+
this.#editorContainer.addChild(framed ? this.#framedEditor : this.#editor);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Re-mount the composer editor (framed when a skin is active) after an overlay. */
|
|
250
|
+
remountComposer(): void {
|
|
251
|
+
this.#mountEditor(this.#mode !== "off");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#pickFrame(now: number): SayknowPixelFrameName {
|
|
255
|
+
const mode = this.#mode;
|
|
256
|
+
if (mode === "off") return "base";
|
|
257
|
+
// Random idle burst → the skin's own animation, driven by its burst descriptor
|
|
258
|
+
// (RedSayknow holds a flex; BlueSayknow dances the para-para then sobs).
|
|
259
|
+
if (now < this.#flexUntil) {
|
|
260
|
+
const burst = PET_SKINS[mode].burst;
|
|
261
|
+
const elapsed = now - (this.#flexUntil - petBurstDurationMs(burst));
|
|
262
|
+
return petBurstFrame(burst, elapsed, now);
|
|
263
|
+
}
|
|
264
|
+
// Working → loop the shared para-para beats.
|
|
265
|
+
if (this.#isWorking()) {
|
|
266
|
+
let d = now % WORK_LOOP_TOTAL;
|
|
267
|
+
for (const [frame, ms] of PARA_PARA_STEPS) {
|
|
268
|
+
if (d < ms) return frame;
|
|
269
|
+
d -= ms;
|
|
270
|
+
}
|
|
271
|
+
return "base";
|
|
272
|
+
}
|
|
273
|
+
let t = now % IDLE_LOOP_TOTAL;
|
|
274
|
+
for (const [frame, ms] of IDLE_LOOP) {
|
|
275
|
+
if (t < ms) return frame;
|
|
276
|
+
t -= ms;
|
|
277
|
+
}
|
|
278
|
+
return "base";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#scheduleAutoFlex(now: number): void {
|
|
282
|
+
if (!this.#autoFlexGapMs) return;
|
|
283
|
+
const [min, max] = this.#autoFlexGapMs;
|
|
284
|
+
this.#nextAutoFlexAt = now + min + Math.random() * Math.max(0, max - min);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#tick(now: number): void {
|
|
288
|
+
if (this.#mode === "off" || !this.#pixel) return;
|
|
289
|
+
// A font/zoom change resizes the terminal cells; rebuild the frames so the
|
|
290
|
+
// kitty image and its sub-cell drop match the new cell metrics.
|
|
291
|
+
const cell = getCellDimensions();
|
|
292
|
+
if (cell.widthPx !== this.#builtCellW || cell.heightPx !== this.#builtCellH) {
|
|
293
|
+
const protocol = this.#forcedProtocol ?? SayknowPetWidget.pixelProtocol();
|
|
294
|
+
if (protocol) {
|
|
295
|
+
this.#buildPixel(protocol);
|
|
296
|
+
this.#mountEditor(true);
|
|
297
|
+
this.#ui.requestRender(true);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Random show-off, both while idle and while working. Each skin's burst runs for
|
|
301
|
+
// its own length (RedSayknow a brief flex; BlueSayknow a para-para cycle plus sob).
|
|
302
|
+
if (this.#autoFlexGapMs && now >= this.#flexUntil) {
|
|
303
|
+
if (this.#nextAutoFlexAt === 0) {
|
|
304
|
+
this.#scheduleAutoFlex(now);
|
|
305
|
+
} else if (now >= this.#nextAutoFlexAt) {
|
|
306
|
+
const burstMs = petBurstDurationMs(PET_SKINS[this.#mode].burst);
|
|
307
|
+
this.#flexUntil = now + burstMs;
|
|
308
|
+
this.#scheduleAutoFlex(now + burstMs);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const frame = this.#pickFrame(now);
|
|
312
|
+
if (frame === this.#frame) return;
|
|
313
|
+
this.#frame = frame;
|
|
314
|
+
// Write directly: a frame swap changes no component line, so the TUI
|
|
315
|
+
// would skip the render write (and with it the post-render emitter).
|
|
316
|
+
const payload = this.#overlayPayload(true) ?? "";
|
|
317
|
+
if (payload && this.#ui.terminalAvailable) {
|
|
318
|
+
this.#ui.terminal.write(`\x1b[?2026h\x1b7${payload}\x1b8\x1b[?2026l`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#petPosition(): { x: number; y: number } | null {
|
|
323
|
+
const pixel = this.#pixel;
|
|
324
|
+
if (!pixel) return null;
|
|
325
|
+
const columns = this.#ui.terminal.columns;
|
|
326
|
+
if (!this.#framedEditor.canFit(columns)) return null;
|
|
327
|
+
const rows = this.#ui.terminal.rows;
|
|
328
|
+
// The sprite is lifted one safety row above the scrolling edge, then dropped
|
|
329
|
+
// back onto the composer's bottom border per protocol (sixel via transparent
|
|
330
|
+
// top padding, kitty via a sub-cell Y offset baked into the frames).
|
|
331
|
+
const composerBottom = rows - this.#getComposerBottomOffset();
|
|
332
|
+
const y = composerBottom - pixel.rows - PET_RAISE_ROWS;
|
|
333
|
+
const x = columns - pixel.columns - PET_SIDE_MARGIN;
|
|
334
|
+
if (y < 0 || x < 0) return null;
|
|
335
|
+
return { x, y };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#clearPetPayload(x: number, y: number): string {
|
|
339
|
+
const pixel = this.#pixel;
|
|
340
|
+
if (pixel?.protocol !== "sixel") return "";
|
|
341
|
+
let out = "\x1b[0m";
|
|
342
|
+
for (let row = 0; row < pixel.rasterRows; row++) {
|
|
343
|
+
out += `\x1b[${y + row + 1};${x + 1}H\x1b[${pixel.columns}X`;
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Draw escape payload at the pet's absolute position. */
|
|
349
|
+
#overlayPayload(clearPet = false): string | null {
|
|
350
|
+
const pixel = this.#pixel;
|
|
351
|
+
if (!pixel) return null;
|
|
352
|
+
const pos = this.#petPosition();
|
|
353
|
+
if (!pos) return null;
|
|
354
|
+
const { x, y } = pos;
|
|
355
|
+
|
|
356
|
+
let out = clearPet ? this.#clearPetPayload(x, y) : "";
|
|
357
|
+
out += `\x1b[${y + 1};${x + 1}H${pixel.frames[this.#frame]}`;
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
}
|