@mariozechner/pi-coding-agent 0.30.2 → 0.31.1
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 +251 -1
- package/README.md +105 -84
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts +3 -3
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +7 -10
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +73 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +464 -210
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -2
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +84 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/core/compaction/branch-summarization.js +233 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -0
- package/dist/core/{compaction.d.ts → compaction/compaction.d.ts} +38 -19
- package/dist/core/compaction/compaction.d.ts.map +1 -0
- package/dist/core/compaction/compaction.js +558 -0
- package/dist/core/compaction/compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +7 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/utils.d.ts +35 -0
- package/dist/core/compaction/utils.d.ts.map +1 -0
- package/dist/core/compaction/utils.js +138 -0
- package/dist/core/compaction/utils.js.map +1 -0
- package/dist/core/custom-tools/index.d.ts +2 -1
- package/dist/core/custom-tools/index.d.ts.map +1 -1
- package/dist/core/custom-tools/index.js +1 -0
- package/dist/core/custom-tools/index.js.map +1 -1
- package/dist/core/custom-tools/loader.d.ts.map +1 -1
- package/dist/core/custom-tools/loader.js +13 -80
- package/dist/core/custom-tools/loader.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +84 -59
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/custom-tools/wrapper.d.ts +15 -0
- package/dist/core/custom-tools/wrapper.d.ts.map +1 -0
- package/dist/core/custom-tools/wrapper.js +23 -0
- package/dist/core/custom-tools/wrapper.js.map +1 -0
- package/dist/core/exec.d.ts +29 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +71 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/export-html/index.d.ts +17 -0
- package/dist/core/export-html/index.d.ts.map +1 -0
- package/dist/core/export-html/index.js +171 -0
- package/dist/core/export-html/index.js.map +1 -0
- package/dist/core/export-html/template.css +781 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1185 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/hooks/index.d.ts +4 -4
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +4 -3
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/loader.d.ts +40 -5
- package/dist/core/hooks/loader.d.ts.map +1 -1
- package/dist/core/hooks/loader.js +43 -10
- package/dist/core/hooks/loader.js.map +1 -1
- package/dist/core/hooks/runner.d.ts +94 -18
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +199 -120
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
- package/dist/core/hooks/tool-wrapper.js +36 -19
- package/dist/core/hooks/tool-wrapper.js.map +1 -1
- package/dist/core/hooks/types.d.ts +407 -96
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +44 -12
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +82 -34
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -5
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +7 -7
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +7 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +45 -14
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +7 -10
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +88 -32
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +202 -36
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +565 -133
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +6 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +33 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +171 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +7 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +20 -95
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +22 -21
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +6 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.js +30 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.js +35 -0
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +36 -0
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +5 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +5 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +12 -6
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +57 -25
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/hook-editor.d.ts +15 -0
- package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-editor.js +95 -0
- package/dist/modes/interactive/components/hook-editor.js.map +1 -0
- package/dist/modes/interactive/components/hook-message.d.ts +18 -0
- package/dist/modes/interactive/components/hook-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-message.js +80 -0
- package/dist/modes/interactive/components/hook-message.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +6 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +15 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +70 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/tree-selector.js +745 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +3 -3
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -5
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +29 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +589 -208
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +13 -1
- package/dist/modes/interactive/theme/light.json +13 -1
- package/dist/modes/interactive/theme/theme-schema.json +34 -0
- package/dist/modes/interactive/theme/theme.d.ts +20 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +135 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +3 -3
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +26 -20
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -10
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +11 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +88 -35
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +30 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts +4 -2
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +36 -7
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +388 -0
- package/docs/custom-tools.md +146 -43
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +562 -596
- package/docs/rpc.md +33 -19
- package/docs/sdk.md +93 -21
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +172 -21
- package/docs/skills.md +2 -0
- package/docs/theme.md +31 -2
- package/docs/tree.md +197 -0
- package/docs/tui.md +343 -0
- package/examples/README.md +1 -9
- package/examples/custom-tools/hello/index.ts +4 -3
- package/examples/custom-tools/question/index.ts +4 -4
- package/examples/custom-tools/subagent/index.ts +7 -6
- package/examples/custom-tools/todo/index.ts +11 -5
- package/examples/hooks/README.md +29 -71
- package/examples/hooks/auto-commit-on-exit.ts +8 -9
- package/examples/hooks/confirm-destructive.ts +29 -30
- package/examples/hooks/custom-compaction.ts +20 -21
- package/examples/hooks/dirty-repo-guard.ts +41 -40
- package/examples/hooks/file-trigger.ts +10 -5
- package/examples/hooks/git-checkpoint.ts +16 -12
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +1 -1
- package/examples/hooks/protected-paths.ts +1 -1
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +4 -4
- package/examples/sdk/06-hooks.ts +1 -1
- package/examples/sdk/07-context-files.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +6 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/10-settings.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/examples/sdk/12-full-control.ts +4 -7
- package/package.json +6 -6
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js +0 -412
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/export-html.d.ts +0 -23
- package/dist/core/export-html.d.ts.map +0 -1
- package/dist/core/export-html.js +0 -1185
- package/dist/core/export-html.js.map +0 -1
- package/dist/modes/interactive/components/compaction.d.ts +0 -15
- package/dist/modes/interactive/components/compaction.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction.js +0 -41
- package/dist/modes/interactive/components/compaction.js.map +0 -1
- package/docs/hooks-v2.md +0 -385
- package/docs/session-tree.md +0 -452
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
* Confirm Destructive Actions Hook
|
|
3
3
|
*
|
|
4
4
|
* Prompts for confirmation before destructive session actions (clear, switch, branch).
|
|
5
|
-
* Demonstrates how to cancel session events using the before_*
|
|
5
|
+
* Demonstrates how to cancel session events using the before_* events.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: HookAPI) {
|
|
11
|
-
pi.on("
|
|
12
|
-
|
|
13
|
-
if (event.reason === "before_new") {
|
|
14
|
-
if (!ctx.hasUI) return;
|
|
11
|
+
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
|
|
12
|
+
if (!ctx.hasUI) return;
|
|
15
13
|
|
|
14
|
+
if (event.reason === "new") {
|
|
16
15
|
const confirmed = await ctx.ui.confirm(
|
|
17
16
|
"Clear session?",
|
|
18
17
|
"This will delete all messages in the current session.",
|
|
@@ -22,39 +21,39 @@ export default function (pi: HookAPI) {
|
|
|
22
21
|
ctx.ui.notify("Clear cancelled", "info");
|
|
23
22
|
return { cancel: true };
|
|
24
23
|
}
|
|
24
|
+
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// reason === "resume" - check if there are unsaved changes (messages since last assistant response)
|
|
28
|
+
const entries = ctx.sessionManager.getEntries();
|
|
29
|
+
const hasUnsavedWork = entries.some(
|
|
30
|
+
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
|
|
31
|
+
);
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"Switch session?",
|
|
36
|
-
"You have messages in the current session. Switch anyway?",
|
|
37
|
-
);
|
|
33
|
+
if (hasUnsavedWork) {
|
|
34
|
+
const confirmed = await ctx.ui.confirm(
|
|
35
|
+
"Switch session?",
|
|
36
|
+
"You have messages in the current session. Switch anyway?",
|
|
37
|
+
);
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
39
|
+
if (!confirmed) {
|
|
40
|
+
ctx.ui.notify("Switch cancelled", "info");
|
|
41
|
+
return { cancel: true };
|
|
43
42
|
}
|
|
44
43
|
}
|
|
44
|
+
});
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
pi.on("session_before_branch", async (event, ctx) => {
|
|
47
|
+
if (!ctx.hasUI) return;
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const choice = await ctx.ui.select(`Branch from entry ${event.entryId.slice(0, 8)}?`, [
|
|
50
|
+
"Yes, create branch",
|
|
51
|
+
"No, stay in current session",
|
|
52
|
+
]);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
54
|
+
if (choice !== "Yes, create branch") {
|
|
55
|
+
ctx.ui.notify("Branch cancelled", "info");
|
|
56
|
+
return { cancel: true };
|
|
58
57
|
}
|
|
59
58
|
});
|
|
60
59
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Replaces the default compaction behavior with a full summary of the entire context.
|
|
5
5
|
* Instead of keeping the last 20k tokens of conversation turns, this hook:
|
|
6
|
-
* 1. Summarizes ALL messages (
|
|
6
|
+
* 1. Summarizes ALL messages (messagesToSummarize + turnPrefixMessages)
|
|
7
7
|
* 2. Discards all old turns completely, keeping only the summary
|
|
8
8
|
*
|
|
9
9
|
* This example also demonstrates using a different model (Gemini Flash) for summarization,
|
|
@@ -14,17 +14,15 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { complete, getModel } from "@mariozechner/pi-ai";
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
17
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
18
|
+
import { convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent";
|
|
19
19
|
|
|
20
20
|
export default function (pi: HookAPI) {
|
|
21
|
-
pi.on("
|
|
22
|
-
if (event.reason !== "before_compact") return;
|
|
23
|
-
|
|
21
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
24
22
|
ctx.ui.notify("Custom compaction hook triggered", "info");
|
|
25
23
|
|
|
26
|
-
const {
|
|
27
|
-
|
|
24
|
+
const { preparation, branchEntries: _, signal } = event;
|
|
25
|
+
const { messagesToSummarize, turnPrefixMessages, tokensBefore, firstKeptEntryId, previousSummary } = preparation;
|
|
28
26
|
|
|
29
27
|
// Use Gemini Flash for summarization (cheaper/faster than most conversation models)
|
|
30
28
|
const model = getModel("google", "gemini-2.5-flash");
|
|
@@ -34,35 +32,34 @@ export default function (pi: HookAPI) {
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
// Resolve API key for the summarization model
|
|
37
|
-
const apiKey = await
|
|
35
|
+
const apiKey = await ctx.modelRegistry.getApiKey(model);
|
|
38
36
|
if (!apiKey) {
|
|
39
37
|
ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
|
|
40
38
|
return;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
// Combine all messages for full summary
|
|
44
|
-
const allMessages = [...messagesToSummarize, ...
|
|
42
|
+
const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
|
|
45
43
|
|
|
46
44
|
ctx.ui.notify(
|
|
47
45
|
`Custom compaction: summarizing ${allMessages.length} messages (${tokensBefore.toLocaleString()} tokens) with ${model.id}...`,
|
|
48
46
|
"info",
|
|
49
47
|
);
|
|
50
48
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
49
|
+
// Convert messages to readable text format
|
|
50
|
+
const conversationText = serializeConversation(convertToLlm(allMessages));
|
|
53
51
|
|
|
54
52
|
// Include previous summary context if available
|
|
55
53
|
const previousContext = previousSummary ? `\n\nPrevious session summary for context:\n${previousSummary}` : "";
|
|
56
54
|
|
|
57
55
|
// Build messages that ask for a comprehensive summary
|
|
58
56
|
const summaryMessages = [
|
|
59
|
-
...transformedMessages,
|
|
60
57
|
{
|
|
61
58
|
role: "user" as const,
|
|
62
59
|
content: [
|
|
63
60
|
{
|
|
64
61
|
type: "text" as const,
|
|
65
|
-
text: `You are a conversation summarizer. Create a comprehensive summary of this
|
|
62
|
+
text: `You are a conversation summarizer. Create a comprehensive summary of this conversation that captures:${previousContext}
|
|
66
63
|
|
|
67
64
|
1. The main goals and objectives discussed
|
|
68
65
|
2. Key decisions made and their rationale
|
|
@@ -73,7 +70,11 @@ export default function (pi: HookAPI) {
|
|
|
73
70
|
|
|
74
71
|
Be thorough but concise. The summary will replace the ENTIRE conversation history, so include all information needed to continue the work effectively.
|
|
75
72
|
|
|
76
|
-
Format the summary as structured markdown with clear sections
|
|
73
|
+
Format the summary as structured markdown with clear sections.
|
|
74
|
+
|
|
75
|
+
<conversation>
|
|
76
|
+
${conversationText}
|
|
77
|
+
</conversation>`,
|
|
77
78
|
},
|
|
78
79
|
],
|
|
79
80
|
timestamp: Date.now(),
|
|
@@ -94,14 +95,12 @@ Format the summary as structured markdown with clear sections.`,
|
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
// Return
|
|
98
|
-
//
|
|
98
|
+
// Return compaction content - SessionManager adds id/parentId
|
|
99
|
+
// Use firstKeptEntryId from preparation to keep recent messages
|
|
99
100
|
return {
|
|
100
|
-
|
|
101
|
-
type: "compaction" as const,
|
|
102
|
-
timestamp: new Date().toISOString(),
|
|
101
|
+
compaction: {
|
|
103
102
|
summary,
|
|
104
|
-
|
|
103
|
+
firstKeptEntryId,
|
|
105
104
|
tokensBefore,
|
|
106
105
|
},
|
|
107
106
|
};
|
|
@@ -5,47 +5,48 @@
|
|
|
5
5
|
* Useful to ensure work is committed before switching context.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI, HookContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
async function checkDirtyRepo(pi: HookAPI, ctx: HookContext, action: string): Promise<{ cancel: boolean } | undefined> {
|
|
11
|
+
// Check for uncommitted changes
|
|
12
|
+
const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
|
|
13
|
+
|
|
14
|
+
if (code !== 0) {
|
|
15
|
+
// Not a git repo, allow the action
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const hasChanges = stdout.trim().length > 0;
|
|
20
|
+
if (!hasChanges) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!ctx.hasUI) {
|
|
25
|
+
// In non-interactive mode, block by default
|
|
26
|
+
return { cancel: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Count changed files
|
|
30
|
+
const changedFiles = stdout.trim().split("\n").filter(Boolean).length;
|
|
31
|
+
|
|
32
|
+
const choice = await ctx.ui.select(`You have ${changedFiles} uncommitted file(s). ${action} anyway?`, [
|
|
33
|
+
"Yes, proceed anyway",
|
|
34
|
+
"No, let me commit first",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
if (choice !== "Yes, proceed anyway") {
|
|
38
|
+
ctx.ui.notify("Commit your changes first", "warning");
|
|
39
|
+
return { cancel: true };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
9
42
|
|
|
10
43
|
export default function (pi: HookAPI) {
|
|
11
|
-
pi.on("
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const { stdout, code } = await ctx.exec("git", ["status", "--porcelain"]);
|
|
19
|
-
|
|
20
|
-
if (code !== 0) {
|
|
21
|
-
// Not a git repo, allow the action
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const hasChanges = stdout.trim().length > 0;
|
|
26
|
-
if (!hasChanges) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!ctx.hasUI) {
|
|
31
|
-
// In non-interactive mode, block by default
|
|
32
|
-
return { cancel: true };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Count changed files
|
|
36
|
-
const changedFiles = stdout.trim().split("\n").filter(Boolean).length;
|
|
37
|
-
|
|
38
|
-
const action =
|
|
39
|
-
event.reason === "before_new" ? "new session" : event.reason === "before_switch" ? "switch session" : "branch";
|
|
40
|
-
|
|
41
|
-
const choice = await ctx.ui.select(`You have ${changedFiles} uncommitted file(s). ${action} anyway?`, [
|
|
42
|
-
"Yes, proceed anyway",
|
|
43
|
-
"No, let me commit first",
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
if (choice !== "Yes, proceed anyway") {
|
|
47
|
-
ctx.ui.notify("Commit your changes first", "warning");
|
|
48
|
-
return { cancel: true };
|
|
49
|
-
}
|
|
44
|
+
pi.on("session_before_switch", async (event, ctx) => {
|
|
45
|
+
const action = event.reason === "new" ? "new session" : "switch session";
|
|
46
|
+
return checkDirtyRepo(pi, ctx, action);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
pi.on("session_before_branch", async (_event, ctx) => {
|
|
50
|
+
return checkDirtyRepo(pi, ctx, "branch");
|
|
50
51
|
});
|
|
51
52
|
}
|
|
@@ -9,19 +9,24 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
12
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
13
13
|
|
|
14
14
|
export default function (pi: HookAPI) {
|
|
15
|
-
pi.on("
|
|
16
|
-
if (event.reason !== "start") return;
|
|
17
|
-
|
|
15
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
18
16
|
const triggerFile = "/tmp/agent-trigger.txt";
|
|
19
17
|
|
|
20
18
|
fs.watch(triggerFile, () => {
|
|
21
19
|
try {
|
|
22
20
|
const content = fs.readFileSync(triggerFile, "utf-8").trim();
|
|
23
21
|
if (content) {
|
|
24
|
-
pi.
|
|
22
|
+
pi.sendMessage(
|
|
23
|
+
{
|
|
24
|
+
customType: "file-trigger",
|
|
25
|
+
content: `External trigger: ${content}`,
|
|
26
|
+
display: true,
|
|
27
|
+
},
|
|
28
|
+
true, // triggerTurn - get LLM to respond
|
|
29
|
+
);
|
|
25
30
|
fs.writeFileSync(triggerFile, ""); // Clear after reading
|
|
26
31
|
}
|
|
27
32
|
} catch {
|
|
@@ -5,25 +5,29 @@
|
|
|
5
5
|
* When branching, offers to restore code to that point in history.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: HookAPI) {
|
|
11
|
-
const checkpoints = new Map<
|
|
11
|
+
const checkpoints = new Map<string, string>();
|
|
12
|
+
let currentEntryId: string | undefined;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
// Track the current entry ID when user messages are saved
|
|
15
|
+
pi.on("tool_result", async (_event, ctx) => {
|
|
16
|
+
const leaf = ctx.sessionManager.getLeafEntry();
|
|
17
|
+
if (leaf) currentEntryId = leaf.id;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
pi.on("turn_start", async () => {
|
|
14
21
|
// Create a git stash entry before LLM makes changes
|
|
15
|
-
const { stdout } = await
|
|
22
|
+
const { stdout } = await pi.exec("git", ["stash", "create"]);
|
|
16
23
|
const ref = stdout.trim();
|
|
17
|
-
if (ref) {
|
|
18
|
-
checkpoints.set(
|
|
24
|
+
if (ref && currentEntryId) {
|
|
25
|
+
checkpoints.set(currentEntryId, ref);
|
|
19
26
|
}
|
|
20
27
|
});
|
|
21
28
|
|
|
22
|
-
pi.on("
|
|
23
|
-
|
|
24
|
-
if (event.reason !== "before_branch") return;
|
|
25
|
-
|
|
26
|
-
const ref = checkpoints.get(event.targetTurnIndex);
|
|
29
|
+
pi.on("session_before_branch", async (event, ctx) => {
|
|
30
|
+
const ref = checkpoints.get(event.entryId);
|
|
27
31
|
if (!ref) return;
|
|
28
32
|
|
|
29
33
|
if (!ctx.hasUI) {
|
|
@@ -37,7 +41,7 @@ export default function (pi: HookAPI) {
|
|
|
37
41
|
]);
|
|
38
42
|
|
|
39
43
|
if (choice?.startsWith("Yes")) {
|
|
40
|
-
await
|
|
44
|
+
await pi.exec("git", ["stash", "apply", ref]);
|
|
41
45
|
ctx.ui.notify("Code restored to checkpoint", "info");
|
|
42
46
|
}
|
|
43
47
|
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff hook - transfer context to a new focused session
|
|
3
|
+
*
|
|
4
|
+
* Instead of compacting (which is lossy), handoff extracts what matters
|
|
5
|
+
* for your next task and creates a new session with a generated prompt.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* /handoff now implement this for teams as well
|
|
9
|
+
* /handoff execute phase one of the plan
|
|
10
|
+
* /handoff check other places that need this fix
|
|
11
|
+
*
|
|
12
|
+
* The generated prompt appears as a draft in the editor for review/editing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { complete, type Message } from "@mariozechner/pi-ai";
|
|
16
|
+
import type { HookAPI, SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
import { BorderedLoader, convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent";
|
|
18
|
+
|
|
19
|
+
const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that:
|
|
20
|
+
|
|
21
|
+
1. Summarizes relevant context from the conversation (decisions made, approaches taken, key findings)
|
|
22
|
+
2. Lists any relevant files that were discussed or modified
|
|
23
|
+
3. Clearly states the next task based on the user's goal
|
|
24
|
+
4. Is self-contained - the new thread should be able to proceed without the old conversation
|
|
25
|
+
|
|
26
|
+
Format your response as a prompt the user can send to start the new thread. Be concise but include all necessary context. Do not include any preamble like "Here's the prompt" - just output the prompt itself.
|
|
27
|
+
|
|
28
|
+
Example output format:
|
|
29
|
+
## Context
|
|
30
|
+
We've been working on X. Key decisions:
|
|
31
|
+
- Decision 1
|
|
32
|
+
- Decision 2
|
|
33
|
+
|
|
34
|
+
Files involved:
|
|
35
|
+
- path/to/file1.ts
|
|
36
|
+
- path/to/file2.ts
|
|
37
|
+
|
|
38
|
+
## Task
|
|
39
|
+
[Clear description of what to do next based on user's goal]`;
|
|
40
|
+
|
|
41
|
+
export default function (pi: HookAPI) {
|
|
42
|
+
pi.registerCommand("handoff", {
|
|
43
|
+
description: "Transfer context to a new focused session",
|
|
44
|
+
handler: async (args, ctx) => {
|
|
45
|
+
if (!ctx.hasUI) {
|
|
46
|
+
ctx.ui.notify("handoff requires interactive mode", "error");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!ctx.model) {
|
|
51
|
+
ctx.ui.notify("No model selected", "error");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const goal = args.trim();
|
|
56
|
+
if (!goal) {
|
|
57
|
+
ctx.ui.notify("Usage: /handoff <goal for new thread>", "error");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Gather conversation context from current branch
|
|
62
|
+
const branch = ctx.sessionManager.getBranch();
|
|
63
|
+
const messages = branch
|
|
64
|
+
.filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message")
|
|
65
|
+
.map((entry) => entry.message);
|
|
66
|
+
|
|
67
|
+
if (messages.length === 0) {
|
|
68
|
+
ctx.ui.notify("No conversation to hand off", "error");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Convert to LLM format and serialize
|
|
73
|
+
const llmMessages = convertToLlm(messages);
|
|
74
|
+
const conversationText = serializeConversation(llmMessages);
|
|
75
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
76
|
+
|
|
77
|
+
// Generate the handoff prompt with loader UI
|
|
78
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, done) => {
|
|
79
|
+
const loader = new BorderedLoader(tui, theme, `Generating handoff prompt...`);
|
|
80
|
+
loader.onAbort = () => done(null);
|
|
81
|
+
|
|
82
|
+
const doGenerate = async () => {
|
|
83
|
+
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
|
|
84
|
+
|
|
85
|
+
const userMessage: Message = {
|
|
86
|
+
role: "user",
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `## Conversation History\n\n${conversationText}\n\n## User's Goal for New Thread\n\n${goal}`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const response = await complete(
|
|
97
|
+
ctx.model!,
|
|
98
|
+
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
99
|
+
{ apiKey, signal: loader.signal },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (response.stopReason === "aborted") {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return response.content
|
|
107
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
108
|
+
.map((c) => c.text)
|
|
109
|
+
.join("\n");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
doGenerate()
|
|
113
|
+
.then(done)
|
|
114
|
+
.catch((err) => {
|
|
115
|
+
console.error("Handoff generation failed:", err);
|
|
116
|
+
done(null);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return loader;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (result === null) {
|
|
123
|
+
ctx.ui.notify("Cancelled", "info");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Let user edit the generated prompt
|
|
128
|
+
const editedPrompt = await ctx.ui.editor("Edit handoff prompt (ctrl+enter to submit, esc to cancel)", result);
|
|
129
|
+
|
|
130
|
+
if (editedPrompt === undefined) {
|
|
131
|
+
ctx.ui.notify("Cancelled", "info");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create new session with parent tracking
|
|
136
|
+
const newSessionResult = await ctx.newSession({
|
|
137
|
+
parentSession: currentSessionFile,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (newSessionResult.cancelled) {
|
|
141
|
+
ctx.ui.notify("New session cancelled", "info");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Set the edited prompt in the main editor for submission
|
|
146
|
+
ctx.ui.setEditorText(editedPrompt);
|
|
147
|
+
ctx.ui.notify("Handoff ready. Submit when ready.", "info");
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Patterns checked: rm -rf, sudo, chmod/chown 777
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: HookAPI) {
|
|
11
11
|
const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Useful for preventing accidental modifications to sensitive files.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
export default function (pi: HookAPI) {
|
|
11
11
|
const protectedPaths = [".env", ".git/", "node_modules/"];
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Q&A extraction hook - extracts questions from assistant responses
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the "prompt generator" pattern:
|
|
5
|
+
* 1. /qna command gets the last assistant message
|
|
6
|
+
* 2. Shows a spinner while extracting (hides editor)
|
|
7
|
+
* 3. Loads the result into the editor for user to fill in answers
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { complete, type UserMessage } from "@mariozechner/pi-ai";
|
|
11
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { BorderedLoader } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
const SYSTEM_PROMPT = `You are a question extractor. Given text from a conversation, extract any questions that need answering and format them for the user to fill in.
|
|
15
|
+
|
|
16
|
+
Output format:
|
|
17
|
+
- List each question on its own line, prefixed with "Q: "
|
|
18
|
+
- After each question, add a blank line for the answer prefixed with "A: "
|
|
19
|
+
- If no questions are found, output "No questions found in the last message."
|
|
20
|
+
|
|
21
|
+
Example output:
|
|
22
|
+
Q: What is your preferred database?
|
|
23
|
+
A:
|
|
24
|
+
|
|
25
|
+
Q: Should we use TypeScript or JavaScript?
|
|
26
|
+
A:
|
|
27
|
+
|
|
28
|
+
Keep questions in the order they appeared. Be concise.`;
|
|
29
|
+
|
|
30
|
+
export default function (pi: HookAPI) {
|
|
31
|
+
pi.registerCommand("qna", {
|
|
32
|
+
description: "Extract questions from last assistant message into editor",
|
|
33
|
+
handler: async (_args, ctx) => {
|
|
34
|
+
if (!ctx.hasUI) {
|
|
35
|
+
ctx.ui.notify("qna requires interactive mode", "error");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!ctx.model) {
|
|
40
|
+
ctx.ui.notify("No model selected", "error");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Find the last assistant message on the current branch
|
|
45
|
+
const branch = ctx.sessionManager.getBranch();
|
|
46
|
+
let lastAssistantText: string | undefined;
|
|
47
|
+
|
|
48
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
49
|
+
const entry = branch[i];
|
|
50
|
+
if (entry.type === "message") {
|
|
51
|
+
const msg = entry.message;
|
|
52
|
+
if ("role" in msg && msg.role === "assistant") {
|
|
53
|
+
if (msg.stopReason !== "stop") {
|
|
54
|
+
ctx.ui.notify(`Last assistant message incomplete (${msg.stopReason})`, "error");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const textParts = msg.content
|
|
58
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
59
|
+
.map((c) => c.text);
|
|
60
|
+
if (textParts.length > 0) {
|
|
61
|
+
lastAssistantText = textParts.join("\n");
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!lastAssistantText) {
|
|
69
|
+
ctx.ui.notify("No assistant messages found", "error");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Run extraction with loader UI
|
|
74
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, done) => {
|
|
75
|
+
const loader = new BorderedLoader(tui, theme, `Extracting questions using ${ctx.model!.id}...`);
|
|
76
|
+
loader.onAbort = () => done(null);
|
|
77
|
+
|
|
78
|
+
// Do the work
|
|
79
|
+
const doExtract = async () => {
|
|
80
|
+
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
|
|
81
|
+
const userMessage: UserMessage = {
|
|
82
|
+
role: "user",
|
|
83
|
+
content: [{ type: "text", text: lastAssistantText! }],
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const response = await complete(
|
|
88
|
+
ctx.model!,
|
|
89
|
+
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
90
|
+
{ apiKey, signal: loader.signal },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (response.stopReason === "aborted") {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return response.content
|
|
98
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
99
|
+
.map((c) => c.text)
|
|
100
|
+
.join("\n");
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
doExtract()
|
|
104
|
+
.then(done)
|
|
105
|
+
.catch(() => done(null));
|
|
106
|
+
|
|
107
|
+
return loader;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result === null) {
|
|
111
|
+
ctx.ui.notify("Cancelled", "info");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
ctx.ui.setEditorText(result);
|
|
116
|
+
ctx.ui.notify("Questions loaded. Edit and submit when ready.", "info");
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|