@selesai/code 0.8.5 → 0.8.6
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 +12 -0
- package/dist/defaults/models.json +5 -0
- package/dist/extensions/agent-browser.test.ts +178 -2
- package/dist/extensions/agent-browser.ts +2 -0
- package/dist/extensions/copy-turn.test.ts +229 -0
- package/dist/extensions/copy-turn.ts +11 -0
- package/dist/extensions/grep-app/index.test.ts +356 -1
- package/dist/extensions/grep-app/index.ts +6 -1
- package/dist/extensions/handoff-new.test.ts +351 -161
- package/dist/extensions/handoff-new.ts +3 -0
- package/dist/extensions/inline-skills.test.ts +93 -0
- package/dist/extensions/inline-skills.ts +3 -0
- package/dist/extensions/pi-subagents/agents/builder.md +5 -2
- package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +8 -0
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +1 -1
- package/dist/extensions/pi-subagents/src/extension/config.ts +6 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +83 -14
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +19 -1
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +39 -9
- package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +16 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +1 -1
- package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
- package/dist/extensions/ponytail/index.js +11 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
- package/dist/extensions/ponytail/test/extension.test.js +274 -140
- package/dist/extensions/ponytail/test/helpers.test.js +280 -92
- package/dist/extensions/question/index.ts +18 -0
- package/dist/extensions/question/question-list.ts +22 -0
- package/dist/extensions/question/tests/batch.test.ts +103 -65
- package/dist/extensions/question/tests/helpers.test.ts +68 -35
- package/dist/extensions/question/tests/question-list.test.ts +506 -204
- package/dist/extensions/question/tests/row-layout.test.ts +180 -114
- package/dist/extensions/question/tests/schemas.test.ts +42 -19
- package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
- package/dist/extensions/question/tests/wizard.test.ts +771 -50
- package/dist/extensions/rtk.test.ts +180 -1
- package/dist/extensions/tokenin-onboarding.ts +3 -0
- package/dist/extensions/undo.test.ts +720 -0
- package/dist/extensions/undo.ts +6 -0
- package/dist/extensions/web-agent-onboarding.test.ts +415 -0
- package/dist/extensions/workflow/extension.ts +3 -3
- package/dist/extensions/workflow/modes.ts +41 -19
- package/dist/skills/pi-subagents/references/execution-controls.md +1 -1
- package/docs/plans/workflow-autoloop-reference.md +1 -2
- package/docs/plans/workflow-handoff-carryover.md +108 -0
- package/package.json +4 -1
|
@@ -1,75 +1,113 @@
|
|
|
1
|
-
import
|
|
2
|
-
import assert from "node:assert/strict";
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
3
2
|
|
|
4
3
|
import { buildQuestionAnswers, prepareQuestions, saveDraftQuestion } from "../batch.ts";
|
|
5
4
|
import type { DraftQuestionState } from "../types.ts";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
describe("question batch helpers", () => {
|
|
7
|
+
it("prepareQuestions: normalizes typed questions and generated ids", () => {
|
|
8
|
+
const result = prepareQuestions([
|
|
9
|
+
{ type: "select", question: "Pick a color", options: [{ value: "red", label: "Red" }, { value: "blue", label: "Blue" }] },
|
|
10
|
+
{ type: "text", id: "notes", label: "Notes", question: "Anything else?" },
|
|
11
|
+
]);
|
|
12
|
+
expect("questions" in result).toBe(true);
|
|
13
|
+
if (!("questions" in result)) return;
|
|
14
|
+
expect(result.questions).toEqual([
|
|
15
|
+
{
|
|
16
|
+
id: "q1",
|
|
17
|
+
label: "Q1",
|
|
18
|
+
question: "Pick a color",
|
|
19
|
+
context: undefined,
|
|
20
|
+
type: "select",
|
|
21
|
+
options: [
|
|
22
|
+
{ value: "red", label: "Red", description: undefined },
|
|
23
|
+
{ value: "blue", label: "Blue", description: undefined },
|
|
24
|
+
],
|
|
25
|
+
allowOther: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "notes",
|
|
29
|
+
label: "Notes",
|
|
30
|
+
question: "Anything else?",
|
|
31
|
+
context: undefined,
|
|
32
|
+
type: "text",
|
|
33
|
+
options: [],
|
|
34
|
+
allowOther: false,
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("prepareQuestions: rejects empty batches, generated-id collisions, and duplicate option values", () => {
|
|
40
|
+
expect(prepareQuestions([])).toEqual({ error: "questions must contain at least one question." });
|
|
41
|
+
expect((prepareQuestions([
|
|
42
|
+
{ type: "text", id: "q2", question: "One" },
|
|
43
|
+
{ type: "text", question: "Two" },
|
|
44
|
+
]) as { error: string }).error).toMatch(/duplicate id: q2/);
|
|
45
|
+
expect((prepareQuestions([{
|
|
20
46
|
type: "select",
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
question: "Choose",
|
|
48
|
+
options: [{ value: "same", label: "A" }, { value: "same", label: "B" }],
|
|
49
|
+
}]) as { error: string }).error).toMatch(/duplicate value: same/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("prepareQuestions: rejects missing question, bad type, empty options, and bad option fields", () => {
|
|
53
|
+
expect((prepareQuestions([{ type: "text" }]) as { error: string }).error).toMatch(/non-empty question/);
|
|
54
|
+
expect((prepareQuestions([{ type: "radio", question: "X", options: [{ value: "a", label: "A" }] }]) as { error: string }).error).toMatch(/type must be select, multiselect, or text/);
|
|
55
|
+
expect((prepareQuestions([{ type: "select", question: "X", options: [] }]) as { error: string }).error).toMatch(/at least one option/);
|
|
56
|
+
expect((prepareQuestions([{ type: "select", question: "X", options: [{ value: "", label: "A" }] }]) as { error: string }).error).toMatch(/value and label must be non-empty/);
|
|
57
|
+
expect((prepareQuestions([{ type: "select", question: "X", options: [{ value: "a", label: "" }] }]) as { error: string }).error).toMatch(/value and label must be non-empty/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("prepareQuestions: trims id/label/context and defaults allowOther", () => {
|
|
61
|
+
const result = prepareQuestions([
|
|
62
|
+
{ type: "multiselect", id: " m1 ", label: " Multi ", question: " Pick many ", context: " ctx ", options: [{ value: "a", label: "A", description: " desc " }] },
|
|
63
|
+
]);
|
|
64
|
+
expect("questions" in result).toBe(true);
|
|
65
|
+
if (!("questions" in result)) return;
|
|
66
|
+
expect(result.questions[0]).toEqual({
|
|
67
|
+
id: "m1",
|
|
68
|
+
label: "Multi",
|
|
69
|
+
question: "Pick many",
|
|
70
|
+
context: "ctx",
|
|
71
|
+
type: "multiselect",
|
|
72
|
+
options: [{ value: "a", label: "A", description: "desc" }],
|
|
34
73
|
allowOther: false,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
38
76
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
]) as { error: string }).error, /duplicate id: q2/);
|
|
45
|
-
assert.match((prepareQuestions([{
|
|
46
|
-
type: "select",
|
|
47
|
-
question: "Choose",
|
|
48
|
-
options: [{ value: "same", label: "A" }, { value: "same", label: "B" }],
|
|
49
|
-
}]) as { error: string }).error, /duplicate value: same/);
|
|
50
|
-
});
|
|
77
|
+
it("saveDraftQuestion: clearing an answer while navigating back leaves it unanswered", () => {
|
|
78
|
+
const states = new Map<string, DraftQuestionState>([["q1", { status: "answered", response: { kind: "text", text: "old" } }]]);
|
|
79
|
+
saveDraftQuestion(states, "q1", null, false);
|
|
80
|
+
expect(states.has("q1")).toBe(false);
|
|
81
|
+
});
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
83
|
+
it("saveDraftQuestion: blank answer with markBlankSkipped marks skipped", () => {
|
|
84
|
+
const states = new Map<string, DraftQuestionState>();
|
|
85
|
+
saveDraftQuestion(states, "q1", null, true);
|
|
86
|
+
expect(states.get("q1")).toEqual({ status: "skipped" });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("saveDraftQuestion: response marks answered", () => {
|
|
90
|
+
const states = new Map<string, DraftQuestionState>();
|
|
91
|
+
saveDraftQuestion(states, "q1", { kind: "text", text: "answer" }, true);
|
|
92
|
+
expect(states.get("q1")).toEqual({ status: "answered", response: { kind: "text", text: "answer" } });
|
|
93
|
+
});
|
|
57
94
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
it("buildQuestionAnswers: preserves answered, skipped, and unvisited states", () => {
|
|
96
|
+
const prepared = prepareQuestions([
|
|
97
|
+
{ type: "text", id: "a", question: "A" },
|
|
98
|
+
{ type: "text", id: "b", question: "B" },
|
|
99
|
+
{ type: "text", id: "c", question: "C" },
|
|
100
|
+
]);
|
|
101
|
+
expect("questions" in prepared).toBe(true);
|
|
102
|
+
if (!("questions" in prepared)) return;
|
|
103
|
+
const states = new Map<string, DraftQuestionState>([
|
|
104
|
+
["a", { status: "answered", response: { kind: "text", text: "answer" } }],
|
|
105
|
+
["b", { status: "skipped" }],
|
|
106
|
+
]);
|
|
107
|
+
expect(buildQuestionAnswers(prepared.questions, states)).toEqual([
|
|
108
|
+
{ id: "a", status: "answered", response: { kind: "text", text: "answer" } },
|
|
109
|
+
{ id: "b", status: "skipped" },
|
|
110
|
+
{ id: "c", status: "unanswered" },
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
75
113
|
});
|
|
@@ -1,53 +1,86 @@
|
|
|
1
|
-
import
|
|
2
|
-
import assert from "node:assert/strict";
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
createSelectionResponse,
|
|
6
5
|
createTextResponse,
|
|
7
6
|
formatQuestionResponse,
|
|
8
7
|
formatResponseSummary,
|
|
8
|
+
formatOptionsForMessage,
|
|
9
9
|
oneLine,
|
|
10
10
|
previewText,
|
|
11
|
+
trimText,
|
|
11
12
|
} from "../helpers.ts";
|
|
12
13
|
import type { PreparedQuestion } from "../types.ts";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
describe("question helpers", () => {
|
|
16
|
+
it("trimText trims and returns undefined for blank", () => {
|
|
17
|
+
expect(trimText(" ")).toBeUndefined();
|
|
18
|
+
expect(trimText(undefined)).toBeUndefined();
|
|
19
|
+
expect(trimText(null)).toBeUndefined();
|
|
20
|
+
expect(trimText(" hello ")).toBe("hello");
|
|
21
|
+
});
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
kind: "
|
|
23
|
-
values: ["a", "b"],
|
|
24
|
-
otherText: "other",
|
|
23
|
+
it("createTextResponse trims edges and rejects blank text", () => {
|
|
24
|
+
expect(createTextResponse(" ")).toBeNull();
|
|
25
|
+
expect(createTextResponse(undefined)).toBeNull();
|
|
26
|
+
expect(createTextResponse(" hello\nworld ")).toEqual({ kind: "text", text: "hello\nworld" });
|
|
25
27
|
});
|
|
26
|
-
});
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
it("createSelectionResponse supports values plus Other and rejects empty", () => {
|
|
30
|
+
expect(createSelectionResponse([], " ")).toBeNull();
|
|
31
|
+
expect(createSelectionResponse([], undefined)).toBeNull();
|
|
32
|
+
expect(createSelectionResponse([" a ", "b"], " other ")).toEqual({
|
|
33
|
+
kind: "selection",
|
|
34
|
+
values: ["a", "b"],
|
|
35
|
+
otherText: "other",
|
|
36
|
+
});
|
|
37
|
+
expect(createSelectionResponse(["a"], undefined)).toEqual({ kind: "selection", values: ["a"] });
|
|
38
|
+
});
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
type: "multiselect",
|
|
39
|
-
options: [{ value: "a", label: "Alpha" }, { value: "b", label: "Beta" }],
|
|
40
|
-
allowOther: true,
|
|
41
|
-
};
|
|
42
|
-
assert.equal(formatQuestionResponse(question, { kind: "selection", values: ["b"], otherText: "Gamma" }), "Beta, Other: Gamma");
|
|
43
|
-
});
|
|
40
|
+
it("formatResponseSummary returns machine values", () => {
|
|
41
|
+
expect(formatResponseSummary({ kind: "selection", values: ["a", "b"], otherText: "c" })).toBe("a, b, c");
|
|
42
|
+
expect(formatResponseSummary({ kind: "selection", values: ["a"] })).toBe("a");
|
|
43
|
+
expect(formatResponseSummary({ kind: "text", text: "hello" })).toBe("hello");
|
|
44
|
+
});
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
it("formatQuestionResponse maps stable values to labels", () => {
|
|
47
|
+
const question: PreparedQuestion = {
|
|
48
|
+
id: "q1",
|
|
49
|
+
label: "Q1",
|
|
50
|
+
question: "Pick",
|
|
51
|
+
type: "multiselect",
|
|
52
|
+
options: [{ value: "a", label: "Alpha" }, { value: "b", label: "Beta" }],
|
|
53
|
+
allowOther: true,
|
|
54
|
+
};
|
|
55
|
+
expect(formatQuestionResponse(question, { kind: "selection", values: ["b"], otherText: "Gamma" })).toBe("Beta, Other: Gamma");
|
|
56
|
+
expect(formatQuestionResponse(question, { kind: "selection", values: ["unknown"] })).toBe("unknown");
|
|
57
|
+
expect(formatQuestionResponse(question, { kind: "text", text: "plain" })).toBe("plain");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("formatOptionsForMessage numbers options with descriptions", () => {
|
|
61
|
+
expect(formatOptionsForMessage([
|
|
62
|
+
{ value: "a", label: "Alpha" },
|
|
63
|
+
{ value: "b", label: "Beta", description: "the beta" },
|
|
64
|
+
])).toBe("1. Alpha\n2. Beta — the beta");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("previewText bounds multiline summaries", () => {
|
|
68
|
+
expect(previewText("one\ntwo\nthree", 160, 2)).toBe("one\ntwo…");
|
|
69
|
+
expect(previewText("a".repeat(200), 10, 2)).toBe(`${"a".repeat(9)}…`);
|
|
70
|
+
expect(previewText("short", 160, 2)).toBe("short");
|
|
71
|
+
expect(previewText("", 160, 2)).toBe("");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("oneLine collapses whitespace and truncates", () => {
|
|
75
|
+
expect(oneLine(" hello world ")).toBe("hello world");
|
|
76
|
+
expect(oneLine("a".repeat(20), 10)).toBe(`${"a".repeat(9)}…`);
|
|
77
|
+
expect(oneLine(undefined)).toBe("");
|
|
78
|
+
expect(oneLine(42)).toBe("42");
|
|
79
|
+
});
|
|
49
80
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
it("previewText handles single-line and exact-boundary text", () => {
|
|
82
|
+
expect(previewText("one line", 160, 2)).toBe("one line");
|
|
83
|
+
expect(previewText("a".repeat(10), 10, 2)).toBe("a".repeat(10));
|
|
84
|
+
expect(previewText("a".repeat(11), 10, 2)).toBe(`${"a".repeat(9)}…`);
|
|
85
|
+
});
|
|
53
86
|
});
|