@dungle-scrubs/tallow 0.9.4 → 0.9.7
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.js +8 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +24 -12
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +229 -146
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/interactive-reset.d.ts +49 -0
- package/dist/interactive-reset.d.ts.map +1 -0
- package/dist/interactive-reset.js +40 -0
- package/dist/interactive-reset.js.map +1 -0
- package/dist/pi-tui-editor-patch.d.ts +10 -0
- package/dist/pi-tui-editor-patch.d.ts.map +1 -0
- package/dist/pi-tui-editor-patch.js +159 -0
- package/dist/pi-tui-editor-patch.js.map +1 -0
- package/dist/pi-tui-patch.d.ts +2 -0
- package/dist/pi-tui-patch.d.ts.map +1 -0
- package/dist/pi-tui-patch.js +563 -0
- package/dist/pi-tui-patch.js.map +1 -0
- package/dist/pi-tui-settings-list-patch.d.ts +11 -0
- package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
- package/dist/pi-tui-settings-list-patch.js +38 -0
- package/dist/pi-tui-settings-list-patch.js.map +1 -0
- package/dist/process-cleanup.js +1 -1
- package/dist/process-cleanup.js.map +1 -1
- package/dist/reset-diagnostics.d.ts +69 -0
- package/dist/reset-diagnostics.d.ts.map +1 -0
- package/dist/reset-diagnostics.js +41 -0
- package/dist/reset-diagnostics.js.map +1 -0
- package/dist/sdk.d.ts +7 -23
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +211 -174
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts +1 -0
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +8 -18
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +4 -5
- package/extensions/_icons/index.ts +2 -4
- package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
- package/extensions/_shared/__tests__/shell-policy.test.ts +19 -0
- package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
- package/extensions/_shared/image-metadata.ts +99 -0
- package/extensions/_shared/inline-preview.ts +1 -1
- package/extensions/_shared/shell-policy.ts +121 -1
- package/extensions/_shared/terminal-links.ts +22 -0
- package/extensions/ask-user-question-tool/index.ts +0 -3
- package/extensions/clear/__tests__/clear.test.ts +269 -2
- package/extensions/command-expansion/index.ts +9 -3
- package/extensions/context-files/index.ts +5 -1
- package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
- package/extensions/context-fork/extension.json +1 -1
- package/extensions/context-fork/frontmatter-index.ts +6 -1
- package/extensions/context-fork/index.ts +32 -0
- package/extensions/edit-tool-enhanced/index.ts +2 -1
- package/extensions/git-status/__tests__/git-status.test.ts +65 -2
- package/extensions/git-status/index.ts +268 -98
- package/extensions/hooks/index.ts +33 -11
- package/extensions/loop/index.ts +14 -1
- package/extensions/lsp/index.ts +64 -13
- package/extensions/lsp/package.json +2 -2
- package/extensions/minimal-skill-display/index.ts +7 -1
- package/extensions/random-spinner/index.ts +7 -642
- package/extensions/read-tool-enhanced/index.ts +13 -10
- package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
- package/extensions/render-stabilizer/index.ts +6 -6
- package/extensions/rewind/__tests__/session-files.test.ts +115 -0
- package/extensions/rewind/__tests__/snapshots.test.ts +23 -0
- package/extensions/rewind/index.ts +5 -0
- package/extensions/rewind/session-files.ts +138 -0
- package/extensions/rewind/snapshots.ts +104 -5
- package/extensions/skill-commands/index.ts +6 -1
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
- package/extensions/slash-command-bridge/index.ts +14 -2
- package/extensions/subagent-tool/model-resolver.ts +274 -7
- package/extensions/subagent-tool/schema.ts +1 -2
- package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
- package/extensions/teams-tool/tools/register-extension.ts +1 -3
- package/extensions/teams-tool/tools/teammate-tools.ts +1 -2
- package/extensions/web-search-tool/index.ts +2 -1
- package/extensions/wezterm-pane-control/index.ts +1 -2
- package/extensions/write-tool-enhanced/index.ts +2 -1
- package/node_modules/@mariozechner/pi-tui/README.md +56 -34
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
- package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +6 -6
- package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
- package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
- package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
- package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
- package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
- package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
- package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
- package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
- package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
- package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
- package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
- package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
- package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
- package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
- package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
- package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
- package/package.json +13 -13
- package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
- package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
- package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
- package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
- package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
- package/packages/tallow-tui/node_modules/marked/README.md +5 -4
- package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
- package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
- package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
- package/packages/tallow-tui/node_modules/marked/package.json +26 -34
- package/skills/tallow-expert/SKILL.md +3 -5
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
- package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
- package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
- package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
- package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
- package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - audit trail recording
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { dirname, isAbsolute, join } from "node:path";
|
|
@@ -1106,6 +1106,102 @@ export function runCommandSync(options: RunCommandOptions): ProcessRunResult {
|
|
|
1106
1106
|
};
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
|
+
/**
|
|
1110
|
+
* Run a non-shell process asynchronously with centralized policy and audit logging.
|
|
1111
|
+
*
|
|
1112
|
+
* @param options - Process invocation options
|
|
1113
|
+
* @returns Structured process result including policy-blocked state
|
|
1114
|
+
*/
|
|
1115
|
+
export async function runCommand(options: RunCommandOptions): Promise<ProcessRunResult> {
|
|
1116
|
+
const commandLine = [options.command, ...options.args].join(" ").trim();
|
|
1117
|
+
const verdict = evaluateCommand(commandLine, options.source, options.cwd);
|
|
1118
|
+
if (!verdict.allowed) {
|
|
1119
|
+
recordAudit({
|
|
1120
|
+
timestamp: Date.now(),
|
|
1121
|
+
command: verdict.normalizedCommand,
|
|
1122
|
+
source: options.source,
|
|
1123
|
+
trustLevel: verdict.trustLevel,
|
|
1124
|
+
cwd: options.cwd,
|
|
1125
|
+
outcome: "blocked",
|
|
1126
|
+
reason: verdict.reason,
|
|
1127
|
+
});
|
|
1128
|
+
return {
|
|
1129
|
+
ok: false,
|
|
1130
|
+
blocked: true,
|
|
1131
|
+
stdout: "",
|
|
1132
|
+
stderr: "",
|
|
1133
|
+
exitCode: null,
|
|
1134
|
+
reason: verdict.reason,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const timeoutMs = clampTimeout(options.timeoutMs);
|
|
1139
|
+
const startedAt = Date.now();
|
|
1140
|
+
|
|
1141
|
+
return await new Promise((resolve) => {
|
|
1142
|
+
execFile(
|
|
1143
|
+
options.command,
|
|
1144
|
+
[...options.args],
|
|
1145
|
+
{
|
|
1146
|
+
cwd: options.cwd,
|
|
1147
|
+
encoding: "utf-8",
|
|
1148
|
+
timeout: timeoutMs,
|
|
1149
|
+
maxBuffer: options.maxBuffer ?? DEFAULT_MAX_BUFFER,
|
|
1150
|
+
},
|
|
1151
|
+
(error, stdout = "", stderr = "") => {
|
|
1152
|
+
const exitCode = typeof error?.code === "number" ? error.code : 0;
|
|
1153
|
+
const durationMs = Date.now() - startedAt;
|
|
1154
|
+
const normalizedStdout = stdout.toString();
|
|
1155
|
+
const normalizedStderr = stderr.toString();
|
|
1156
|
+
if (error) {
|
|
1157
|
+
const reason =
|
|
1158
|
+
error.killed && error.signal === "SIGTERM"
|
|
1159
|
+
? `Command timed out after ${timeoutMs}ms`
|
|
1160
|
+
: error.message;
|
|
1161
|
+
recordAudit({
|
|
1162
|
+
timestamp: Date.now(),
|
|
1163
|
+
command: verdict.normalizedCommand,
|
|
1164
|
+
source: options.source,
|
|
1165
|
+
trustLevel: verdict.trustLevel,
|
|
1166
|
+
cwd: options.cwd,
|
|
1167
|
+
outcome: "failed",
|
|
1168
|
+
reason,
|
|
1169
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
1170
|
+
durationMs,
|
|
1171
|
+
});
|
|
1172
|
+
resolve({
|
|
1173
|
+
ok: false,
|
|
1174
|
+
blocked: false,
|
|
1175
|
+
stdout: normalizedStdout,
|
|
1176
|
+
stderr: normalizedStderr,
|
|
1177
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
1178
|
+
reason,
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
recordAudit({
|
|
1184
|
+
timestamp: Date.now(),
|
|
1185
|
+
command: verdict.normalizedCommand,
|
|
1186
|
+
source: options.source,
|
|
1187
|
+
trustLevel: verdict.trustLevel,
|
|
1188
|
+
cwd: options.cwd,
|
|
1189
|
+
outcome: "executed",
|
|
1190
|
+
exitCode,
|
|
1191
|
+
durationMs,
|
|
1192
|
+
});
|
|
1193
|
+
resolve({
|
|
1194
|
+
ok: true,
|
|
1195
|
+
blocked: false,
|
|
1196
|
+
stdout: normalizedStdout,
|
|
1197
|
+
stderr: normalizedStderr,
|
|
1198
|
+
exitCode,
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1109
1205
|
/**
|
|
1110
1206
|
* Run a git command through centralized policy and process wrapper.
|
|
1111
1207
|
*
|
|
@@ -1130,6 +1226,30 @@ export function runGitCommandSync(
|
|
|
1130
1226
|
return result.stdout.trim();
|
|
1131
1227
|
}
|
|
1132
1228
|
|
|
1229
|
+
/**
|
|
1230
|
+
* Run a git command asynchronously through centralized policy and process wrapper.
|
|
1231
|
+
*
|
|
1232
|
+
* @param args - Git arguments
|
|
1233
|
+
* @param cwd - Working directory
|
|
1234
|
+
* @param timeoutMs - Optional timeout override
|
|
1235
|
+
* @returns Trimmed stdout on success, null otherwise
|
|
1236
|
+
*/
|
|
1237
|
+
export async function runGitCommand(
|
|
1238
|
+
args: readonly string[],
|
|
1239
|
+
cwd: string,
|
|
1240
|
+
timeoutMs?: number
|
|
1241
|
+
): Promise<string | null> {
|
|
1242
|
+
const result = await runCommand({
|
|
1243
|
+
command: "git",
|
|
1244
|
+
args,
|
|
1245
|
+
cwd,
|
|
1246
|
+
source: "git-helper",
|
|
1247
|
+
timeoutMs,
|
|
1248
|
+
});
|
|
1249
|
+
if (!result.ok) return null;
|
|
1250
|
+
return result.stdout.trim();
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1133
1253
|
/**
|
|
1134
1254
|
* Check whether an executable exists on PATH using policy-wrapped process execution.
|
|
1135
1255
|
*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap visible text in an OSC 8 terminal hyperlink.
|
|
3
|
+
*
|
|
4
|
+
* @param url - Target URL
|
|
5
|
+
* @param text - Visible link text
|
|
6
|
+
* @returns OSC 8 hyperlink sequence
|
|
7
|
+
*/
|
|
8
|
+
export function hyperlink(url: string, text: string): string {
|
|
9
|
+
return `\u001b]8;;${url}\u0007${text}\u001b]8;;\u0007`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wrap a file path in an OSC 8 hyperlink using the file:// protocol.
|
|
14
|
+
*
|
|
15
|
+
* @param filePath - Absolute or relative file path
|
|
16
|
+
* @param displayText - Optional visible text override
|
|
17
|
+
* @returns File path wrapped in a file:// OSC 8 hyperlink
|
|
18
|
+
*/
|
|
19
|
+
export function fileLink(filePath: string, displayText?: string): string {
|
|
20
|
+
const url = `file://${encodeURI(filePath)}`;
|
|
21
|
+
return hyperlink(url, displayText ?? filePath);
|
|
22
|
+
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Editor,
|
|
10
10
|
type EditorTheme,
|
|
11
11
|
Key,
|
|
12
|
-
Loader,
|
|
13
12
|
matchesKey,
|
|
14
13
|
Text,
|
|
15
14
|
truncateToWidth,
|
|
@@ -153,8 +152,6 @@ WHEN NOT TO USE:
|
|
|
153
152
|
{ label: "Type something.", isOther: true },
|
|
154
153
|
];
|
|
155
154
|
|
|
156
|
-
ctx.ui.setWorkingMessage(Loader.HIDE);
|
|
157
|
-
|
|
158
155
|
const result = await ctx.ui.custom<{
|
|
159
156
|
answer: string;
|
|
160
157
|
wasCustom: boolean;
|
|
@@ -1,7 +1,140 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
-
import
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { AssistantMessage, ToolResultMessage, Usage } from "@mariozechner/pi-ai";
|
|
6
|
+
import type {
|
|
7
|
+
ExtensionAPI,
|
|
8
|
+
ExtensionContext,
|
|
9
|
+
ExtensionUIContext,
|
|
10
|
+
TurnEndEvent,
|
|
11
|
+
} from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import {
|
|
13
|
+
getResetDiagnosticsForTests,
|
|
14
|
+
resetResetDiagnosticsForTests,
|
|
15
|
+
} from "../../../src/reset-diagnostics.js";
|
|
16
|
+
import { ExtensionHarness } from "../../../test-utils/extension-harness.js";
|
|
17
|
+
import { ManualTimerScheduler } from "../../../test-utils/manual-timer-scheduler.js";
|
|
18
|
+
import { registerContextForkExtension } from "../../context-fork/index.js";
|
|
19
|
+
import slashCommandBridge, {
|
|
20
|
+
resetSlashCommandBridgeStateForTests,
|
|
21
|
+
setSlashCommandBridgeSchedulerForTests,
|
|
22
|
+
} from "../../slash-command-bridge/index.js";
|
|
3
23
|
import registerClear from "../index.js";
|
|
4
24
|
|
|
25
|
+
const ZERO_USAGE: Usage = {
|
|
26
|
+
input: 0,
|
|
27
|
+
output: 0,
|
|
28
|
+
cacheRead: 0,
|
|
29
|
+
cacheWrite: 0,
|
|
30
|
+
totalTokens: 0,
|
|
31
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let harness: ExtensionHarness;
|
|
35
|
+
let scheduler: ManualTimerScheduler;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
scheduler = new ManualTimerScheduler();
|
|
39
|
+
setSlashCommandBridgeSchedulerForTests(scheduler.runtime);
|
|
40
|
+
harness = ExtensionHarness.create();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
resetResetDiagnosticsForTests();
|
|
45
|
+
resetSlashCommandBridgeStateForTests();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build a compact-lifecycle test context.
|
|
50
|
+
*
|
|
51
|
+
* @param overrides - Context overrides
|
|
52
|
+
* @returns Extension context
|
|
53
|
+
*/
|
|
54
|
+
function buildContext(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
55
|
+
return {
|
|
56
|
+
ui: {} as ExtensionContext["ui"],
|
|
57
|
+
hasUI: false,
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
sessionManager: {} as ExtensionContext["sessionManager"],
|
|
60
|
+
modelRegistry: {} as ExtensionContext["modelRegistry"],
|
|
61
|
+
model: undefined,
|
|
62
|
+
isIdle: () => true,
|
|
63
|
+
abort: () => {},
|
|
64
|
+
hasPendingMessages: () => false,
|
|
65
|
+
shutdown: () => {},
|
|
66
|
+
getContextUsage: () => ({ contextWindow: 100, tokens: 90 }),
|
|
67
|
+
compact: () => {},
|
|
68
|
+
getSystemPrompt: () => "",
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build a realistic assistant turn_end event for compact lifecycle tests.
|
|
75
|
+
*
|
|
76
|
+
* @param stopReason - Assistant stop reason for the completed turn
|
|
77
|
+
* @returns TurnEnd event payload
|
|
78
|
+
*/
|
|
79
|
+
function buildAssistantTurnEnd(stopReason: AssistantMessage["stopReason"]): TurnEndEvent {
|
|
80
|
+
return {
|
|
81
|
+
type: "turn_end",
|
|
82
|
+
turnIndex: 0,
|
|
83
|
+
message: {
|
|
84
|
+
role: "assistant",
|
|
85
|
+
content: [],
|
|
86
|
+
api: "anthropic-messages",
|
|
87
|
+
provider: "mock",
|
|
88
|
+
model: "mock-model",
|
|
89
|
+
stopReason,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
usage: { ...ZERO_USAGE },
|
|
92
|
+
},
|
|
93
|
+
toolResults: stopReason === "toolUse" ? [buildCompactToolResult()] : [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build the compact tool result payload recorded on the tool-use turn.
|
|
99
|
+
*
|
|
100
|
+
* @returns Tool result message for `run_slash_command({ command: "compact" })`
|
|
101
|
+
*/
|
|
102
|
+
function buildCompactToolResult(): ToolResultMessage<{ command: string }> {
|
|
103
|
+
return {
|
|
104
|
+
role: "toolResult",
|
|
105
|
+
toolCallId: "mock-tool-call",
|
|
106
|
+
toolName: "run_slash_command",
|
|
107
|
+
content: [
|
|
108
|
+
{ type: "text", text: "Session compaction will begin after this response completes." },
|
|
109
|
+
],
|
|
110
|
+
details: { command: "compact" },
|
|
111
|
+
isError: false,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface Deferred<T> {
|
|
117
|
+
readonly promise: Promise<T>;
|
|
118
|
+
readonly reject: (error?: unknown) => void;
|
|
119
|
+
readonly resolve: (value: T) => void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a deferred promise for controlling async completion timing in tests.
|
|
124
|
+
*
|
|
125
|
+
* @template T
|
|
126
|
+
* @returns Deferred promise controls
|
|
127
|
+
*/
|
|
128
|
+
function createDeferred<T>(): Deferred<T> {
|
|
129
|
+
let reject!: (error?: unknown) => void;
|
|
130
|
+
let resolve!: (value: T) => void;
|
|
131
|
+
const promise = new Promise<T>((innerResolve, innerReject) => {
|
|
132
|
+
resolve = innerResolve;
|
|
133
|
+
reject = innerReject;
|
|
134
|
+
});
|
|
135
|
+
return { promise, reject, resolve };
|
|
136
|
+
}
|
|
137
|
+
|
|
5
138
|
describe("clear extension", () => {
|
|
6
139
|
test("registers /clear command", () => {
|
|
7
140
|
const commands: Array<{ name: string; description: string }> = [];
|
|
@@ -35,4 +168,138 @@ describe("clear extension", () => {
|
|
|
35
168
|
await handler?.("", { newSession });
|
|
36
169
|
expect(newSession).toHaveBeenCalledTimes(1);
|
|
37
170
|
});
|
|
171
|
+
|
|
172
|
+
test("/clear cancels pending compact continuation before it can restart work", async () => {
|
|
173
|
+
await harness.loadExtension(slashCommandBridge);
|
|
174
|
+
registerClear(harness.api);
|
|
175
|
+
|
|
176
|
+
const compactTool = harness.tools.get("run_slash_command");
|
|
177
|
+
const clearCommand = harness.commands.get("clear");
|
|
178
|
+
let compactOptions: Parameters<ExtensionContext["compact"]>[0];
|
|
179
|
+
const widgetUpdates: Array<{ key: string; content: string[] | undefined }> = [];
|
|
180
|
+
const workingMessages: Array<string | undefined> = [];
|
|
181
|
+
const ctx = buildContext({
|
|
182
|
+
hasUI: true,
|
|
183
|
+
ui: {
|
|
184
|
+
setWidget: (key: string, content?: string[]) => {
|
|
185
|
+
widgetUpdates.push({ key, content });
|
|
186
|
+
},
|
|
187
|
+
setWorkingMessage: (message?: string) => {
|
|
188
|
+
workingMessages.push(message);
|
|
189
|
+
},
|
|
190
|
+
} as ExtensionUIContext,
|
|
191
|
+
compact: (options) => {
|
|
192
|
+
compactOptions = options;
|
|
193
|
+
},
|
|
194
|
+
isIdle: () => true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!compactTool?.execute || !clearCommand?.handler) {
|
|
198
|
+
throw new Error("expected compact tool and clear command to be registered");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await compactTool.execute("test-call-id", { command: "compact" }, undefined, undefined, ctx);
|
|
202
|
+
await harness.fireEvent("turn_end", buildAssistantTurnEnd("toolUse"), ctx);
|
|
203
|
+
await harness.fireEvent("turn_end", buildAssistantTurnEnd("stop"), ctx);
|
|
204
|
+
compactOptions?.onComplete?.();
|
|
205
|
+
|
|
206
|
+
const newSession = mock(async () => {
|
|
207
|
+
await harness.fireEvent(
|
|
208
|
+
"session_before_switch",
|
|
209
|
+
{ type: "session_before_switch", reason: "new" },
|
|
210
|
+
ctx
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
await clearCommand.handler("", { ...ctx, newSession } as never);
|
|
214
|
+
scheduler.advanceBy(200);
|
|
215
|
+
|
|
216
|
+
expect(newSession).toHaveBeenCalledTimes(1);
|
|
217
|
+
expect(harness.sentMessages.some((message) => message.customType === "compact-continue")).toBe(
|
|
218
|
+
false
|
|
219
|
+
);
|
|
220
|
+
expect(widgetUpdates.at(-1)).toEqual({ key: "compact-progress", content: undefined });
|
|
221
|
+
expect(workingMessages.at(-1)).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("/clear after deferred fork completion leaves the replacement session idle", async () => {
|
|
225
|
+
const commandDir = mkdtempSync(join(tmpdir(), "clear-fork-command-"));
|
|
226
|
+
const commandPath = join(commandDir, "review.md");
|
|
227
|
+
const deferred = createDeferred<{ duration: number; exitCode: number; output: string }>();
|
|
228
|
+
const workingMessages: string[] = [];
|
|
229
|
+
writeFileSync(commandPath, "Review the code.\n", "utf-8");
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
registerContextForkExtension(harness.api, {
|
|
233
|
+
buildFrontmatterIndex: () =>
|
|
234
|
+
new Map([
|
|
235
|
+
[
|
|
236
|
+
"review",
|
|
237
|
+
{
|
|
238
|
+
context: "fork",
|
|
239
|
+
filePath: commandPath,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
]),
|
|
243
|
+
loadAllAgents: () => new Map(),
|
|
244
|
+
routeForkedModel: async () => undefined,
|
|
245
|
+
spawnForkSubprocess: () => deferred.promise,
|
|
246
|
+
});
|
|
247
|
+
registerClear(harness.api);
|
|
248
|
+
|
|
249
|
+
const clearCommand = harness.commands.get("clear");
|
|
250
|
+
const ctx = buildContext({
|
|
251
|
+
hasUI: true,
|
|
252
|
+
ui: {
|
|
253
|
+
notify: () => {},
|
|
254
|
+
setWorkingMessage: (message?: string) => {
|
|
255
|
+
workingMessages.push(message ?? "");
|
|
256
|
+
},
|
|
257
|
+
} as ExtensionUIContext,
|
|
258
|
+
isIdle: () => true,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (!clearCommand?.handler) {
|
|
262
|
+
throw new Error("expected clear command to be registered");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const [forkResult] = await harness.fireEvent("input", { text: "/review" }, ctx);
|
|
266
|
+
expect(forkResult).toEqual({ action: "handled" });
|
|
267
|
+
expect(harness.sentMessages).toHaveLength(1);
|
|
268
|
+
expect(harness.sentMessages[0]?.content).toContain("🔀 /review");
|
|
269
|
+
|
|
270
|
+
const newSession = mock(async () => {
|
|
271
|
+
await harness.fireEvent(
|
|
272
|
+
"session_before_switch",
|
|
273
|
+
{ type: "session_before_switch", reason: "new" },
|
|
274
|
+
ctx
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
await clearCommand.handler("", { ...ctx, newSession } as never);
|
|
278
|
+
deferred.resolve({ duration: 5, exitCode: 0, output: "fork done" });
|
|
279
|
+
await Promise.resolve();
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
281
|
+
|
|
282
|
+
expect(newSession).toHaveBeenCalledTimes(1);
|
|
283
|
+
expect(workingMessages).toContain("🔀 forking: /review");
|
|
284
|
+
expect(workingMessages.at(-1)).toBe("");
|
|
285
|
+
expect(harness.sentMessages).toHaveLength(1);
|
|
286
|
+
expect(harness.sentMessages.some((message) => message.options?.triggerTurn === true)).toBe(
|
|
287
|
+
false
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const diagnostics = getResetDiagnosticsForTests();
|
|
291
|
+
expect(diagnostics.some((event) => event.kind === "deferred_cancelled")).toBe(true);
|
|
292
|
+
expect(
|
|
293
|
+
diagnostics.some(
|
|
294
|
+
(event) =>
|
|
295
|
+
event.kind === "deferred_dropped" &&
|
|
296
|
+
event.source === "context-fork" &&
|
|
297
|
+
event.reason === "session_generation_mismatch"
|
|
298
|
+
)
|
|
299
|
+
).toBe(true);
|
|
300
|
+
} finally {
|
|
301
|
+
deferred.reject(new Error("cleanup"));
|
|
302
|
+
rmSync(commandDir, { force: true, recursive: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
38
305
|
});
|
|
@@ -19,7 +19,7 @@ import * as fs from "node:fs";
|
|
|
19
19
|
import * as os from "node:os";
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
22
|
-
import { loadSkills, stripFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
22
|
+
import { getAgentDir, loadSkills, stripFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
23
23
|
import { createLazyInitializer } from "../_shared/lazy-init.js";
|
|
24
24
|
import { isProjectTrusted } from "../_shared/project-trust.js";
|
|
25
25
|
|
|
@@ -52,7 +52,13 @@ interface CommandExpansionDependencies {
|
|
|
52
52
|
|
|
53
53
|
/** Default dependency implementation for production runtime. */
|
|
54
54
|
const DEFAULT_COMMAND_EXPANSION_DEPENDENCIES: CommandExpansionDependencies = {
|
|
55
|
-
loadSkills
|
|
55
|
+
loadSkills: () =>
|
|
56
|
+
loadSkills({
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
agentDir: getAgentDir(),
|
|
59
|
+
skillPaths: [],
|
|
60
|
+
includeDefaults: true,
|
|
61
|
+
}),
|
|
56
62
|
loadPromptTemplates,
|
|
57
63
|
};
|
|
58
64
|
|
|
@@ -418,7 +424,7 @@ export function registerCommandExpansionExtension(
|
|
|
418
424
|
|
|
419
425
|
// Only process if it looks like a command with arguments
|
|
420
426
|
const split = splitOuterCommand(text);
|
|
421
|
-
if (!split
|
|
427
|
+
if (!split?.args) {
|
|
422
428
|
return { action: "continue" as const };
|
|
423
429
|
}
|
|
424
430
|
|
|
@@ -943,7 +943,11 @@ export default function contextFilesExtension(pi: ExtensionAPI) {
|
|
|
943
943
|
resetSessionState();
|
|
944
944
|
});
|
|
945
945
|
|
|
946
|
-
|
|
946
|
+
(
|
|
947
|
+
pi as unknown as {
|
|
948
|
+
on: (event: string, handler: () => Promise<void>) => void;
|
|
949
|
+
}
|
|
950
|
+
).on("session_switch", async () => {
|
|
947
951
|
resetSessionState();
|
|
948
952
|
});
|
|
949
953
|
|
|
@@ -6,7 +6,7 @@ import { ExtensionHarness } from "../../../test-utils/extension-harness.js";
|
|
|
6
6
|
import { buildFrontmatterIndex } from "../frontmatter-index.js";
|
|
7
7
|
import { registerContextForkExtension } from "../index.js";
|
|
8
8
|
import { resolveModel } from "../model-resolver.js";
|
|
9
|
-
import type { ForkOptions } from "../spawn.js";
|
|
9
|
+
import type { ForkOptions, ForkResult } from "../spawn.js";
|
|
10
10
|
import { buildForkArgs } from "../spawn.js";
|
|
11
11
|
|
|
12
12
|
// ── Model Resolver ──────────────────────────────────────────
|
|
@@ -503,3 +503,96 @@ describe("context-fork lazy resource initialization", () => {
|
|
|
503
503
|
expect(agentLoads).toBe(2);
|
|
504
504
|
});
|
|
505
505
|
});
|
|
506
|
+
|
|
507
|
+
interface Deferred<T> {
|
|
508
|
+
readonly promise: Promise<T>;
|
|
509
|
+
readonly reject: (error?: unknown) => void;
|
|
510
|
+
readonly resolve: (value: T) => void;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Create a deferred promise for controlling async completion timing in tests.
|
|
515
|
+
*
|
|
516
|
+
* @template T
|
|
517
|
+
* @returns Deferred promise controls
|
|
518
|
+
*/
|
|
519
|
+
function createDeferred<T>(): Deferred<T> {
|
|
520
|
+
let reject!: (error?: unknown) => void;
|
|
521
|
+
let resolve!: (value: T) => void;
|
|
522
|
+
const promise = new Promise<T>((innerResolve, innerReject) => {
|
|
523
|
+
resolve = innerResolve;
|
|
524
|
+
reject = innerReject;
|
|
525
|
+
});
|
|
526
|
+
return { promise, reject, resolve };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Build a minimal extension context with a mutable working-message log.
|
|
531
|
+
*
|
|
532
|
+
* @param workingMessages - Collector for working-message updates
|
|
533
|
+
* @returns Minimal extension context for context-fork tests
|
|
534
|
+
*/
|
|
535
|
+
function buildTestContext(workingMessages: string[]): Record<string, unknown> {
|
|
536
|
+
return {
|
|
537
|
+
cwd: process.cwd(),
|
|
538
|
+
hasUI: true,
|
|
539
|
+
isIdle: () => true,
|
|
540
|
+
model: undefined,
|
|
541
|
+
ui: {
|
|
542
|
+
notify: () => {},
|
|
543
|
+
setWorkingMessage: (message?: string) => {
|
|
544
|
+
workingMessages.push(message ?? "");
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
describe("context-fork reset boundaries", () => {
|
|
551
|
+
test("ignores late fork completion after session_before_switch", async () => {
|
|
552
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "fork-reset-test-"));
|
|
553
|
+
const commandPath = path.join(tmpDir, "review.md");
|
|
554
|
+
const deferred = createDeferred<ForkResult>();
|
|
555
|
+
const harness = ExtensionHarness.create();
|
|
556
|
+
const workingMessages: string[] = [];
|
|
557
|
+
fs.writeFileSync(commandPath, "Review the code.\n", "utf-8");
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
registerContextForkExtension(harness.api, {
|
|
561
|
+
buildFrontmatterIndex: () =>
|
|
562
|
+
new Map([
|
|
563
|
+
[
|
|
564
|
+
"review",
|
|
565
|
+
{
|
|
566
|
+
context: "fork",
|
|
567
|
+
filePath: commandPath,
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
]),
|
|
571
|
+
loadAllAgents: () => new Map(),
|
|
572
|
+
routeForkedModel: async () => undefined,
|
|
573
|
+
spawnForkSubprocess: () => deferred.promise,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const ctx = buildTestContext(workingMessages);
|
|
577
|
+
const [result] = await harness.fireEvent("input", { text: "/review" }, ctx as never);
|
|
578
|
+
expect(result).toEqual({ action: "handled" });
|
|
579
|
+
expect(harness.sentMessages).toHaveLength(1);
|
|
580
|
+
expect(harness.sentMessages[0]?.content).toContain("🔀 /review");
|
|
581
|
+
|
|
582
|
+
await harness.fireEvent(
|
|
583
|
+
"session_before_switch",
|
|
584
|
+
{ type: "session_before_switch", reason: "new" },
|
|
585
|
+
ctx as never
|
|
586
|
+
);
|
|
587
|
+
deferred.resolve({ duration: 5, exitCode: 0, output: "fork done" });
|
|
588
|
+
await Promise.resolve();
|
|
589
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
590
|
+
|
|
591
|
+
expect(workingMessages).toContain("🔀 forking: /review");
|
|
592
|
+
expect(harness.sentMessages).toHaveLength(1);
|
|
593
|
+
} finally {
|
|
594
|
+
deferred.reject(new Error("cleanup"));
|
|
595
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "Runs commands/skills with context: fork frontmatter in isolated pi subprocesses",
|
|
5
5
|
"whenToUse": "Use when a command or skill must run in an isolated forked tallow subprocess.",
|
|
6
6
|
"capabilities": {
|
|
7
|
-
"events": ["input", "session_start"]
|
|
7
|
+
"events": ["input", "session_before_switch", "session_start"]
|
|
8
8
|
},
|
|
9
9
|
"permissionSurface": {
|
|
10
10
|
"filesystem": "write",
|
|
@@ -297,7 +297,12 @@ export function buildFrontmatterIndex(debugLog?: (msg: string) => void): Frontma
|
|
|
297
297
|
|
|
298
298
|
// Skills: skill name as-is
|
|
299
299
|
try {
|
|
300
|
-
const { skills } = loadSkills(
|
|
300
|
+
const { skills } = loadSkills({
|
|
301
|
+
cwd: process.cwd(),
|
|
302
|
+
agentDir: getAgentDir(),
|
|
303
|
+
skillPaths: [],
|
|
304
|
+
includeDefaults: true,
|
|
305
|
+
});
|
|
301
306
|
for (const skill of skills) {
|
|
302
307
|
maybeAdd(skill.name, skill.filePath);
|
|
303
308
|
}
|