@gajae-code/coding-agent 0.9.6 → 0.10.0
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/CHANGELOG.md +45 -0
- 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 +4 -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/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 +1 -1
- 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/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +47 -7
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/server.d.ts +67 -6
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/internal-urls/local-protocol.d.ts +13 -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/gajae-pet-widget.d.ts +59 -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/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- 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/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/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 +26 -2
- package/dist/types/session/cache-economics.d.ts +79 -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/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +25 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/package.json +9 -9
- 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 +50 -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/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/server.ts +2653 -425
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/deep-interview/render-middleware.ts +67 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/gjc-runtime/launch-tmux.ts +702 -135
- package/src/gjc-runtime/session-state-sidecar.ts +756 -135
- package/src/gjc-runtime/team-runtime.ts +6 -0
- package/src/gjc-runtime/tmux-common.ts +26 -7
- package/src/gjc-runtime/tmux-gc.ts +52 -9
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/gjc-runtime/tmux-sessions.ts +871 -68
- package/src/internal-urls/docs-index.generated.ts +9 -8
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +298 -75
- package/src/modes/acp/acp-agent.ts +728 -98
- 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/gajae-pet-widget.ts +360 -0
- 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/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- 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 +36 -17
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +183 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- 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/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-cli.ts +2 -0
- 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 +396 -145
- package/src/session/cache-economics.ts +275 -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/slash-commands/builtin-registry.ts +143 -9
- 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/ask.ts +355 -33
- 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
|
@@ -14,22 +14,38 @@ import {
|
|
|
14
14
|
import { formatBytes } from "@gajae-code/utils";
|
|
15
15
|
import { theme } from "../../modes/theme/theme";
|
|
16
16
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
17
|
-
import type { SessionInfo } from "../../session/session-manager";
|
|
17
|
+
import type { ResumeSessionIdentity, ResumeTailInspection, SessionInfo } from "../../session/session-manager";
|
|
18
18
|
import { DynamicBorder } from "./dynamic-border";
|
|
19
19
|
import { HookSelectorComponent } from "./hook-selector";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
export interface SelectedSession {
|
|
22
|
+
kind: "selected";
|
|
23
|
+
path: string;
|
|
24
|
+
identity: ResumeSessionIdentity;
|
|
25
|
+
action: "continue-tail" | "open-idle";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CancelledSessionSelection {
|
|
29
|
+
kind: "cancelled";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SessionSelectionResult = SelectedSession | CancelledSessionSelection;
|
|
33
|
+
export type SessionInspector = (sessionPath: string) => Promise<ResumeTailInspection>;
|
|
34
|
+
|
|
35
|
+
type SelectorState =
|
|
36
|
+
| { kind: "browsing" }
|
|
37
|
+
| { kind: "checking"; session: SessionInfo; token: number }
|
|
38
|
+
| { kind: "confirming"; session: SessionInfo; token: number; identity: ResumeSessionIdentity }
|
|
39
|
+
| { kind: "settled" };
|
|
40
|
+
|
|
24
41
|
class SessionList implements Component {
|
|
25
42
|
#filteredSessions: SessionInfo[] = [];
|
|
26
|
-
#selectedIndex
|
|
27
|
-
readonly #searchInput
|
|
28
|
-
|
|
43
|
+
#selectedIndex = 0;
|
|
44
|
+
readonly #searchInput = new Input();
|
|
45
|
+
#inputFrozen = false;
|
|
46
|
+
onSelect?: (session: SessionInfo) => void;
|
|
29
47
|
onCancel?: () => void;
|
|
30
48
|
onExit: () => void = () => {};
|
|
31
|
-
#maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
|
|
32
|
-
|
|
33
49
|
onDeleteRequest?: (session: SessionInfo) => void;
|
|
34
50
|
|
|
35
51
|
constructor(
|
|
@@ -37,255 +53,173 @@ class SessionList implements Component {
|
|
|
37
53
|
private readonly showCwd = false,
|
|
38
54
|
) {
|
|
39
55
|
this.#filteredSessions = allSessions;
|
|
40
|
-
this.#searchInput = new Input();
|
|
41
|
-
|
|
42
|
-
// Handle Enter in search input - select current item
|
|
43
56
|
this.#searchInput.onSubmit = () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (this.onSelect) {
|
|
47
|
-
this.onSelect(selected.path);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
57
|
+
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
58
|
+
if (selected) this.onSelect?.(selected);
|
|
50
59
|
};
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
setInputFrozen(frozen: boolean): void {
|
|
63
|
+
this.#inputFrozen = frozen;
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
#clampSelectedIndex(): void {
|
|
54
|
-
|
|
55
|
-
this.#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.#selectedIndex = Math.max(0, Math.min(this.#selectedIndex, this.#filteredSessions.length - 1));
|
|
67
|
+
this.#selectedIndex =
|
|
68
|
+
this.#filteredSessions.length === 0
|
|
69
|
+
? 0
|
|
70
|
+
: Math.max(0, Math.min(this.#selectedIndex, this.#filteredSessions.length - 1));
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
#filterSessions(query: string): void {
|
|
62
|
-
this.#filteredSessions = fuzzyFilter(this.allSessions, query, session =>
|
|
63
|
-
|
|
74
|
+
this.#filteredSessions = fuzzyFilter(this.allSessions, query, session =>
|
|
75
|
+
[
|
|
64
76
|
session.id,
|
|
65
77
|
session.title ?? "",
|
|
66
78
|
session.cwd ?? "",
|
|
67
79
|
session.firstMessage ?? "",
|
|
68
80
|
session.allMessagesText,
|
|
69
81
|
session.path,
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(" "),
|
|
85
|
+
);
|
|
73
86
|
this.#clampSelectedIndex();
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
removeSession(sessionPath: string): void {
|
|
77
|
-
const index = this.allSessions.findIndex(
|
|
90
|
+
const index = this.allSessions.findIndex(session => session.path === sessionPath);
|
|
78
91
|
if (index === -1) return;
|
|
79
92
|
this.allSessions.splice(index, 1);
|
|
80
|
-
// Re-filter to update filteredSessions
|
|
81
93
|
this.#filterSessions(this.#searchInput.getValue());
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
invalidate(): void {
|
|
85
|
-
// No cached state to invalidate currently
|
|
86
|
-
}
|
|
96
|
+
invalidate(): void {}
|
|
87
97
|
|
|
88
98
|
render(width: number): string[] {
|
|
89
|
-
const lines
|
|
90
|
-
|
|
91
|
-
// Render search input
|
|
92
|
-
lines.push(...this.#searchInput.render(width));
|
|
93
|
-
lines.push(""); // Blank line after search
|
|
94
|
-
|
|
99
|
+
const lines = [...this.#searchInput.render(width), ""];
|
|
95
100
|
if (this.#filteredSessions.length === 0) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
101
|
+
lines.push(
|
|
102
|
+
truncateToWidth(
|
|
103
|
+
theme.fg(
|
|
104
|
+
"muted",
|
|
105
|
+
this.showCwd ? " No sessions found" : " No sessions in current folder. Press Tab to view all.",
|
|
106
|
+
),
|
|
107
|
+
width,
|
|
108
|
+
),
|
|
109
|
+
);
|
|
105
110
|
return lines;
|
|
106
111
|
}
|
|
107
|
-
|
|
108
|
-
// Format dates
|
|
109
112
|
const formatDate = (date: Date): string => {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
|
|
118
|
-
if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
|
|
119
|
-
if (diffDays === 1) return "1 day ago";
|
|
120
|
-
if (diffDays < 7) return `${diffDays} days ago`;
|
|
121
|
-
|
|
122
|
-
return date.toLocaleDateString();
|
|
113
|
+
const minutes = Math.floor((Date.now() - date.getTime()) / 60000);
|
|
114
|
+
if (minutes < 1) return "just now";
|
|
115
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
116
|
+
const hours = Math.floor(minutes / 60);
|
|
117
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
118
|
+
const days = Math.floor(hours / 24);
|
|
119
|
+
return days === 1 ? "1 day ago" : days < 7 ? `${days} days ago` : date.toLocaleDateString();
|
|
123
120
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const startIndex = Math.max(
|
|
121
|
+
const maxVisible = 5;
|
|
122
|
+
const start = Math.max(
|
|
127
123
|
0,
|
|
128
|
-
Math.min(
|
|
129
|
-
this.#selectedIndex - Math.floor(this.#maxVisible / 2),
|
|
130
|
-
this.#filteredSessions.length - this.#maxVisible,
|
|
131
|
-
),
|
|
124
|
+
Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.#filteredSessions.length - maxVisible),
|
|
132
125
|
);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Second line: dimmed first message preview
|
|
156
|
-
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth);
|
|
157
|
-
lines.push(` ${theme.fg("dim", truncatedPreview)}`);
|
|
158
|
-
} else {
|
|
159
|
-
// No title: show first message as main line
|
|
160
|
-
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth);
|
|
161
|
-
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
|
162
|
-
lines.push(messageLine);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Metadata line: date + file size
|
|
166
|
-
const modified = formatDate(session.modified);
|
|
167
|
-
const metadata = ` ${modified} ${theme.sep.dot} ${formatBytes(session.size)}`;
|
|
168
|
-
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width));
|
|
169
|
-
|
|
170
|
-
lines.push(metadataLine);
|
|
171
|
-
lines.push(""); // Blank line between sessions
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Add scroll indicator if needed
|
|
175
|
-
if (startIndex > 0 || endIndex < this.#filteredSessions.length) {
|
|
176
|
-
const scrollText = ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`;
|
|
177
|
-
const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width));
|
|
178
|
-
lines.push(scrollInfo);
|
|
126
|
+
const end = Math.min(start + maxVisible, this.#filteredSessions.length);
|
|
127
|
+
for (let index = start; index < end; index++) {
|
|
128
|
+
const session = this.#filteredSessions[index];
|
|
129
|
+
const selected = index === this.#selectedIndex;
|
|
130
|
+
const cursor = `${theme.nav.cursor} `;
|
|
131
|
+
const cursorWidth = visibleWidth(cursor);
|
|
132
|
+
const prefix = selected ? theme.fg("accent", cursor) : padding(cursorWidth);
|
|
133
|
+
const message = session.firstMessage.replace(/\n/g, " ").trim();
|
|
134
|
+
const title = truncateToWidth(session.title ?? message, width - cursorWidth);
|
|
135
|
+
lines.push(prefix + (selected ? theme.bold(title) : title));
|
|
136
|
+
if (session.title) lines.push(` ${theme.fg("dim", truncateToWidth(message, width - cursorWidth))}`);
|
|
137
|
+
lines.push(
|
|
138
|
+
theme.fg(
|
|
139
|
+
"dim",
|
|
140
|
+
truncateToWidth(
|
|
141
|
+
` ${formatDate(session.modified)} ${theme.sep.dot} ${formatBytes(session.size)}`,
|
|
142
|
+
width,
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
lines.push("");
|
|
179
147
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
lines.push(
|
|
183
|
-
|
|
184
|
-
|
|
148
|
+
if (start > 0 || end < this.#filteredSessions.length)
|
|
149
|
+
lines.push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`));
|
|
150
|
+
lines.push(
|
|
151
|
+
"",
|
|
152
|
+
theme.fg("muted", " [Del to delete selected transcript/artifacts, Enter to select, Esc to cancel]"),
|
|
153
|
+
);
|
|
185
154
|
return lines;
|
|
186
155
|
}
|
|
187
156
|
|
|
188
157
|
handleInput(keyData: string): void {
|
|
189
|
-
|
|
158
|
+
if (matchesAppInterrupt(keyData)) {
|
|
159
|
+
this.onCancel?.();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (matchesKey(keyData, "ctrl+c")) {
|
|
163
|
+
this.onExit();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (this.#inputFrozen) return;
|
|
167
|
+
const move = (delta: number): void => {
|
|
190
168
|
if (this.#filteredSessions.length === 0) return;
|
|
191
169
|
this.#selectedIndex += delta;
|
|
192
170
|
this.#clampSelectedIndex();
|
|
193
171
|
};
|
|
194
|
-
|
|
195
|
-
// Delete key - request delete confirmation from parent
|
|
196
172
|
if (matchesKey(keyData, "delete")) {
|
|
197
173
|
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
198
|
-
if (selected
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Up arrow
|
|
205
|
-
if (matchesKey(keyData, "up")) {
|
|
206
|
-
moveSelection(-1);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
// Down arrow
|
|
210
|
-
if (matchesKey(keyData, "down")) {
|
|
211
|
-
moveSelection(1);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
// Page up - jump up by maxVisible items
|
|
215
|
-
if (matchesKey(keyData, "pageUp")) {
|
|
216
|
-
moveSelection(-this.#maxVisible);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// Page down - jump down by maxVisible items
|
|
220
|
-
if (matchesKey(keyData, "pageDown")) {
|
|
221
|
-
moveSelection(this.#maxVisible);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
// Enter
|
|
225
|
-
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
174
|
+
if (selected) this.onDeleteRequest?.(selected);
|
|
175
|
+
} else if (matchesKey(keyData, "up")) move(-1);
|
|
176
|
+
else if (matchesKey(keyData, "down")) move(1);
|
|
177
|
+
else if (matchesKey(keyData, "pageUp")) move(-5);
|
|
178
|
+
else if (matchesKey(keyData, "pageDown")) move(5);
|
|
179
|
+
else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
226
180
|
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
227
|
-
if (selected
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
// Escape - cancel
|
|
233
|
-
if (matchesAppInterrupt(keyData)) {
|
|
234
|
-
if (this.onCancel) {
|
|
235
|
-
this.onCancel();
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
// Ctrl+C - exit
|
|
240
|
-
if (matchesKey(keyData, "ctrl+c")) {
|
|
241
|
-
this.onExit();
|
|
242
|
-
return;
|
|
181
|
+
if (selected) this.onSelect?.(selected);
|
|
182
|
+
} else {
|
|
183
|
+
this.#searchInput.handleInput(keyData);
|
|
184
|
+
this.#filterSessions(this.#searchInput.getValue());
|
|
243
185
|
}
|
|
244
|
-
// Pass everything else to search input
|
|
245
|
-
this.#searchInput.handleInput(keyData);
|
|
246
|
-
this.#filterSessions(this.#searchInput.getValue());
|
|
247
186
|
}
|
|
248
187
|
}
|
|
249
188
|
|
|
250
|
-
/**
|
|
251
|
-
* Component that renders a session selector with optional confirmation dialog
|
|
252
|
-
*/
|
|
189
|
+
/** A one-shot resume consent selector. It never opens or mutates sessions. */
|
|
253
190
|
export class SessionSelectorComponent extends Container {
|
|
254
191
|
#sessionList: SessionList;
|
|
255
192
|
#confirmationDialog: HookSelectorComponent | null = null;
|
|
256
|
-
#messageContainer
|
|
257
|
-
#onDelete?: (session: SessionInfo) => Promise<boolean>;
|
|
193
|
+
#messageContainer = new Container();
|
|
258
194
|
#onRequestRender?: () => void;
|
|
195
|
+
#state: SelectorState = { kind: "browsing" };
|
|
196
|
+
#nextToken = 0;
|
|
259
197
|
|
|
260
198
|
constructor(
|
|
261
199
|
sessions: SessionInfo[],
|
|
262
|
-
onSelect: (sessionPath: string) => void,
|
|
263
|
-
onCancel: () => void,
|
|
264
|
-
onExit: () => void,
|
|
265
|
-
onDelete?: (session: SessionInfo) => Promise<boolean>,
|
|
200
|
+
private readonly onSelect: (sessionPath: string) => void,
|
|
201
|
+
private readonly onCancel: () => void,
|
|
202
|
+
private readonly onExit: () => void,
|
|
203
|
+
private readonly onDelete?: (session: SessionInfo) => Promise<boolean>,
|
|
204
|
+
private readonly inspector?: SessionInspector,
|
|
205
|
+
private readonly onSelection?: (selection: SessionSelectionResult) => void,
|
|
266
206
|
) {
|
|
267
207
|
super();
|
|
268
|
-
|
|
269
|
-
this.#messageContainer = new Container();
|
|
270
|
-
this.#onDelete = onDelete;
|
|
271
|
-
// Add header
|
|
272
208
|
this.addChild(new Spacer(1));
|
|
273
209
|
this.addChild(new Text(theme.bold("Resume Session"), 1, 0));
|
|
274
210
|
this.addChild(new Spacer(1));
|
|
275
211
|
this.addChild(new DynamicBorder());
|
|
276
212
|
this.addChild(new Spacer(1));
|
|
277
213
|
this.addChild(this.#messageContainer);
|
|
278
|
-
// Create session list
|
|
279
214
|
this.#sessionList = new SessionList(sessions);
|
|
280
|
-
this.#sessionList.onSelect =
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.#sessionList.onDeleteRequest = (session: SessionInfo) => {
|
|
284
|
-
this.#showDeleteConfirmation(session);
|
|
215
|
+
this.#sessionList.onSelect = session => {
|
|
216
|
+
if (this.inspector) void this.#inspect(session);
|
|
217
|
+
else this.onSelect(session.path);
|
|
285
218
|
};
|
|
219
|
+
this.#sessionList.onCancel = () => this.#cancel();
|
|
220
|
+
this.#sessionList.onExit = () => this.#exit();
|
|
221
|
+
this.#sessionList.onDeleteRequest = session => this.#showDeleteConfirmation(session);
|
|
286
222
|
this.addChild(this.#sessionList);
|
|
287
|
-
|
|
288
|
-
// Add bottom border
|
|
289
223
|
this.addChild(new Spacer(1));
|
|
290
224
|
this.addChild(new DynamicBorder());
|
|
291
225
|
}
|
|
@@ -293,60 +227,127 @@ export class SessionSelectorComponent extends Container {
|
|
|
293
227
|
setOnRequestRender(callback: () => void): void {
|
|
294
228
|
this.#onRequestRender = callback;
|
|
295
229
|
}
|
|
296
|
-
|
|
297
230
|
#clearError(): void {
|
|
298
231
|
this.#messageContainer.clear();
|
|
299
232
|
}
|
|
300
|
-
|
|
301
233
|
#showError(message: string): void {
|
|
302
234
|
this.#messageContainer.clear();
|
|
303
|
-
this.#messageContainer.addChild(
|
|
235
|
+
this.#messageContainer.addChild(
|
|
236
|
+
new Text(theme.fg("error", `Error: ${replaceTabs(message).slice(0, 200)}`), 1, 0),
|
|
237
|
+
);
|
|
304
238
|
this.#messageContainer.addChild(new Spacer(1));
|
|
305
239
|
}
|
|
240
|
+
#requestRender(): void {
|
|
241
|
+
this.#onRequestRender?.();
|
|
242
|
+
}
|
|
306
243
|
|
|
244
|
+
#settle(selection: SessionSelectionResult): void {
|
|
245
|
+
if (this.#state.kind === "settled") return;
|
|
246
|
+
this.#state = { kind: "settled" };
|
|
247
|
+
this.#sessionList.setInputFrozen(true);
|
|
248
|
+
const dialog = this.#confirmationDialog;
|
|
249
|
+
this.#confirmationDialog = null;
|
|
250
|
+
if (dialog) this.removeChild(dialog);
|
|
251
|
+
if (this.onSelection) this.onSelection(selection);
|
|
252
|
+
else if (selection.kind === "selected") this.onSelect(selection.path);
|
|
253
|
+
else this.onCancel();
|
|
254
|
+
}
|
|
255
|
+
#cancel(): void {
|
|
256
|
+
if (this.#state.kind === "settled") return;
|
|
257
|
+
if (this.#state.kind === "checking") this.#nextToken++;
|
|
258
|
+
this.#settle({ kind: "cancelled" });
|
|
259
|
+
}
|
|
260
|
+
#exit(): void {
|
|
261
|
+
if (this.#state.kind !== "settled") this.#nextToken++;
|
|
262
|
+
this.#state = { kind: "settled" };
|
|
263
|
+
this.#sessionList.setInputFrozen(true);
|
|
264
|
+
this.onExit();
|
|
265
|
+
}
|
|
266
|
+
async #inspect(session: SessionInfo): Promise<void> {
|
|
267
|
+
const inspector = this.inspector;
|
|
268
|
+
if (!inspector || this.#state.kind !== "browsing") return;
|
|
269
|
+
const token = ++this.#nextToken;
|
|
270
|
+
this.#state = { kind: "checking", session, token };
|
|
271
|
+
this.#sessionList.setInputFrozen(true);
|
|
272
|
+
this.#clearError();
|
|
273
|
+
this.#requestRender();
|
|
274
|
+
try {
|
|
275
|
+
const inspection = await inspector(session.path);
|
|
276
|
+
if (this.#state.kind !== "checking" || this.#state.token !== token) return;
|
|
277
|
+
if (inspection.kind === "error") {
|
|
278
|
+
this.#state = { kind: "browsing" };
|
|
279
|
+
this.#sessionList.setInputFrozen(false);
|
|
280
|
+
this.#showError(`Unable to inspect session (${inspection.reason}).`);
|
|
281
|
+
this.#requestRender();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (inspection.kind === "terminal") {
|
|
285
|
+
this.#settle({ kind: "selected", path: session.path, identity: inspection.identity, action: "open-idle" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.#showResumeConfirmation(session, token, inspection.identity);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (this.#state.kind !== "checking" || this.#state.token !== token) return;
|
|
291
|
+
this.#state = { kind: "browsing" };
|
|
292
|
+
this.#sessionList.setInputFrozen(false);
|
|
293
|
+
this.#showError(error instanceof Error ? error.message : String(error));
|
|
294
|
+
this.#requestRender();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
#showResumeConfirmation(session: SessionInfo, token: number, identity: ResumeSessionIdentity): void {
|
|
298
|
+
this.#state = { kind: "confirming", session, token, identity };
|
|
299
|
+
const dialog = new HookSelectorComponent(
|
|
300
|
+
"Resume this session?",
|
|
301
|
+
["Yes", "No"],
|
|
302
|
+
option => {
|
|
303
|
+
if (this.#confirmationDialog !== dialog || this.#state.kind !== "confirming") return;
|
|
304
|
+
if (option === "Yes")
|
|
305
|
+
this.#settle({ kind: "selected", path: session.path, identity, action: "continue-tail" });
|
|
306
|
+
else this.#settle({ kind: "cancelled" });
|
|
307
|
+
},
|
|
308
|
+
() => this.#settle({ kind: "cancelled" }),
|
|
309
|
+
{ acceleratorMap: { y: "Yes", n: "No" } },
|
|
310
|
+
);
|
|
311
|
+
this.#confirmationDialog = dialog;
|
|
312
|
+
this.addChild(dialog);
|
|
313
|
+
this.#requestRender();
|
|
314
|
+
}
|
|
307
315
|
#showDeleteConfirmation(session: SessionInfo): void {
|
|
316
|
+
if (this.#state.kind !== "browsing" || this.#confirmationDialog) return;
|
|
308
317
|
const displayName = session.title || session.firstMessage.slice(0, 40) || session.id;
|
|
309
|
-
|
|
318
|
+
let deleting = false;
|
|
319
|
+
const dialog = new HookSelectorComponent(
|
|
310
320
|
`Delete selected session transcript and artifacts?\n${displayName}\nThis cannot be undone. Other sessions and topic/history metadata are not deleted.`,
|
|
311
321
|
["Yes", "No"],
|
|
312
|
-
async
|
|
313
|
-
if (
|
|
322
|
+
async option => {
|
|
323
|
+
if (this.#confirmationDialog !== dialog || deleting) return;
|
|
324
|
+
if (option === "Yes" && this.onDelete) {
|
|
325
|
+
deleting = true;
|
|
314
326
|
this.#clearError();
|
|
315
327
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
} catch (err) {
|
|
321
|
-
this.#showError(err instanceof Error ? err.message : String(err));
|
|
328
|
+
if (await this.onDelete(session)) this.#sessionList.removeSession(session.path);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
this.#showError(error instanceof Error ? error.message : String(error));
|
|
322
331
|
}
|
|
323
332
|
}
|
|
324
|
-
|
|
325
|
-
this.removeChild(this.#confirmationDialog!);
|
|
333
|
+
this.removeChild(dialog);
|
|
326
334
|
this.#confirmationDialog = null;
|
|
327
|
-
|
|
328
|
-
this.#onRequestRender?.();
|
|
335
|
+
this.#requestRender();
|
|
329
336
|
},
|
|
330
337
|
() => {
|
|
331
|
-
|
|
332
|
-
this.removeChild(
|
|
338
|
+
if (this.#confirmationDialog !== dialog || deleting) return;
|
|
339
|
+
this.removeChild(dialog);
|
|
333
340
|
this.#confirmationDialog = null;
|
|
334
|
-
|
|
335
|
-
this.#onRequestRender?.();
|
|
341
|
+
this.#requestRender();
|
|
336
342
|
},
|
|
337
343
|
);
|
|
338
|
-
|
|
339
|
-
this.addChild(
|
|
344
|
+
this.#confirmationDialog = dialog;
|
|
345
|
+
this.addChild(dialog);
|
|
340
346
|
}
|
|
341
|
-
|
|
342
347
|
handleInput(keyData: string): void {
|
|
343
|
-
if (this.#confirmationDialog)
|
|
344
|
-
|
|
345
|
-
} else {
|
|
346
|
-
this.#sessionList.handleInput(keyData);
|
|
347
|
-
}
|
|
348
|
+
if (this.#confirmationDialog) this.#confirmationDialog.handleInput(keyData);
|
|
349
|
+
else this.#sessionList.handleInput(keyData);
|
|
348
350
|
}
|
|
349
|
-
|
|
350
351
|
getSessionList(): SessionList {
|
|
351
352
|
return this.#sessionList;
|
|
352
353
|
}
|
|
@@ -682,6 +682,8 @@ export interface SettingsCallbacks {
|
|
|
682
682
|
onThemePreview?: (theme: string) => void | Promise<void>;
|
|
683
683
|
/** Called to restore the rendered theme when theme settings preview is cancelled */
|
|
684
684
|
onThemePreviewCancel?: (theme: string) => void | Promise<void>;
|
|
685
|
+
/** Called to live-preview the gajae pet skin while browsing the pet setting. */
|
|
686
|
+
onPetPreview?: (mode: string) => void;
|
|
685
687
|
/** Called for status line preview while configuring */
|
|
686
688
|
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
687
689
|
/** Get current rendered status line for inline preview */
|
|
@@ -925,6 +927,14 @@ export class SettingsSelectorComponent extends Container {
|
|
|
925
927
|
});
|
|
926
928
|
this.#updateStatusPreview();
|
|
927
929
|
};
|
|
930
|
+
} else if (def.path === "pet.mode") {
|
|
931
|
+
const savedPetMode = currentValue;
|
|
932
|
+
onPreview = value => {
|
|
933
|
+
this.callbacks.onPetPreview?.(value);
|
|
934
|
+
};
|
|
935
|
+
onPreviewCancel = () => {
|
|
936
|
+
this.callbacks.onPetPreview?.(savedPetMode);
|
|
937
|
+
};
|
|
928
938
|
}
|
|
929
939
|
|
|
930
940
|
// Provide status line preview for theme selection
|