topchester-ai 0.22.0 → 0.24.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/dist/cli.mjs +191 -43
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/topchester-config/SKILL.md +4 -2
package/dist/cli.mjs
CHANGED
|
@@ -98,7 +98,7 @@ async function enqueueFileMutation(path, mutate) {
|
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
100
|
//#region src/agent/instructions.ts
|
|
101
|
-
const PROJECT_INSTRUCTION_FILENAMES = ["AGENTS.
|
|
101
|
+
const PROJECT_INSTRUCTION_FILENAMES = ["AGENTS.md", "AGENTS.override.md"];
|
|
102
102
|
async function resolveProjectInstructions(workspaceRoot, options = {}) {
|
|
103
103
|
const filenames = getProjectInstructionFilenames(options);
|
|
104
104
|
if (filenames.length === 0) return {
|
|
@@ -114,21 +114,23 @@ async function resolveProjectInstructions(workspaceRoot, options = {}) {
|
|
|
114
114
|
const sources = [];
|
|
115
115
|
let remainingBytes = Math.max(0, maxTotalBytes);
|
|
116
116
|
for (const directory of directories) {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
const candidates = await readInstructionCandidates(resolvedWorkspace, directory, filenames, options.logger);
|
|
118
|
+
if (candidates.length === 0) continue;
|
|
119
|
+
for (const candidate of candidates) {
|
|
120
|
+
const scopedContentLimit = Math.min(Math.max(0, maxBytesPerFile), remainingBytes);
|
|
121
|
+
const content = truncateUtf8(candidate.content, scopedContentLimit);
|
|
122
|
+
const truncated = content !== candidate.content;
|
|
123
|
+
remainingBytes -= Buffer.byteLength(content, "utf8");
|
|
124
|
+
sources.push({
|
|
125
|
+
path: candidate.absolutePath,
|
|
126
|
+
relativePath: candidate.relativePath,
|
|
127
|
+
scopePath: formatScopePath(relative(resolvedWorkspace, directory)),
|
|
128
|
+
depth: getScopeDepth(relative(resolvedWorkspace, directory)),
|
|
129
|
+
bytes: candidate.bytes,
|
|
130
|
+
truncated,
|
|
131
|
+
content
|
|
132
|
+
});
|
|
133
|
+
}
|
|
132
134
|
}
|
|
133
135
|
options.logger?.debug({
|
|
134
136
|
event: "project_instructions_resolved",
|
|
@@ -212,7 +214,8 @@ function getInstructionDirectories(resolvedWorkspace, targetDirectory) {
|
|
|
212
214
|
}
|
|
213
215
|
return directories;
|
|
214
216
|
}
|
|
215
|
-
async function
|
|
217
|
+
async function readInstructionCandidates(resolvedWorkspace, directory, filenames, logger) {
|
|
218
|
+
const candidates = [];
|
|
216
219
|
for (const filename of filenames) {
|
|
217
220
|
const absolutePath = resolve(directory, filename);
|
|
218
221
|
const relativePath = formatRelativePath(relative(resolvedWorkspace, absolutePath));
|
|
@@ -235,12 +238,12 @@ async function readFirstInstructionCandidate(resolvedWorkspace, directory, filen
|
|
|
235
238
|
}, "project instruction skipped");
|
|
236
239
|
continue;
|
|
237
240
|
}
|
|
238
|
-
|
|
241
|
+
candidates.push({
|
|
239
242
|
absolutePath,
|
|
240
243
|
relativePath,
|
|
241
244
|
content,
|
|
242
245
|
bytes: bytes.byteLength
|
|
243
|
-
};
|
|
246
|
+
});
|
|
244
247
|
} catch (error) {
|
|
245
248
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
246
249
|
if (code === "ENOENT" || code === "ENOTDIR") continue;
|
|
@@ -252,6 +255,7 @@ async function readFirstInstructionCandidate(resolvedWorkspace, directory, filen
|
|
|
252
255
|
}, "project instruction skipped");
|
|
253
256
|
}
|
|
254
257
|
}
|
|
258
|
+
return candidates;
|
|
255
259
|
}
|
|
256
260
|
function truncateUtf8(content, maxBytes) {
|
|
257
261
|
if (maxBytes <= 0) return "";
|
|
@@ -5269,6 +5273,7 @@ const hookHandlerSchema = z.object({
|
|
|
5269
5273
|
type: z.literal("command").optional(),
|
|
5270
5274
|
command: z.string().min(1),
|
|
5271
5275
|
timeoutMs: hookTimeoutMsSchema.optional(),
|
|
5276
|
+
statusMessage: z.string().min(1).optional(),
|
|
5272
5277
|
matcher: hookMatcherSchema
|
|
5273
5278
|
}).strict();
|
|
5274
5279
|
const canonicalHooksConfigSchema = z.object({
|
|
@@ -7563,6 +7568,14 @@ function toolCallMessage(call, label, resultSummary) {
|
|
|
7563
7568
|
resultSummary
|
|
7564
7569
|
};
|
|
7565
7570
|
}
|
|
7571
|
+
function hookStatusMessage(label, eventName, statusMessage) {
|
|
7572
|
+
return {
|
|
7573
|
+
kind: "hook_status",
|
|
7574
|
+
label,
|
|
7575
|
+
...eventName === void 0 ? {} : { eventName },
|
|
7576
|
+
...statusMessage === void 0 ? {} : { statusMessage }
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7566
7579
|
function subagentMessage(message) {
|
|
7567
7580
|
return {
|
|
7568
7581
|
kind: "subagent",
|
|
@@ -7579,6 +7592,7 @@ const DEFAULT_MODAL_VISIBLE_ACTION_LIMIT = 16;
|
|
|
7579
7592
|
function renderChatMessage(message, options = {}) {
|
|
7580
7593
|
if (message.kind === "modal") return renderChatModal(message, options.selectedActionIndex, options.maxModalHeight);
|
|
7581
7594
|
if (message.kind === "tool_call") return renderToolCallMessage(message);
|
|
7595
|
+
if (message.kind === "hook_status") return renderHookStatusMessage(message);
|
|
7582
7596
|
if (message.kind === "subagent") return renderSubagentMessage(message);
|
|
7583
7597
|
if (message.kind === "thinking") return message.text.split("\n").map((line) => ui.muted(line));
|
|
7584
7598
|
if (message.text.length === 0) return [""];
|
|
@@ -7613,6 +7627,9 @@ function renderToolCallMessage(message) {
|
|
|
7613
7627
|
const visibleLabel = message.resultSummary && !message.label.includes(message.resultSummary) ? `${message.label} ${message.resultSummary}` : message.label;
|
|
7614
7628
|
return [` ${ui.muted(expandTabs(visibleLabel))}`];
|
|
7615
7629
|
}
|
|
7630
|
+
function renderHookStatusMessage(message) {
|
|
7631
|
+
return [` ${ui.muted(expandTabs(message.label))}`];
|
|
7632
|
+
}
|
|
7616
7633
|
function renderSubagentMessage(message) {
|
|
7617
7634
|
const label = message.title ?? shortSessionId(message.sessionId);
|
|
7618
7635
|
switch (message.status) {
|
|
@@ -7770,6 +7787,12 @@ const toolCallPayloadSchema = z.object({
|
|
|
7770
7787
|
label: z.string(),
|
|
7771
7788
|
call: z.record(z.string(), jsonValueSchema)
|
|
7772
7789
|
});
|
|
7790
|
+
const hookStatusPayloadSchema = z.object({
|
|
7791
|
+
kind: z.literal("hook_status"),
|
|
7792
|
+
eventName: z.string(),
|
|
7793
|
+
statusMessage: z.string(),
|
|
7794
|
+
label: z.string()
|
|
7795
|
+
});
|
|
7773
7796
|
const taskPlanItemPayloadSchema = z.object({
|
|
7774
7797
|
text: z.string(),
|
|
7775
7798
|
status: z.enum([
|
|
@@ -7836,6 +7859,7 @@ const subagentFailedPayloadSchema = subagentLifecycleBasePayloadSchema.extend({
|
|
|
7836
7859
|
const sessionEventPayloadSchema = z.discriminatedUnion("kind", [
|
|
7837
7860
|
messagePayloadSchema,
|
|
7838
7861
|
toolCallPayloadSchema,
|
|
7862
|
+
hookStatusPayloadSchema,
|
|
7839
7863
|
taskPlanPayloadSchema,
|
|
7840
7864
|
instructionContextPayloadSchema,
|
|
7841
7865
|
statusPayloadSchema,
|
|
@@ -8008,6 +8032,9 @@ function rehydrateSession(events) {
|
|
|
8008
8032
|
case "tool_call":
|
|
8009
8033
|
messages.push(toolCallMessage(event.call, event.label));
|
|
8010
8034
|
break;
|
|
8035
|
+
case "hook_status":
|
|
8036
|
+
messages.push(hookStatusMessage(event.label, event.eventName, event.statusMessage));
|
|
8037
|
+
break;
|
|
8011
8038
|
case "task_plan":
|
|
8012
8039
|
taskPlan = {
|
|
8013
8040
|
items: event.items,
|
|
@@ -8634,6 +8661,14 @@ const agentEvent = {
|
|
|
8634
8661
|
label
|
|
8635
8662
|
};
|
|
8636
8663
|
},
|
|
8664
|
+
hookStatus(eventName, statusMessage) {
|
|
8665
|
+
return {
|
|
8666
|
+
type: "hook_status",
|
|
8667
|
+
eventName,
|
|
8668
|
+
statusMessage,
|
|
8669
|
+
label: `🪝 hook>${formatHookEventName(eventName)}: ${statusMessage}`
|
|
8670
|
+
};
|
|
8671
|
+
},
|
|
8637
8672
|
taskPlan(plan) {
|
|
8638
8673
|
return {
|
|
8639
8674
|
type: "task_plan",
|
|
@@ -8694,6 +8729,9 @@ function choiceAction(label, value) {
|
|
|
8694
8729
|
value
|
|
8695
8730
|
};
|
|
8696
8731
|
}
|
|
8732
|
+
function formatHookEventName(eventName) {
|
|
8733
|
+
return eventName.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
|
|
8734
|
+
}
|
|
8697
8735
|
//#endregion
|
|
8698
8736
|
//#region src/tui/keys.ts
|
|
8699
8737
|
function isUpKey(data) {
|
|
@@ -8783,6 +8821,7 @@ var PromptHistory = class {
|
|
|
8783
8821
|
};
|
|
8784
8822
|
//#endregion
|
|
8785
8823
|
//#region src/tui/status.ts
|
|
8824
|
+
const STARTUP_PROMPT_HINT = "Prompt hint: Enter sends, Shift+Enter adds a line, / opens commands, ↑↓ browse history.";
|
|
8786
8825
|
function getStartupThreadMessages(context) {
|
|
8787
8826
|
const assignments = context.config.models?.assignments ?? {};
|
|
8788
8827
|
const providers = context.config.models?.providers ?? {};
|
|
@@ -8932,6 +8971,7 @@ var ChatLayout = class {
|
|
|
8932
8971
|
promptCursor = 0;
|
|
8933
8972
|
status = "ready";
|
|
8934
8973
|
knowledgeStatus;
|
|
8974
|
+
startupHintLine;
|
|
8935
8975
|
ephemeralLine;
|
|
8936
8976
|
taskPlanNoticeLine;
|
|
8937
8977
|
noticeLine;
|
|
@@ -8993,6 +9033,9 @@ var ChatLayout = class {
|
|
|
8993
9033
|
isReady() {
|
|
8994
9034
|
return this.status === "ready";
|
|
8995
9035
|
}
|
|
9036
|
+
setStartupHintLine(line) {
|
|
9037
|
+
this.startupHintLine = line;
|
|
9038
|
+
}
|
|
8996
9039
|
setEphemeralLine(line) {
|
|
8997
9040
|
this.ephemeralLine = line;
|
|
8998
9041
|
}
|
|
@@ -9026,6 +9069,7 @@ var ChatLayout = class {
|
|
|
9026
9069
|
this.promptCursor = 0;
|
|
9027
9070
|
this.status = "ready";
|
|
9028
9071
|
this.knowledgeStatus = void 0;
|
|
9072
|
+
this.startupHintLine = void 0;
|
|
9029
9073
|
this.ephemeralLine = void 0;
|
|
9030
9074
|
this.taskPlanNoticeLine = void 0;
|
|
9031
9075
|
this.noticeLine = void 0;
|
|
@@ -9056,6 +9100,7 @@ var ChatLayout = class {
|
|
|
9056
9100
|
case "system":
|
|
9057
9101
|
case "thinking":
|
|
9058
9102
|
case "tool_call":
|
|
9103
|
+
case "hook_status":
|
|
9059
9104
|
case "subagent":
|
|
9060
9105
|
case "modal": return [];
|
|
9061
9106
|
}
|
|
@@ -9110,6 +9155,7 @@ var ChatLayout = class {
|
|
|
9110
9155
|
const spacer = index === this.messages.length - 1 ? [] : [padThreadLine("", width)];
|
|
9111
9156
|
return [...this.renderThreadMessageLines(messageLines, innerWidth, width, message.kind === "user"), ...spacer];
|
|
9112
9157
|
});
|
|
9158
|
+
if (this.startupHintLine) lines.push(...this.renderThreadMessageLines([` ${ui.muted(this.startupHintLine)}`], innerWidth, width, false));
|
|
9113
9159
|
if (this.ephemeralLine) lines.push(...this.renderThreadMessageLines([` ${this.ephemeralLine}`], innerWidth, width, false));
|
|
9114
9160
|
if (this.taskPlanNoticeLine) lines.push(...this.renderThreadMessageLines([` ${this.taskPlanNoticeLine}`], innerWidth, width, false));
|
|
9115
9161
|
if (this.noticeLine) lines.push(...this.renderThreadMessageLines([` ${this.noticeLine}`], innerWidth, width, false));
|
|
@@ -9123,6 +9169,7 @@ var ChatLayout = class {
|
|
|
9123
9169
|
});
|
|
9124
9170
|
}
|
|
9125
9171
|
renderPrompt(width) {
|
|
9172
|
+
const slashSuggestions = this.getSlashSuggestions();
|
|
9126
9173
|
const top = `┌${"─".repeat(Math.max(0, width - 2))}┐`;
|
|
9127
9174
|
const bottom = `└${"─".repeat(Math.max(0, width - 2))}┘`;
|
|
9128
9175
|
const prefix = "> ";
|
|
@@ -9131,7 +9178,7 @@ var ChatLayout = class {
|
|
|
9131
9178
|
const statusInnerWidth = Math.max(1, width - 2);
|
|
9132
9179
|
const status = truncateToWidth(` ${formatStatusLine(this.folderName, this.modelLabel, this.status, this.knowledgeStatus, statusInnerWidth)} `, width, "…", true);
|
|
9133
9180
|
return [
|
|
9134
|
-
...this.renderSlashSuggestions(width),
|
|
9181
|
+
...this.renderSlashSuggestions(width, slashSuggestions),
|
|
9135
9182
|
...this.renderTaskPlan(width),
|
|
9136
9183
|
top,
|
|
9137
9184
|
...inputLines.map((line, index) => `│ ${index === 0 ? prefix : " "}${padPromptInputLine(line, innerWidth)} │`),
|
|
@@ -9190,8 +9237,7 @@ var ChatLayout = class {
|
|
|
9190
9237
|
if (!this.taskPlan) return [];
|
|
9191
9238
|
return formatTaskPlanForTui(this.taskPlan, Math.max(1, width));
|
|
9192
9239
|
}
|
|
9193
|
-
renderSlashSuggestions(width) {
|
|
9194
|
-
const suggestions = this.getSlashSuggestions();
|
|
9240
|
+
renderSlashSuggestions(width, suggestions = this.getSlashSuggestions()) {
|
|
9195
9241
|
if (suggestions.length === 0 || this.promptHint) return [];
|
|
9196
9242
|
this.activeSlashSuggestionIndex = Math.min(this.activeSlashSuggestionIndex, suggestions.length - 1);
|
|
9197
9243
|
const innerWidth = Math.max(1, width - 4);
|
|
@@ -9542,6 +9588,8 @@ var ChatLayout = class {
|
|
|
9542
9588
|
}
|
|
9543
9589
|
submitUserInput(message) {
|
|
9544
9590
|
this.setTaskPlanNotice(void 0);
|
|
9591
|
+
this.setStartupHintLine(void 0);
|
|
9592
|
+
this.setEphemeralLine(void 0);
|
|
9545
9593
|
this.promptHistory.add(message);
|
|
9546
9594
|
if (message.startsWith("/")) this.submitCommand?.(message);
|
|
9547
9595
|
else this.submitMessage?.(message);
|
|
@@ -9625,6 +9673,11 @@ async function runTopchesterHooks(context, event, payload, options = {}) {
|
|
|
9625
9673
|
};
|
|
9626
9674
|
for (const handler of handlers) {
|
|
9627
9675
|
result.handlerCount += 1;
|
|
9676
|
+
const statusMessage = handler.statusMessage?.trim();
|
|
9677
|
+
if (statusMessage) options.onHookStart?.({
|
|
9678
|
+
event,
|
|
9679
|
+
statusMessage
|
|
9680
|
+
});
|
|
9628
9681
|
const handlerResult = await runCommandHandler(context, event, payload, handler, options);
|
|
9629
9682
|
result.contexts.push(...handlerResult.contexts);
|
|
9630
9683
|
result.messages.push(...handlerResult.messages);
|
|
@@ -10197,6 +10250,12 @@ function runtimeEventToSessionPayload(event) {
|
|
|
10197
10250
|
label: event.label,
|
|
10198
10251
|
call: event.call
|
|
10199
10252
|
};
|
|
10253
|
+
case "hook_status": return {
|
|
10254
|
+
kind: "hook_status",
|
|
10255
|
+
eventName: event.eventName,
|
|
10256
|
+
statusMessage: event.statusMessage,
|
|
10257
|
+
label: event.label
|
|
10258
|
+
};
|
|
10200
10259
|
case "task_plan": return {
|
|
10201
10260
|
kind: "task_plan",
|
|
10202
10261
|
items: event.plan.items,
|
|
@@ -10838,15 +10897,27 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
10838
10897
|
const sessionKey = session?.sessionId ?? `workspace:${this.context.workspaceRoot}`;
|
|
10839
10898
|
if (this.startedHookSessionKeys.has(sessionKey)) return [];
|
|
10840
10899
|
this.startedHookSessionKeys.add(sessionKey);
|
|
10900
|
+
const startedEvents = [];
|
|
10841
10901
|
const result = await this.runHookEvent("SessionStart", this.createBaseHookPayload("SessionStart", session, {
|
|
10842
10902
|
isResumed: Boolean(options.isResumed),
|
|
10843
10903
|
taskStartAlias: "TaskStart"
|
|
10844
|
-
}), {
|
|
10845
|
-
|
|
10904
|
+
}), {
|
|
10905
|
+
abortSignal: options.abortSignal,
|
|
10906
|
+
onHookStart: (status) => {
|
|
10907
|
+
startedEvents.push(agentEvent.hookStatus(status.event, status.statusMessage));
|
|
10908
|
+
}
|
|
10909
|
+
});
|
|
10910
|
+
return [...startedEvents, ...this.hookResultToEvents(result)];
|
|
10846
10911
|
}
|
|
10847
10912
|
async runPreCompactHooks(session, options = {}) {
|
|
10848
|
-
const
|
|
10849
|
-
|
|
10913
|
+
const startedEvents = [];
|
|
10914
|
+
const result = await this.runHookEvent("PreCompact", this.createBaseHookPayload("PreCompact", session, { reason: options.reason ?? "Compaction is about to start." }), {
|
|
10915
|
+
abortSignal: options.abortSignal,
|
|
10916
|
+
onHookStart: (status) => {
|
|
10917
|
+
startedEvents.push(agentEvent.hookStatus(status.event, status.statusMessage));
|
|
10918
|
+
}
|
|
10919
|
+
});
|
|
10920
|
+
return [...startedEvents, ...this.hookResultToEvents(result)];
|
|
10850
10921
|
}
|
|
10851
10922
|
/**
|
|
10852
10923
|
* Streams one user chat turn through the agent loop. It builds the model
|
|
@@ -10862,11 +10933,13 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
10862
10933
|
async *submitMessageStream(conversation, message, abortSignal, options = {}) {
|
|
10863
10934
|
const session = options.session ?? this.options.session;
|
|
10864
10935
|
for (const event of await this.runSessionStartHooks(session, { abortSignal })) yield event;
|
|
10865
|
-
const
|
|
10936
|
+
const userPromptHookRun = this.startHookEvent("UserPromptSubmit", this.createBaseHookPayload("UserPromptSubmit", session, {
|
|
10866
10937
|
prompt: { text: message },
|
|
10867
10938
|
prompt_text: message,
|
|
10868
10939
|
user_prompt: message
|
|
10869
10940
|
}), { abortSignal });
|
|
10941
|
+
for await (const event of userPromptHookRun.statusEvents) yield event;
|
|
10942
|
+
const userPromptHook = await userPromptHookRun.result;
|
|
10870
10943
|
for (const event of this.hookResultToEvents(userPromptHook)) yield event;
|
|
10871
10944
|
if (userPromptHook.blocked || userPromptHook.stopped) {
|
|
10872
10945
|
const interruption = userPromptHook.blocked ?? userPromptHook.stopped;
|
|
@@ -10995,7 +11068,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
10995
11068
|
}
|
|
10996
11069
|
const finalMessage = finalText.trim() || "I got an empty response from the model.";
|
|
10997
11070
|
yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(result.modelId, totalDurationMs, tokenUsageTotals));
|
|
10998
|
-
for (const event of
|
|
11071
|
+
for await (const event of this.streamStopHookEvents(session, finalMessage, "completed", abortSignal)) yield event;
|
|
10999
11072
|
yield agentEvent.status("ready");
|
|
11000
11073
|
return;
|
|
11001
11074
|
}
|
|
@@ -11019,7 +11092,9 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11019
11092
|
for (let batchIndex = 0; batchIndex < batch.length; batchIndex += 1) {
|
|
11020
11093
|
const call = batch[batchIndex];
|
|
11021
11094
|
const resultIndex = index + batchIndex;
|
|
11022
|
-
const
|
|
11095
|
+
const preHookRun = this.startPreToolUseHook(call, modelToolCalls[resultIndex]?.id, session, abortSignal);
|
|
11096
|
+
for await (const event of preHookRun.statusEvents) yield event;
|
|
11097
|
+
const preHook = await preHookRun.result;
|
|
11023
11098
|
for (const event of this.hookResultToEvents(preHook)) yield event;
|
|
11024
11099
|
if (preHook.stopped) {
|
|
11025
11100
|
if (preHook.messages.length === 0) yield agentEvent.systemMessage(preHook.stopped.message);
|
|
@@ -11065,7 +11140,9 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11065
11140
|
const toolResult = taskResults[index];
|
|
11066
11141
|
for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
|
|
11067
11142
|
yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
|
|
11068
|
-
const
|
|
11143
|
+
const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
|
|
11144
|
+
for await (const event of postHookRun.statusEvents) yield event;
|
|
11145
|
+
const postHook = await postHookRun.result;
|
|
11069
11146
|
for (const event of this.hookResultToEvents(postHook)) yield event;
|
|
11070
11147
|
postHookContexts.push(...postHook.contexts);
|
|
11071
11148
|
if (postHook.stopped) {
|
|
@@ -11085,7 +11162,9 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11085
11162
|
const postHookContexts = [];
|
|
11086
11163
|
for (let index = 0; index < parallelCalls.length; index += 1) {
|
|
11087
11164
|
const call = parallelCalls[index];
|
|
11088
|
-
const
|
|
11165
|
+
const preHookRun = this.startPreToolUseHook(call, modelToolCalls[index]?.id, session, abortSignal);
|
|
11166
|
+
for await (const event of preHookRun.statusEvents) yield event;
|
|
11167
|
+
const preHook = await preHookRun.result;
|
|
11089
11168
|
for (const event of this.hookResultToEvents(preHook)) yield event;
|
|
11090
11169
|
if (preHook.stopped) {
|
|
11091
11170
|
if (preHook.messages.length === 0) yield agentEvent.systemMessage(preHook.stopped.message);
|
|
@@ -11123,7 +11202,9 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11123
11202
|
const toolResult = parallelResults[index];
|
|
11124
11203
|
for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
|
|
11125
11204
|
yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
|
|
11126
|
-
const
|
|
11205
|
+
const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
|
|
11206
|
+
for await (const event of postHookRun.statusEvents) yield event;
|
|
11207
|
+
const postHook = await postHookRun.result;
|
|
11127
11208
|
for (const event of this.hookResultToEvents(postHook)) yield event;
|
|
11128
11209
|
postHookContexts.push(...postHook.contexts);
|
|
11129
11210
|
if (postHook.stopped) {
|
|
@@ -11141,11 +11222,13 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11141
11222
|
if (suppressiblePlanTodoAnswer !== void 0) {
|
|
11142
11223
|
const finalMessage = suppressiblePlanTodoAnswer || "I got an empty response from the model.";
|
|
11143
11224
|
yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(result.modelId, totalDurationMs, tokenUsageTotals));
|
|
11144
|
-
for (const event of
|
|
11225
|
+
for await (const event of this.streamStopHookEvents(session, finalMessage, "completed", abortSignal)) yield event;
|
|
11145
11226
|
yield agentEvent.status("ready");
|
|
11146
11227
|
return;
|
|
11147
11228
|
}
|
|
11148
|
-
const
|
|
11229
|
+
const preHookRun = this.startPreToolUseHook(executableToolCall, toolCall.id, session, abortSignal);
|
|
11230
|
+
for await (const event of preHookRun.statusEvents) yield event;
|
|
11231
|
+
const preHook = await preHookRun.result;
|
|
11149
11232
|
for (const event of this.hookResultToEvents(preHook)) yield event;
|
|
11150
11233
|
let toolResult;
|
|
11151
11234
|
if (preHook.stopped) {
|
|
@@ -11183,7 +11266,9 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11183
11266
|
for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
|
|
11184
11267
|
yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult));
|
|
11185
11268
|
if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") yield agentEvent.taskPlan(toolResult.plan);
|
|
11186
|
-
const
|
|
11269
|
+
const postHookRun = this.startPostToolUseHook(executableToolCall, toolCall.id, toolResult, session, abortSignal);
|
|
11270
|
+
for await (const event of postHookRun.statusEvents) yield event;
|
|
11271
|
+
const postHook = await postHookRun.result;
|
|
11187
11272
|
for (const event of this.hookResultToEvents(postHook)) yield event;
|
|
11188
11273
|
if (postHook.stopped) {
|
|
11189
11274
|
if (postHook.messages.length === 0) yield agentEvent.systemMessage(postHook.stopped.message);
|
|
@@ -11195,7 +11280,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11195
11280
|
}
|
|
11196
11281
|
const finalMessage = "I stopped because the tool loop ended unexpectedly.";
|
|
11197
11282
|
yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(lastModelId, totalDurationMs, tokenUsageTotals));
|
|
11198
|
-
for (const event of
|
|
11283
|
+
for await (const event of this.streamStopHookEvents(session, finalMessage, "failed", abortSignal)) yield event;
|
|
11199
11284
|
yield agentEvent.status("ready");
|
|
11200
11285
|
}
|
|
11201
11286
|
/**
|
|
@@ -11210,29 +11295,45 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11210
11295
|
}
|
|
11211
11296
|
return events;
|
|
11212
11297
|
}
|
|
11213
|
-
|
|
11214
|
-
return this.
|
|
11298
|
+
startPreToolUseHook(call, toolCallId, session, abortSignal) {
|
|
11299
|
+
return this.startHookEvent("PreToolUse", this.createToolHookPayload("PreToolUse", call, toolCallId, session), {
|
|
11215
11300
|
toolName: call.tool,
|
|
11216
11301
|
abortSignal
|
|
11217
11302
|
});
|
|
11218
11303
|
}
|
|
11219
|
-
|
|
11220
|
-
return this.
|
|
11304
|
+
startPostToolUseHook(call, toolCallId, result, session, abortSignal) {
|
|
11305
|
+
return this.startHookEvent("PostToolUse", this.createToolHookPayload("PostToolUse", call, toolCallId, session, { result }), {
|
|
11221
11306
|
toolName: call.tool,
|
|
11222
11307
|
abortSignal
|
|
11223
11308
|
});
|
|
11224
11309
|
}
|
|
11225
|
-
async
|
|
11226
|
-
const
|
|
11310
|
+
async *streamStopHookEvents(session, finalMessage, status, abortSignal) {
|
|
11311
|
+
const hookRun = this.startHookEvent("Stop", this.createBaseHookPayload("Stop", session, {
|
|
11227
11312
|
taskCompleteAlias: "TaskComplete",
|
|
11228
11313
|
finalMessage,
|
|
11229
11314
|
status
|
|
11230
11315
|
}), { abortSignal });
|
|
11231
|
-
|
|
11316
|
+
for await (const event of hookRun.statusEvents) yield event;
|
|
11317
|
+
const result = await hookRun.result;
|
|
11318
|
+
for (const event of this.hookResultToEvents(result)) yield event;
|
|
11232
11319
|
}
|
|
11233
11320
|
async runHookEvent(event, payload, options = {}) {
|
|
11234
11321
|
return runTopchesterHooks(this.context, event, payload, options);
|
|
11235
11322
|
}
|
|
11323
|
+
startHookEvent(event, payload, options = {}) {
|
|
11324
|
+
const queue = createRuntimeEventQueue();
|
|
11325
|
+
return {
|
|
11326
|
+
statusEvents: queue,
|
|
11327
|
+
result: this.runHookEvent(event, payload, {
|
|
11328
|
+
...options,
|
|
11329
|
+
onHookStart: (status) => {
|
|
11330
|
+
queue.push(agentEvent.hookStatus(status.event, status.statusMessage));
|
|
11331
|
+
}
|
|
11332
|
+
}).finally(() => {
|
|
11333
|
+
queue.close();
|
|
11334
|
+
})
|
|
11335
|
+
};
|
|
11336
|
+
}
|
|
11236
11337
|
createBaseHookPayload(event, session, extra = {}) {
|
|
11237
11338
|
return {
|
|
11238
11339
|
hook_event_name: event,
|
|
@@ -11240,6 +11341,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11240
11341
|
cwd: this.context.workspaceRoot,
|
|
11241
11342
|
workspaceRoot: this.context.workspaceRoot,
|
|
11242
11343
|
source: "topchester",
|
|
11344
|
+
...this.createHookModelPayload("agent.primary"),
|
|
11243
11345
|
...session ? {
|
|
11244
11346
|
session_id: session.sessionId,
|
|
11245
11347
|
sessionId: session.sessionId,
|
|
@@ -11253,6 +11355,33 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
|
|
|
11253
11355
|
...extra
|
|
11254
11356
|
};
|
|
11255
11357
|
}
|
|
11358
|
+
createHookModelPayload(purpose) {
|
|
11359
|
+
const resolveModel = this.context.modelGateway.resolveModel;
|
|
11360
|
+
if (typeof resolveModel !== "function") return {};
|
|
11361
|
+
try {
|
|
11362
|
+
const resolved = resolveModel.call(this.context.modelGateway, purpose);
|
|
11363
|
+
const modelRef = `${resolved.providerId}/${resolved.modelId}`;
|
|
11364
|
+
return {
|
|
11365
|
+
model_purpose: resolved.purpose,
|
|
11366
|
+
model_provider: resolved.providerId,
|
|
11367
|
+
model_id: resolved.modelId,
|
|
11368
|
+
model_ref: modelRef,
|
|
11369
|
+
model: {
|
|
11370
|
+
purpose: resolved.purpose,
|
|
11371
|
+
providerId: resolved.providerId,
|
|
11372
|
+
modelId: resolved.modelId,
|
|
11373
|
+
ref: modelRef
|
|
11374
|
+
}
|
|
11375
|
+
};
|
|
11376
|
+
} catch (error) {
|
|
11377
|
+
this.context.logger.debug({
|
|
11378
|
+
event: "hook_model_resolution_skipped",
|
|
11379
|
+
purpose,
|
|
11380
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11381
|
+
}, "hook model metadata unavailable");
|
|
11382
|
+
return {};
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11256
11385
|
createToolHookPayload(event, call, toolCallId, session, extra = {}) {
|
|
11257
11386
|
return this.createBaseHookPayload(event, session, {
|
|
11258
11387
|
tool_name: call.tool,
|
|
@@ -11481,6 +11610,7 @@ function renderRuntimeEvent(event) {
|
|
|
11481
11610
|
switch (event.type) {
|
|
11482
11611
|
case "message": return [event.role === "assistant" ? agentMessage(event.text, event.meta) : systemMessage(event.text)];
|
|
11483
11612
|
case "tool_call": return [toolCallMessage(event.call, event.label)];
|
|
11613
|
+
case "hook_status": return [hookStatusMessage(event.label, event.eventName, event.statusMessage)];
|
|
11484
11614
|
case "knowledge_status": return [systemMessage([`KB status: ${formatKnowledgePathStatus(event.status)}${formatKbPathSource(event.status)}`, event.guidance].filter(Boolean).join("\n"))];
|
|
11485
11615
|
case "choice": return [modalMessage({
|
|
11486
11616
|
tone: event.tone,
|
|
@@ -11520,6 +11650,11 @@ function formatForwardedSubagentEvent(sessionId, event) {
|
|
|
11520
11650
|
sessionId,
|
|
11521
11651
|
text: event.label
|
|
11522
11652
|
})];
|
|
11653
|
+
if (event.type === "hook_status") return [subagentMessage({
|
|
11654
|
+
status: "event",
|
|
11655
|
+
sessionId,
|
|
11656
|
+
text: event.label
|
|
11657
|
+
})];
|
|
11523
11658
|
return [];
|
|
11524
11659
|
}
|
|
11525
11660
|
function formatKbPathSource(status) {
|
|
@@ -11718,6 +11853,12 @@ function chatMessageToSessionPayload(message) {
|
|
|
11718
11853
|
};
|
|
11719
11854
|
if (message.kind === "thinking") return;
|
|
11720
11855
|
if (message.kind === "subagent") return;
|
|
11856
|
+
if (message.kind === "hook_status") return message.eventName && message.statusMessage ? {
|
|
11857
|
+
kind: "hook_status",
|
|
11858
|
+
eventName: message.eventName,
|
|
11859
|
+
statusMessage: message.statusMessage,
|
|
11860
|
+
label: message.label
|
|
11861
|
+
} : void 0;
|
|
11721
11862
|
if (message.kind === "modal") return {
|
|
11722
11863
|
kind: "choice",
|
|
11723
11864
|
tone: message.tone,
|
|
@@ -11969,6 +12110,7 @@ var TopchesterTuiShell = class {
|
|
|
11969
12110
|
}
|
|
11970
12111
|
});
|
|
11971
12112
|
app.setTaskPlan(this.options.initialTaskPlan);
|
|
12113
|
+
if (!isResumed) app.setStartupHintLine(STARTUP_PROMPT_HINT);
|
|
11972
12114
|
app.setSubmitMessage((message) => {
|
|
11973
12115
|
this.startBackgroundTask(app, tui, "Chat", () => this.submitChatMessage(app, tui, message));
|
|
11974
12116
|
});
|
|
@@ -12580,6 +12722,7 @@ var TopchesterTuiShell = class {
|
|
|
12580
12722
|
await this.appendStartupRuntimeEvents(session, messages, await this.runtime.runSessionStartHooks?.(session, { isResumed: false }) ?? []);
|
|
12581
12723
|
await this.appendStartupRuntimeEvents(session, messages, await this.runtime.checkProjectInstructions?.() ?? []);
|
|
12582
12724
|
app.resetForNewSession(messages);
|
|
12725
|
+
app.setStartupHintLine(STARTUP_PROMPT_HINT);
|
|
12583
12726
|
tui.requestRender();
|
|
12584
12727
|
await this.checkAgent(app, tui);
|
|
12585
12728
|
}
|
|
@@ -12787,6 +12930,7 @@ async function loadConversation(workspaceRoot, resume) {
|
|
|
12787
12930
|
case "system":
|
|
12788
12931
|
case "thinking":
|
|
12789
12932
|
case "tool_call":
|
|
12933
|
+
case "hook_status":
|
|
12790
12934
|
case "subagent":
|
|
12791
12935
|
case "modal": return [];
|
|
12792
12936
|
}
|
|
@@ -12819,6 +12963,10 @@ function printPlainEvent(event) {
|
|
|
12819
12963
|
console.log(event.label);
|
|
12820
12964
|
return;
|
|
12821
12965
|
}
|
|
12966
|
+
if (event.type === "hook_status") {
|
|
12967
|
+
console.log(event.label);
|
|
12968
|
+
return;
|
|
12969
|
+
}
|
|
12822
12970
|
if (event.type === "knowledge_status" && event.guidance) {
|
|
12823
12971
|
console.log(event.guidance);
|
|
12824
12972
|
return;
|