@selesai/code 0.5.16 → 0.5.18

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interactive-mode.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-mode.test.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport { InteractiveMode } from \"./interactive-mode.js\";\n\ndescribe(\"InteractiveMode handleEvent tool_execution_update\", () => {\n\tit(\"refreshes displayed args from execution update\", () => {\n\t\tconst component = {\n\t\t\tupdateArgs: vi.fn(),\n\t\t\tupdateResult: vi.fn(),\n\t\t};\n\t\tconst requestRender = vi.fn();\n\n\t\tconst mode = Object.create(InteractiveMode.prototype) as InteractiveMode;\n\t\tmode.isInitialized = true;\n\t\tmode.footer = { invalidate: vi.fn() } as any;\n\t\tmode.pendingTools = new Map([[\"tc-1\", component as any]]);\n\t\tmode.ui = { requestRender } as any as InteractiveMode[\"ui\"];\n\n\t\tmode.handleEvent({\n\t\t\ttype: \"tool_execution_update\",\n\t\t\ttoolCallId: \"tc-1\",\n\t\t\ttoolName: \"bash\",\n\t\t\targs: { command: \"rtk ls -la\" },\n\t\t\tpartialResult: { content: [], details: undefined },\n\t\t} as any);\n\n\t\texpect(component.updateArgs).toHaveBeenCalledTimes(1);\n\t\texpect(component.updateArgs).toHaveBeenCalledWith({ command: \"rtk ls -la\" });\n\t\texpect(component.updateResult).toHaveBeenCalledTimes(1);\n\t\texpect(component.updateResult).toHaveBeenCalledWith(\n\t\t\t{ content: [], details: undefined, isError: false },\n\t\t\ttrue,\n\t\t);\n\t\texpect(requestRender).toHaveBeenCalledTimes(1);\n\t});\n\n\tit(\"is a no-op when update references an unknown tool\", () => {\n\t\tconst requestRender = vi.fn();\n\t\tconst mode = Object.create(InteractiveMode.prototype) as InteractiveMode;\n\t\tmode.isInitialized = true;\n\t\tmode.footer = { invalidate: vi.fn() } as any;\n\t\tmode.pendingTools = new Map();\n\t\tmode.ui = { requestRender } as any as InteractiveMode[\"ui\"];\n\n\t\tmode.handleEvent({\n\t\t\ttype: \"tool_execution_update\",\n\t\t\ttoolCallId: \"missing\",\n\t\t\ttoolName: \"bash\",\n\t\t\targs: { command: \"rtk ls -la\" },\n\t\t\tpartialResult: { content: [], details: undefined },\n\t\t} as any);\n\n\t\texpect(requestRender).not.toHaveBeenCalled();\n\t});\n});\n"]}
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { InteractiveMode } from "./interactive-mode.js";
3
+ describe("InteractiveMode handleEvent tool_execution_update", () => {
4
+ it("refreshes displayed args from execution update", () => {
5
+ const component = {
6
+ updateArgs: vi.fn(),
7
+ updateResult: vi.fn(),
8
+ };
9
+ const requestRender = vi.fn();
10
+ const mode = Object.create(InteractiveMode.prototype);
11
+ mode.isInitialized = true;
12
+ mode.footer = { invalidate: vi.fn() };
13
+ mode.pendingTools = new Map([["tc-1", component]]);
14
+ mode.ui = { requestRender };
15
+ mode.handleEvent({
16
+ type: "tool_execution_update",
17
+ toolCallId: "tc-1",
18
+ toolName: "bash",
19
+ args: { command: "rtk ls -la" },
20
+ partialResult: { content: [], details: undefined },
21
+ });
22
+ expect(component.updateArgs).toHaveBeenCalledTimes(1);
23
+ expect(component.updateArgs).toHaveBeenCalledWith({ command: "rtk ls -la" });
24
+ expect(component.updateResult).toHaveBeenCalledTimes(1);
25
+ expect(component.updateResult).toHaveBeenCalledWith({ content: [], details: undefined, isError: false }, true);
26
+ expect(requestRender).toHaveBeenCalledTimes(1);
27
+ });
28
+ it("is a no-op when update references an unknown tool", () => {
29
+ const requestRender = vi.fn();
30
+ const mode = Object.create(InteractiveMode.prototype);
31
+ mode.isInitialized = true;
32
+ mode.footer = { invalidate: vi.fn() };
33
+ mode.pendingTools = new Map();
34
+ mode.ui = { requestRender };
35
+ mode.handleEvent({
36
+ type: "tool_execution_update",
37
+ toolCallId: "missing",
38
+ toolName: "bash",
39
+ args: { command: "rtk ls -la" },
40
+ partialResult: { content: [], details: undefined },
41
+ });
42
+ expect(requestRender).not.toHaveBeenCalled();
43
+ });
44
+ });
45
+ //# sourceMappingURL=interactive-mode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-mode.test.js","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,QAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE,CAAC;IACnE,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG;YACjB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;YACnB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;SACrB,CAAC;QACF,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAE9B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAoB,CAAC;QACzE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,EAAS,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,SAAgB,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,aAAa,EAAkC,CAAC;QAE5D,IAAI,CAAC,WAAW,CAAC;YAChB,IAAI,EAAE,uBAAuB;YAC7B,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE;YAC/B,aAAa,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;SAC3C,CAAC,CAAC;QAEV,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;QAC7E,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,oBAAoB,CAClD,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EACnD,IAAI,CACJ,CAAC;QACF,MAAM,CAAC,aAAa,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAAA,CAC/C,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE,CAAC;QAC7D,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAoB,CAAC;QACzE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,EAAS,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,EAAE,GAAG,EAAE,aAAa,EAAkC,CAAC;QAE5D,IAAI,CAAC,WAAW,CAAC;YAChB,IAAI,EAAE,uBAAuB;YAC7B,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE;YAC/B,aAAa,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;SAC3C,CAAC,CAAC;QAEV,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAAA,CAC7C,CAAC,CAAC;AAAA,CACH,CAAC,CAAC","sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport { InteractiveMode } from \"./interactive-mode.js\";\n\ndescribe(\"InteractiveMode handleEvent tool_execution_update\", () => {\n\tit(\"refreshes displayed args from execution update\", () => {\n\t\tconst component = {\n\t\t\tupdateArgs: vi.fn(),\n\t\t\tupdateResult: vi.fn(),\n\t\t};\n\t\tconst requestRender = vi.fn();\n\n\t\tconst mode = Object.create(InteractiveMode.prototype) as InteractiveMode;\n\t\tmode.isInitialized = true;\n\t\tmode.footer = { invalidate: vi.fn() } as any;\n\t\tmode.pendingTools = new Map([[\"tc-1\", component as any]]);\n\t\tmode.ui = { requestRender } as any as InteractiveMode[\"ui\"];\n\n\t\tmode.handleEvent({\n\t\t\ttype: \"tool_execution_update\",\n\t\t\ttoolCallId: \"tc-1\",\n\t\t\ttoolName: \"bash\",\n\t\t\targs: { command: \"rtk ls -la\" },\n\t\t\tpartialResult: { content: [], details: undefined },\n\t\t} as any);\n\n\t\texpect(component.updateArgs).toHaveBeenCalledTimes(1);\n\t\texpect(component.updateArgs).toHaveBeenCalledWith({ command: \"rtk ls -la\" });\n\t\texpect(component.updateResult).toHaveBeenCalledTimes(1);\n\t\texpect(component.updateResult).toHaveBeenCalledWith(\n\t\t\t{ content: [], details: undefined, isError: false },\n\t\t\ttrue,\n\t\t);\n\t\texpect(requestRender).toHaveBeenCalledTimes(1);\n\t});\n\n\tit(\"is a no-op when update references an unknown tool\", () => {\n\t\tconst requestRender = vi.fn();\n\t\tconst mode = Object.create(InteractiveMode.prototype) as InteractiveMode;\n\t\tmode.isInitialized = true;\n\t\tmode.footer = { invalidate: vi.fn() } as any;\n\t\tmode.pendingTools = new Map();\n\t\tmode.ui = { requestRender } as any as InteractiveMode[\"ui\"];\n\n\t\tmode.handleEvent({\n\t\t\ttype: \"tool_execution_update\",\n\t\t\ttoolCallId: \"missing\",\n\t\t\ttoolName: \"bash\",\n\t\t\targs: { command: \"rtk ls -la\" },\n\t\t\tpartialResult: { content: [], details: undefined },\n\t\t} as any);\n\n\t\texpect(requestRender).not.toHaveBeenCalled();\n\t});\n});\n"]}
@@ -33,7 +33,7 @@ Done when every proposed behavior has one owner: state machine, shared adapter,
33
33
  For a new mode, add `src/extensions/workflow/modes/<name>.ts`, modeled on `quick.ts`, with only `WorkflowConfig` and `WorkflowModeRegistration`:
