@selesai/code 0.1.19 → 0.2.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/dist/agents/architect.md +4 -3
- package/dist/agents/builder.md +5 -4
- package/dist/agents/commentator.md +3 -2
- package/dist/agents/explorer.md +4 -4
- package/dist/agents/recapper.md +4 -3
- package/dist/config.d.ts +6 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -9
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +3 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +18 -24
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +8 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +25 -11
- package/dist/core/skills.js.map +1 -1
- package/dist/defaults/models.json +143 -39
- package/dist/defaults/settings.json +2 -29
- package/dist/extensions/package.json +5 -4
- package/dist/extensions/pi-intercom/index.ts +12 -5
- package/dist/extensions/pi-powerline-footer/index.ts +4 -1
- package/dist/extensions/pi-powerline-footer/tps.ts +286 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +47 -46
- package/dist/extensions/pi-subagents/README.md +12 -5
- package/dist/extensions/pi-subagents/agents/context-builder.md +2 -2
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -5
- package/dist/extensions/pi-subagents/package.json +8 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +1 -1
- package/dist/extensions/pi-subagents/src/agents/agents.ts +3 -3
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +1 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +1 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +29 -2
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +72 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +85 -41
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +46 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -4
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +6 -6
- package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -3
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/render.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +45 -0
- package/dist/extensions/pi-subagents/tsconfig.runner.json +17 -0
- package/dist/extensions/pi-web-agent/package.json +31 -0
- package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -0
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -0
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -0
- package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -0
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +133 -0
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -0
- package/dist/extensions/pi-web-agent/src/extension.ts +126 -0
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -0
- package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -0
- package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -0
- package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -0
- package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -0
- package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -0
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -0
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -0
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -0
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -0
- package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -0
- package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -0
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -0
- package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -0
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +208 -0
- package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -0
- package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -0
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -0
- package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -0
- package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -0
- package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -0
- package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -0
- package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -0
- package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -0
- package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -0
- package/dist/extensions/pi-web-agent/src/types.ts +88 -0
- package/dist/extensions/ponytail/index.js +189 -0
- package/dist/extensions/ponytail/package.json +8 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +122 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +94 -0
- package/dist/extensions/ponytail/test/extension.test.js +167 -0
- package/dist/extensions/ponytail/test/helpers.test.js +92 -0
- package/dist/extensions/question/constants.ts +38 -0
- package/dist/extensions/question/dialog-adapter.ts +74 -0
- package/dist/extensions/question/helpers.ts +102 -0
- package/dist/extensions/question/index.ts +671 -0
- package/dist/extensions/question/navigation.ts +14 -0
- package/dist/extensions/question/package.json +18 -0
- package/dist/extensions/question/question-list.ts +498 -0
- package/dist/extensions/question/row-layout.ts +162 -0
- package/dist/extensions/question/schemas.ts +69 -0
- package/dist/extensions/question/selection-mode.ts +49 -0
- package/dist/extensions/question/shortcuts.ts +53 -0
- package/dist/extensions/question/tests/helpers.test.ts +178 -0
- package/dist/extensions/question/tests/question-list.test.ts +230 -0
- package/dist/extensions/question/tests/row-layout.test.ts +136 -0
- package/dist/extensions/question/tests/shortcuts.test.ts +85 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +143 -0
- package/dist/extensions/question/tui-adapter.ts +33 -0
- package/dist/extensions/question/types.ts +102 -0
- package/dist/extensions/question/ui-protocol.ts +46 -0
- package/dist/extensions/tokenin-onboarding.ts +265 -2
- package/dist/extensions/web-agent-onboarding.ts +222 -0
- package/dist/extensions/workflow/adapter.ts +588 -0
- package/dist/extensions/workflow/extension.ts +24 -0
- package/dist/extensions/workflow/modes/prototype.ts +156 -0
- package/dist/extensions/workflow/modes/quick.ts +138 -0
- package/dist/extensions/workflow/package.json +17 -0
- package/dist/extensions/workflow/state-machine.ts +386 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +57 -13
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +24 -1
- package/dist/utils/mime.js.map +1 -1
- package/docs/docs.json +4 -0
- package/docs/workflows.md +206 -0
- package/package.json +5 -1
- package/README.md +0 -198
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
filterSkillBodyForMode,
|
|
9
|
+
parsePonytailCommand,
|
|
10
|
+
readDefaultMode,
|
|
11
|
+
resolveSessionMode,
|
|
12
|
+
writeDefaultMode,
|
|
13
|
+
} from "../index.js";
|
|
14
|
+
|
|
15
|
+
test("parsePonytailCommand falls back to full when invoked bare and default is off", () => {
|
|
16
|
+
assert.deepEqual(parsePonytailCommand("", "off"), { type: "set-mode", mode: "full" });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("parsePonytailCommand parses modes, status, and default subcommand", () => {
|
|
20
|
+
assert.deepEqual(parsePonytailCommand("ultra", "full"), { type: "set-mode", mode: "ultra" });
|
|
21
|
+
assert.deepEqual(parsePonytailCommand("status", "full"), { type: "status" });
|
|
22
|
+
assert.deepEqual(parsePonytailCommand("default lite", "full"), { type: "set-default", mode: "lite" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("resolveSessionMode prefers latest persisted session mode", () => {
|
|
26
|
+
const entries = [
|
|
27
|
+
{ type: "custom", customType: "ponytail-mode", data: { mode: "lite" } },
|
|
28
|
+
{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
assert.equal(resolveSessionMode(entries, "full"), "ultra");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("resolveSessionMode returns fallback when entries is not an array", () => {
|
|
35
|
+
assert.equal(resolveSessionMode(null, "ultra"), "ultra");
|
|
36
|
+
assert.equal(resolveSessionMode(undefined, "lite"), "lite");
|
|
37
|
+
assert.equal(resolveSessionMode({}, "full"), "full");
|
|
38
|
+
assert.equal(resolveSessionMode("not an array"), "full"); // DEFAULT_MODE fallback
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("readDefaultMode and writeDefaultMode use XDG config path", () => {
|
|
42
|
+
const tempDir = mkdtempSync(join(tmpdir(), "ponytail-config-"));
|
|
43
|
+
const previousXdg = process.env.XDG_CONFIG_HOME;
|
|
44
|
+
const previousDefault = process.env.PONYTAIL_DEFAULT_MODE;
|
|
45
|
+
const configPath = join(tempDir, "ponytail", "config.json");
|
|
46
|
+
process.env.XDG_CONFIG_HOME = tempDir;
|
|
47
|
+
delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
assert.equal(readDefaultMode(), "full");
|
|
51
|
+
assert.equal(writeDefaultMode("ultra"), "ultra");
|
|
52
|
+
assert.equal(readDefaultMode(), "ultra");
|
|
53
|
+
assert.ok(existsSync(configPath));
|
|
54
|
+
assert.deepEqual(JSON.parse(readFileSync(configPath, "utf8")), { defaultMode: "ultra" });
|
|
55
|
+
} finally {
|
|
56
|
+
if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
57
|
+
else process.env.XDG_CONFIG_HOME = previousXdg;
|
|
58
|
+
if (previousDefault === undefined) delete process.env.PONYTAIL_DEFAULT_MODE;
|
|
59
|
+
else process.env.PONYTAIL_DEFAULT_MODE = previousDefault;
|
|
60
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("filterSkillBodyForMode keeps only requested intensity examples and rows", () => {
|
|
65
|
+
const body = `---\nname: ponytail\n---\n| **lite** | keep lite |\n| **full** | keep full |\n| **ultra** | keep ultra |\n- lite: Lite example\n- full: Full example\n- ultra: Ultra example\nOther line`;
|
|
66
|
+
|
|
67
|
+
const filtered = filterSkillBodyForMode(body, "ultra");
|
|
68
|
+
|
|
69
|
+
assert.ok(!filtered.includes("keep lite"));
|
|
70
|
+
assert.ok(!filtered.includes("keep full"));
|
|
71
|
+
assert.ok(filtered.includes("keep ultra"));
|
|
72
|
+
assert.ok(!filtered.includes("Lite example"));
|
|
73
|
+
assert.ok(filtered.includes("Ultra example"));
|
|
74
|
+
assert.ok(filtered.includes("Other line"));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("filterSkillBodyForMode keeps rule bullets that contain a colon", () => {
|
|
78
|
+
// Regression: rule bullets outside the Intensity section (e.g. the
|
|
79
|
+
// "No unrequested abstractions:" rule or the `ponytail:` comment convention)
|
|
80
|
+
// contain a colon and must not be mistaken for mode-example lines.
|
|
81
|
+
const skillPath = new URL("../../../skills/ponytail/SKILL.md", import.meta.url);
|
|
82
|
+
const body = readFileSync(skillPath, "utf8");
|
|
83
|
+
|
|
84
|
+
const filtered = filterSkillBodyForMode(body, "full");
|
|
85
|
+
|
|
86
|
+
assert.ok(filtered.includes("No unrequested abstractions"));
|
|
87
|
+
assert.ok(filtered.includes("Mark deliberate simplifications"));
|
|
88
|
+
// The Intensity examples are still filtered down to the active mode.
|
|
89
|
+
assert.ok(filtered.includes('full: "`@lru_cache'));
|
|
90
|
+
assert.ok(!filtered.includes('lite: "Done'));
|
|
91
|
+
assert.ok(!filtered.includes('ultra: "No cache'));
|
|
92
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// All constants for the question extension.
|
|
2
|
+
|
|
3
|
+
import { Key } from "@earendil-works/pi-tui";
|
|
4
|
+
import type { ResolvedShortcut } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
export const QUESTION_STATUS_KEY = "question";
|
|
7
|
+
export const QUESTION_VERSION = "1";
|
|
8
|
+
|
|
9
|
+
export const OVERLAY_MAX_HEIGHT_RATIO = 0.85;
|
|
10
|
+
export const OVERLAY_WIDTH: `${number}%` = "92%";
|
|
11
|
+
export const OVERLAY_OVERLAY_MAX_HEIGHT: `${number}%` = "85%";
|
|
12
|
+
export const OVERLAY_MIN_WIDTH = 40;
|
|
13
|
+
|
|
14
|
+
export const SPLIT_PANE_MIN_WIDTH = 84;
|
|
15
|
+
export const SPLIT_PANE_LEFT_MIN_WIDTH = 32;
|
|
16
|
+
export const SPLIT_PANE_RIGHT_MIN_WIDTH = 28;
|
|
17
|
+
export const SPLIT_PANE_SEPARATOR = " │ ";
|
|
18
|
+
|
|
19
|
+
export const FREEFORM_LABEL = "Type custom answer — enter a custom response";
|
|
20
|
+
export const COMMENT_TOGGLE_LABEL = "Add extra context after selection";
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_OVERLAY_TOGGLE_KEY = "alt+o";
|
|
23
|
+
export const DEFAULT_COMMENT_TOGGLE_KEY = "ctrl+g";
|
|
24
|
+
|
|
25
|
+
export const BOX_BORDER_LEFT = "│ ";
|
|
26
|
+
export const BOX_BORDER_RIGHT = " │";
|
|
27
|
+
export const BOX_BORDER_OVERHEAD = BOX_BORDER_LEFT.length + BOX_BORDER_RIGHT.length;
|
|
28
|
+
|
|
29
|
+
export const SHORTCUT_DISABLE_VALUES = new Set(["off", "none", "disabled", ""]);
|
|
30
|
+
|
|
31
|
+
export const DISABLED_SHORTCUT: ResolvedShortcut = {
|
|
32
|
+
disabled: true,
|
|
33
|
+
spec: null,
|
|
34
|
+
matches: ((_data: string) => false) as (data: string) => false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const VIM_UP = Key.ctrl("k");
|
|
38
|
+
export const VIM_DOWN = Key.ctrl("j");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Dialog/RPC fallback adapter — used when ctx.hasUI is false or custom() returns undefined.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildCommentPrompt,
|
|
5
|
+
createFreeformResponse,
|
|
6
|
+
createSelectionResponse,
|
|
7
|
+
formatOptionsForMessage,
|
|
8
|
+
isCancelled,
|
|
9
|
+
parseDialogSelections,
|
|
10
|
+
} from "./helpers.ts";
|
|
11
|
+
import type { QuestionOption, QuestionResponse, ResolvedQuestionParams } from "./types.ts";
|
|
12
|
+
import type { FallbackProtocol, UIProtocol } from "./ui-protocol.ts";
|
|
13
|
+
|
|
14
|
+
const FREEFORM_DIALOG_OPTION = "✏️ Type custom response...";
|
|
15
|
+
|
|
16
|
+
async function askViaDialogs(
|
|
17
|
+
ui: Pick<UIProtocol, "select" | "input">,
|
|
18
|
+
question: string,
|
|
19
|
+
context: string | undefined,
|
|
20
|
+
options: QuestionOption[],
|
|
21
|
+
allowMultiple: boolean,
|
|
22
|
+
allowFreeform: boolean,
|
|
23
|
+
allowComment: boolean,
|
|
24
|
+
timeout?: number,
|
|
25
|
+
): Promise<QuestionResponse | null> {
|
|
26
|
+
const dialogOpts = timeout ? { timeout } : undefined;
|
|
27
|
+
const prompt = context ? `${question}\n\nContext:\n${context}` : question;
|
|
28
|
+
|
|
29
|
+
if (allowMultiple) {
|
|
30
|
+
const optionList = formatOptionsForMessage(options);
|
|
31
|
+
const raw = await ui.input(
|
|
32
|
+
`${prompt}\n\nOptions (select one or more):\n${optionList}`,
|
|
33
|
+
"Type your selection(s)...",
|
|
34
|
+
dialogOpts,
|
|
35
|
+
);
|
|
36
|
+
if (isCancelled(raw)) return null;
|
|
37
|
+
const selections = parseDialogSelections(raw);
|
|
38
|
+
if (selections.length === 0) return null;
|
|
39
|
+
if (!allowComment) return createSelectionResponse(selections);
|
|
40
|
+
const comment = await ui.input(buildCommentPrompt(prompt, selections), "Optional comment (press Enter to skip)...", dialogOpts);
|
|
41
|
+
return createSelectionResponse(selections, comment);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const selectOptions = options.map((o) => o.label);
|
|
45
|
+
if (allowFreeform) selectOptions.push(FREEFORM_DIALOG_OPTION);
|
|
46
|
+
|
|
47
|
+
const selected = await ui.select(prompt, selectOptions, dialogOpts);
|
|
48
|
+
if (isCancelled(selected)) return null;
|
|
49
|
+
|
|
50
|
+
if (selected === FREEFORM_DIALOG_OPTION) {
|
|
51
|
+
const answer = await ui.input(prompt, "Type your answer...", dialogOpts);
|
|
52
|
+
if (isCancelled(answer)) return null;
|
|
53
|
+
return createFreeformResponse(answer);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!allowComment) return createSelectionResponse([selected]);
|
|
57
|
+
const comment = await ui.input(buildCommentPrompt(prompt, [selected]), "Optional comment (press Enter to skip)...", dialogOpts);
|
|
58
|
+
return createSelectionResponse([selected], comment);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DialogFallback: FallbackProtocol = {
|
|
62
|
+
async ask(params, protocol) {
|
|
63
|
+
return askViaDialogs(
|
|
64
|
+
protocol,
|
|
65
|
+
params.question,
|
|
66
|
+
params.context,
|
|
67
|
+
params.options,
|
|
68
|
+
params.allowMultiple,
|
|
69
|
+
params.allowFreeform,
|
|
70
|
+
params.allowComment,
|
|
71
|
+
params.timeout,
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Pure helper functions for the question extension.
|
|
2
|
+
|
|
3
|
+
import type { MarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
import type { QuestionOption, QuestionResponse, RawOption } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
export function getOptionsFormatError(options: RawOption[] | undefined): string | null {
|
|
8
|
+
for (const option of options ?? []) {
|
|
9
|
+
if (typeof option !== "string") continue;
|
|
10
|
+
const text = option.trim();
|
|
11
|
+
if (!text.startsWith("[") && !text.startsWith("{")) continue;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(text);
|
|
14
|
+
const jsonOption = (value: unknown) => !!value && typeof value === "object" && typeof (value as { label?: unknown }).label === "string";
|
|
15
|
+
if (jsonOption(parsed) || (Array.isArray(parsed) && parsed.some(jsonOption))) {
|
|
16
|
+
return 'Invalid question.options: pass an array of strings or {label, description} objects, not a JSON-encoded string. Example: {"options":[{"label":"Yes","description":"..."}]}, not {"options":["[{\\"label\\":\\"Yes\\"}]"]}.';
|
|
17
|
+
}
|
|
18
|
+
} catch {}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeOptions(options: RawOption[] | undefined): QuestionOption[] {
|
|
24
|
+
return (options ?? [])
|
|
25
|
+
.map((option) =>
|
|
26
|
+
typeof option === "string"
|
|
27
|
+
? { label: option }
|
|
28
|
+
: { label: option.label, description: option.description },
|
|
29
|
+
)
|
|
30
|
+
.filter((option) => option.label.trim().length > 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function oneLine(value: unknown, max = 100): string {
|
|
34
|
+
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
35
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ponytail: lazy import avoids hard dependency on pi-coding-agent at module load
|
|
39
|
+
// time (it may not be resolvable in dev/test environments). The function is
|
|
40
|
+
// only called at runtime when the TUI is active.
|
|
41
|
+
let _getMarkdownTheme: (() => MarkdownTheme) | null | undefined;
|
|
42
|
+
export function safeMarkdownTheme(): MarkdownTheme | undefined {
|
|
43
|
+
try {
|
|
44
|
+
if (_getMarkdownTheme === undefined) {
|
|
45
|
+
// Dynamic require avoids breaking module load when package is absent.
|
|
46
|
+
_getMarkdownTheme = (globalThis as any).require
|
|
47
|
+
? (globalThis as any).require("@earendil-works/pi-coding-agent").getMarkdownTheme
|
|
48
|
+
: null;
|
|
49
|
+
}
|
|
50
|
+
if (!_getMarkdownTheme) return undefined;
|
|
51
|
+
const md = _getMarkdownTheme();
|
|
52
|
+
if (!md) return undefined;
|
|
53
|
+
md.bold("");
|
|
54
|
+
return md;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createFreeformResponse(text: string | null | undefined): QuestionResponse | null {
|
|
61
|
+
const trimmed = text?.trim();
|
|
62
|
+
return trimmed ? { kind: "freeform", text: trimmed } : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createSelectionResponse(selections: string[], comment?: string | null): QuestionResponse | null {
|
|
66
|
+
const norm = selections.map((s) => s.trim()).filter(Boolean);
|
|
67
|
+
if (norm.length === 0) return null;
|
|
68
|
+
const c = comment?.trim();
|
|
69
|
+
return c ? { kind: "selection", selections: norm, comment: c } : { kind: "selection", selections: norm };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function formatResponseSummary(response: QuestionResponse): string {
|
|
73
|
+
if (response.kind === "freeform") return response.text;
|
|
74
|
+
const selections = response.selections.join(", ");
|
|
75
|
+
return response.comment ? `${selections} — ${response.comment}` : selections;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function formatOptionsForMessage(options: QuestionOption[]): string {
|
|
79
|
+
return options
|
|
80
|
+
.map((option, index) => {
|
|
81
|
+
const desc = option.description ? ` — ${option.description}` : "";
|
|
82
|
+
return `${index + 1}. ${option.label}${desc}`;
|
|
83
|
+
})
|
|
84
|
+
.join("\n");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function buildCommentPrompt(prompt: string, selections: string[]): string {
|
|
88
|
+
const label = selections.length === 1 ? "Selected option" : "Selected options";
|
|
89
|
+
const lines = selections.map((selection) => `- ${selection}`).join("\n");
|
|
90
|
+
return `${prompt}\n\n${label}:\n${lines}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function parseDialogSelections(input: string): string[] {
|
|
94
|
+
return input
|
|
95
|
+
.split(",")
|
|
96
|
+
.map((selection) => selection.trim())
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isCancelled(value: unknown): value is null | undefined {
|
|
101
|
+
return value === null || value === undefined;
|
|
102
|
+
}
|