34
34
 
35
35
  - ordered phases, phase artifacts, prompts, validators, close artifacts/validators;
36
- - unique mode/status/entry identities and start/resume/end tool plus slash-command names;
36
+ - unique mode/status/entry identities and slash-command name; shared start/resume/end tools select the mode;
37
37
  - prompts that name exact artifact paths and use `write_workflow_artifact` only for workflow artifacts.
38
38
 
39
39
  Register the mode once in `MODES` in `extension.ts`; document its lifecycle and commands in `docs/workflows.md`.
@@ -47,7 +47,7 @@ The shared adapter owns UUID artifact directories, atomic saves, resume, loop re
47
47
  - Persisted state changes after start, artifact/loop transition, resume reconciliation, and explicit end.
48
48
  - Never auto-resume on `session_start`; an explicit selector attaches a run.
49
49
  - Artifact completion advances durable state then stops the parent turn; the user deliberately continues the attached mode.
50
- - Terminal-ready stays active. Only `end_<mode>_workflow` marks the record completed and terminates.
50
+ - Terminal-ready stays active. Only `end_workflow({ mode })` marks the record completed and terminates.
51
51
  - One `ExtensionAPI` hosts all modes: shared writer once, stale reload handlers inert, one attached run total.
52
52
  - Builder/reviewer loops use adapter-owned rounds, review files, markers, and max-iteration pause.
53
53
 
package/docs/workflows.md CHANGED
@@ -18,10 +18,10 @@ src/extensions/workflow/
18
18
  ```
19
19
 
20
20
  - **`state-machine.ts`** is the deep module. It owns the phase graph, artifact gating, skip rules, the terminal close gate, and the reentrancy guard. It imports nothing external — no `node:fs`, no pi API, no `pi-tui`, no `typebox`. Every method returns a `WorkflowEffect` (a discriminated union in domain vocabulary) that the adapter pattern-matches on.
21
- - **`adapter.ts`** is the thin glue. It owns Pi/fs wiring, durable state, explicit resume, loop review persistence, and the git-based `reuse` skip predicate. Parent-written artifacts advance durable phase state. `prototype` and `quick` stop at user-controlled boundaries; `task` queues its build loop as soon as its plan is ready.
21
+ - **`adapter.ts`** is the thin glue. It owns Pi/fs wiring, durable state, explicit resume, loop review persistence, and the git-based `reuse` skip predicate. Parent-written artifacts advance durable phase state and queue hidden engine continuations; every built-in mode flows automatically.
22
22
  - **`workflow.json`** in each artifact directory is the canonical, versioned run record. It is atomically replaced after state changes; session custom entries are only pointers for UI/history and never reconstruct an active run.
23
- - **`extension.ts`** imports each mode's registration object and calls `createWorkflowExtension(config, options)(pi)` for each, so one extension load resolves a single shared writer tool + one start/end tool pair per mode.
24
- - **A mode file** is pure data: the phase list, the per-phase artifact filenames, the per-phase prompt generators, the terminal close artifacts, and identity strings (tool names, command name, status key, entry type). Prompts are functions that receive `{ artifactDir, userPrompt }` and return a string. Each mode exports a `WorkflowModeRegistration` object (e.g. `prototypeMode`, `quickMode`); it does not call `createWorkflowExtension` itself.
23
+ - **`extension.ts`** imports each mode's registration object and calls `createWorkflowExtension(config, options)(pi)` for each. One extension load registers one shared writer plus `start_workflow`, `resume_workflow`, and `end_workflow`; each lifecycle call selects a mode.
24
+ - **A mode file** is pure data: the phase list, per-phase artifact filenames, prompt generators, terminal close artifacts, and command/status/entry identities. Prompts receive `{ artifactDir, userPrompt }`. Each mode exports a `WorkflowModeRegistration` object (e.g. `prototypeMode`, `quickMode`); it does not call `createWorkflowExtension` itself.
25
25
 
26
26
  ## To add a future mode
27
27
 
@@ -91,16 +91,6 @@ export const rigorousMode: WorkflowModeRegistration = {
91
91
  commandName: "rigorous",
92
92
  commandDescription:
93
93
  "Run the rigorous workflow (grill → spec → research → plan → reuse → handoff → loop → audit → sign-off)",
94
- toolNames: {
95
- start: "start_rigorous_workflow",
96
- resume: "resume_rigorous_workflow",
97
- end: "end_rigorous_workflow",
98
- },
99
- toolLabels: {
100
- start: "Start Rigorous Workflow",
101
- resume: "Resume Rigorous Workflow",
102
- end: "End Rigorous Workflow",
103
- },
104
94
  };
105
95
 
106
96
  export default rigorousMode;
@@ -116,20 +106,20 @@ import { rigorousMode } from "./modes/rigorous.ts";
116
106
  const MODES = [prototypeMode, quickMode, rigorousMode] as const;
117
107
  ```
118
108
 
119
- That's it. The loader picks it up at boot (`package.json` loads only `./extension.ts`); start/resume/end tools and the `/rigorous` command are registered automatically. There is no `next` tool — phases auto-advance as artifacts land and only the `end` tool completes the terminal phase.
109
+ That's it. The loader picks it up at boot (`package.json` loads only `./extension.ts`); the shared lifecycle tools accept `mode: "rigorous"`, and the `/rigorous` command is registered automatically. There is no `next` tool — phases auto-advance as artifacts land and only `end_workflow({ mode: "rigorous" })` completes the terminal phase.
120
110
 
121
111
  ## Built-in modes
122
112
 
123
113
  ### `task` — plan → codebase exploration → handoff → build/review loop
124
114
 
125
- Task now follows the same phase shape as the other modes, minus grilling/research/audit: an architect subagent produces a validated `plan.md`, an optional explorer subagent produces `reuse.md`, a recapper subagent produces a validated `handoff.md`, and then a builder↔commentator review loop runs (max 3 blocking rounds). A clean review makes the workflow terminal-ready; `end_task_workflow` completes it.
115
+ Task now follows the same phase shape as the other modes, minus grilling/research/audit: an architect subagent produces a validated `plan.md`, an optional explorer subagent produces `reuse.md`, a recapper subagent produces a validated `handoff.md`, and then a builder↔commentator review loop runs (max 3 blocking rounds). A clean review makes the workflow terminal-ready; `end_workflow({ mode: "task" })` completes it.
126
116
 
127
- Lifecycle: `plan → reuse → handoff → loop (build ↔ review) → terminal-ready → end_task_workflow`
117
+ Lifecycle: `plan → reuse → handoff → loop (build ↔ review) → terminal-ready → end_workflow({ mode: "task" })`
128
118
 
129
119
  - `/workflow-task <goal>` — start a new run
130
120
  - `/workflow-task resume` — list and resume active runs
131
121
  - `/workflow-task help` — show the lifecycle
132
- - Valid `plan.md`, `reuse.md`, and `handoff.md` each automatically queue the next phase prompt (the workflow does not pause at those boundaries)
122
+ - Valid phase artifacts automatically queue the next phase prompt (the workflow does not pause at artifact boundaries)
133
123
  - No grilling, research, or audit phases
134
124
  - `reuse.md` is optional; it is skipped automatically when the project has no git history
135
125
 
@@ -146,7 +136,6 @@ Lifecycle: `plan → reuse → handoff → loop (build ↔ review) → terminal-
146
136
  | `statusKey` | `string` | Footer status key. |
147
137
  | `entryType` | `string` | Session-history custom-type. It stores a pointer only; `workflow.json` is canonical. |
148
138
  | `footerLabel` | `string` | Label shown in the footer (`● label · step/total phase`). |
149
- | `continueAfterArtifact?` | `boolean` | Queue the next phase prompt after the parent writes a valid artifact. `task` enables this at every parent-owned artifact boundary (plan, reuse, handoff) so the workflow flows automatically into the loop. |
150
139
 
151
140
  ### Adapter options
152
141
 
@@ -154,8 +143,6 @@ The second argument to `createWorkflowExtension`:
154
143
 
155
144
  | Field | Description |
156
145
  |---|---|
157
- | `toolNames` | `{ start, resume, end }` — registered tool names. Artifacts advance phase state; the user explicitly continues the attached run. |
158
- | `toolLabels` | Human-readable labels for the tools. |
159
146
  | `commandName` | The `/<command>` name users type to kick off the workflow. |
160
147
  | `commandDescription` | Description shown in the command list. |
161
148
 
@@ -165,14 +152,14 @@ Each started workflow receives a UUID artifact directory under `.selesai/artifac
165
152
 
166
153
  Runs are **never** auto-resumed on session start. At most one run can be attached to a Pi instance, but older active runs remain resumable:
167
154
 
168
- - `resume_workflow({ run: "<id-or-path>" })` / `resume_quick_workflow(...)` / `resume_task_workflow(...)`
155
+ - `start_workflow({ mode, goal })`, `resume_workflow({ mode, run: "<id-or-path>" })`, and `end_workflow({ mode })`, where `mode` is `prototype`, `quick`, or `task`
169
156
  - `/workflow-prototype resume <id-or-artifact-dir-or-workflow.json>` / `/workflow-quick resume ...` / `/workflow-task resume ...`
170
157
  - `/workflow-prototype resume`, `/workflow-quick resume`, or `/workflow-task resume` lists active runs (and offers a UI picker when available).
171
158
  - `/workflow-prototype help`, `/workflow-quick help`, or `/workflow-task help` shows the start, resume, continue, and explicit-completion lifecycle.
172
159
 
173
- Resume validates the selected file is under the artifacts base, belongs to that mode, is active, and matches its containing directory. It reconciles the current expected artifact once before emitting the current prompt, covering a crash after `write_workflow_artifact` writes the file but before the phase-state write. Artifact writes do not inject the next phase prompt or launch the next subagent; they terminate the parent turn and wait for the user to continue. A mode can opt out of that pause after a parent artifact write; `task` does so at every parent-owned artifact boundary (`plan.md`, `reuse.md`, `handoff.md`) so the build loop starts immediately after a valid handoff. Corrupt records are skipped during discovery.
160
+ Resume validates the selected file is under the artifacts base, belongs to that mode, is active, and matches its containing directory. It reconciles the current expected artifact once before emitting the current prompt, covering a crash after `write_workflow_artifact` writes the file but before the phase-state write. Valid artifact writes queue one hidden engine-controlled continuation using `steer` and terminate the current parent turn; invalid writes stay in the current phase and do not terminate. Prompts injected by start, resume, and continue commands are hidden custom messages rather than visible synthetic user messages. Transition-capable calls (`write_workflow_artifact`, loop commentator transitions, and `end_workflow`) must be the sole tool call in their assistant batch; the adapter fails closed when that cannot be proven. Corrupt records are skipped during discovery. Reloads never auto-resume; explicit `resume_workflow` remains required.
174
161
 
175
- A valid terminal artifact makes a workflow **terminal-ready**; it does not complete the run. Call the mode-specific `end_*_workflow` tool to write `status: "completed"`, append the done entry, and terminate. This is the only completion path.
162
+ A valid terminal artifact makes a workflow **terminal-ready**; it does not complete the run. Call `end_workflow({ mode })` to write `status: "completed"`, append the done entry, and terminate. This is the only completion path.
176
163
 
177
164
  ## Artifact ownership
178
165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
5
5
  "type": "module",
6
6
  "repository": {