@preapexis/piexis 0.2.2 → 0.2.5
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/README.md +85 -131
- package/apps/server/src/index.ts +97 -0
- package/apps/server/src/session-manager.ts +95 -0
- package/apps/server/src/workspace.ts +39 -0
- package/{tsconfig.json → apps/server/tsconfig.json} +3 -3
- package/apps/web/dist/assets/index-D4n3_lAw.js +10 -0
- package/apps/web/dist/assets/index-igiK0FPY.css +1 -0
- package/apps/web/dist/index.html +3 -0
- package/bin/piexis-web.mjs +24 -0
- package/extensions/agents.ts +205 -0
- package/extensions/ask-question.ts +54 -95
- package/extensions/recommended-tools.ts +62 -0
- package/extensions/ui.ts +204 -58
- package/extensions/webui.ts +45 -0
- package/lib/agent-config.ts +55 -0
- package/lib/agents.ts +208 -0
- package/lib/question-model.ts +98 -0
- package/lib/question-ui.ts +214 -0
- package/lib/recommended-tools.ts +119 -0
- package/lib/subagent-runner.ts +146 -0
- package/lib/ui-tips.json +41 -0
- package/package.json +41 -14
- package/packages/pi-integration/src/rpc-client.ts +99 -0
- package/packages/shared/src/index.ts +27 -0
- package/prompts/init.md +10 -2
- package/prompts/summary.md +7 -6
- package/schemas/agent.schema.json +48 -0
- package/skills/{claude-api/LICENSE.txt → caveman/LICENSE} +2 -2
- package/skills/caveman/LICENSE-MIT +21 -0
- package/skills/caveman/NOTICE +10 -0
- package/skills/caveman/SKILL.md +89 -0
- package/skills/find-skills/SKILL.md +141 -0
- package/skills/ponytail/LICENSE +21 -0
- package/skills/ponytail/NOTICE +9 -0
- package/skills/ponytail/SKILL.md +52 -0
- package/themes/midnight.json +81 -0
- package/.github/workflows/publish.yml +0 -35
- package/AGENTS.md +0 -58
- package/eval/README.md +0 -72
- package/eval/permission-cases.json +0 -28
- package/eval/permissions.mjs +0 -103
- package/extensions/background-tasks.ts +0 -433
- package/extensions/background-worker.ts +0 -50
- package/extensions/glob.ts +0 -32
- package/extensions/mode.ts +0 -397
- package/lib/bash-approvals.ts +0 -113
- package/lib/bash-policy.ts +0 -164
- package/lib/mode-policy.ts +0 -174
- package/lib/path-policy.ts +0 -182
- package/lib/permission-classifier.ts +0 -128
- package/lib/permission-config.ts +0 -91
- package/lib/permission-denials.ts +0 -32
- package/lib/permission-intent.ts +0 -128
- package/lib/permission-taxonomy.ts +0 -15
- package/lib/task-broker.ts +0 -65
- package/lib/task-checkout.ts +0 -39
- package/lib/task-git.ts +0 -44
- package/lib/task-patch.ts +0 -106
- package/lib/task-permissions.ts +0 -51
- package/lib/task-report.ts +0 -62
- package/lib/task-worktree-policy.ts +0 -56
- package/scripts/release.mjs +0 -32
- package/skills/claude-api/SKILL.md +0 -572
- package/skills/claude-api/csharp/claude-api/README.md +0 -362
- package/skills/claude-api/csharp/claude-api/batches.md +0 -14
- package/skills/claude-api/csharp/claude-api/files-api.md +0 -25
- package/skills/claude-api/csharp/claude-api/streaming.md +0 -28
- package/skills/claude-api/csharp/claude-api/tool-use.md +0 -164
- package/skills/claude-api/curl/examples.md +0 -256
- package/skills/claude-api/curl/managed-agents.md +0 -371
- package/skills/claude-api/go/claude-api/README.md +0 -188
- package/skills/claude-api/go/claude-api/files-api.md +0 -23
- package/skills/claude-api/go/claude-api/streaming.md +0 -43
- package/skills/claude-api/go/claude-api/tool-use.md +0 -220
- package/skills/claude-api/go/managed-agents/README.md +0 -564
- package/skills/claude-api/java/claude-api/README.md +0 -238
- package/skills/claude-api/java/claude-api/files-api.md +0 -27
- package/skills/claude-api/java/claude-api/streaming.md +0 -24
- package/skills/claude-api/java/claude-api/tool-use.md +0 -228
- package/skills/claude-api/java/managed-agents/README.md +0 -443
- package/skills/claude-api/php/claude-api/README.md +0 -174
- package/skills/claude-api/php/claude-api/batches.md +0 -15
- package/skills/claude-api/php/claude-api/files-api.md +0 -13
- package/skills/claude-api/php/claude-api/streaming.md +0 -27
- package/skills/claude-api/php/claude-api/tool-use.md +0 -253
- package/skills/claude-api/php/managed-agents/README.md +0 -443
- package/skills/claude-api/python/claude-api/README.md +0 -565
- package/skills/claude-api/python/claude-api/batches.md +0 -198
- package/skills/claude-api/python/claude-api/files-api.md +0 -170
- package/skills/claude-api/python/claude-api/sdk-upgrade.md +0 -286
- package/skills/claude-api/python/claude-api/streaming.md +0 -179
- package/skills/claude-api/python/claude-api/tool-use.md +0 -627
- package/skills/claude-api/python/managed-agents/README.md +0 -335
- package/skills/claude-api/ruby/claude-api/README.md +0 -134
- package/skills/claude-api/ruby/claude-api/streaming.md +0 -16
- package/skills/claude-api/ruby/claude-api/tool-use.md +0 -41
- package/skills/claude-api/ruby/managed-agents/README.md +0 -394
- package/skills/claude-api/shared/admin-api.md +0 -179
- package/skills/claude-api/shared/agent-design.md +0 -101
- package/skills/claude-api/shared/anthropic-cli.md +0 -269
- package/skills/claude-api/shared/claude-platform-on-aws.md +0 -59
- package/skills/claude-api/shared/cost-optimization.md +0 -233
- package/skills/claude-api/shared/error-codes.md +0 -262
- package/skills/claude-api/shared/live-sources.md +0 -166
- package/skills/claude-api/shared/managed-agents-api-reference.md +0 -455
- package/skills/claude-api/shared/managed-agents-client-patterns.md +0 -220
- package/skills/claude-api/shared/managed-agents-core.md +0 -359
- package/skills/claude-api/shared/managed-agents-environments.md +0 -226
- package/skills/claude-api/shared/managed-agents-events.md +0 -290
- package/skills/claude-api/shared/managed-agents-memory.md +0 -201
- package/skills/claude-api/shared/managed-agents-multiagent.md +0 -260
- package/skills/claude-api/shared/managed-agents-onboarding.md +0 -82
- package/skills/claude-api/shared/managed-agents-outcomes.md +0 -108
- package/skills/claude-api/shared/managed-agents-overview.md +0 -77
- package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +0 -156
- package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +0 -282
- package/skills/claude-api/shared/managed-agents-tools.md +0 -504
- package/skills/claude-api/shared/managed-agents-webhooks.md +0 -143
- package/skills/claude-api/shared/model-migration.md +0 -1872
- package/skills/claude-api/shared/models.md +0 -142
- package/skills/claude-api/shared/platform-availability.md +0 -53
- package/skills/claude-api/shared/prompt-audit.md +0 -223
- package/skills/claude-api/shared/prompt-caching.md +0 -292
- package/skills/claude-api/shared/token-counting.md +0 -56
- package/skills/claude-api/shared/tool-use-concepts.md +0 -526
- package/skills/claude-api/typescript/claude-api/README.md +0 -405
- package/skills/claude-api/typescript/claude-api/batches.md +0 -106
- package/skills/claude-api/typescript/claude-api/files-api.md +0 -98
- package/skills/claude-api/typescript/claude-api/streaming.md +0 -178
- package/skills/claude-api/typescript/claude-api/tool-use.md +0 -586
- package/skills/claude-api/typescript/managed-agents/README.md +0 -359
- package/skills-lock.json +0 -359
- package/tests/auto-permissions.test.mjs +0 -315
- package/tests/background-tasks.test.mjs +0 -27
- package/tests/background-worker.test.mjs +0 -60
- package/tests/bash-permissions.test.mjs +0 -276
- package/tests/glob.test.mjs +0 -31
- package/tests/mode.test.mjs +0 -62
- package/tests/permission-evaluation.test.mjs +0 -70
- package/tests/permission-foundation.test.mjs +0 -194
- package/tests/permission-policy.test.mjs +0 -158
- package/tests/runtime/README.md +0 -51
- package/tests/runtime/harness.mjs +0 -190
- package/tests/runtime/permissions.test.mjs +0 -192
- package/tests/runtime/tui.test.mjs +0 -60
- package/tests/skills-routing.test.mjs +0 -51
- package/tests/task-integration.test.mjs +0 -324
- package/tests/task-permissions.test.mjs +0 -178
- package/tests/task-worktree-policy.test.mjs +0 -53
package/README.md
CHANGED
|
@@ -1,180 +1,145 @@
|
|
|
1
1
|
# PieXis
|
|
2
2
|
|
|
3
|
-
PieXis is a Pi package with
|
|
3
|
+
PieXis is a Pi package with interactive questions, coding skills, prompts, and terminal UI resources. It requires Node.js 22.19+ and a current Pi release.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
> **PieXis permission controls are not a security boundary.** They help prevent accidental or unapproved agent tool actions; they do **not** provide OS-level sandboxing, filesystem isolation, workspace-only filesystem enforcement, protected-path enforcement, network isolation, or network allowlisting. Run Pi in an appropriate container, VM, sandbox, or restricted user account when executing untrusted code or allowing unattended/high-autonomy agents.
|
|
7
|
-
|
|
8
|
-
## Install and use
|
|
5
|
+
## Installation
|
|
9
6
|
|
|
10
7
|
```sh
|
|
11
8
|
pi install npm:@preapexis/piexis
|
|
12
9
|
```
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
Restart Pi or run `/reload` after installation. The package loads AskQuestion, agent profiles/subagents, the custom header/footer, automatic recommended-tool setup, prompts, skills, and themes.
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
## Resources
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
- **AskQuestion** provides keyboard-driven single/multi selection and text questions in the interactive TUI, with validation, answer review, and cancellation. It requires terminal input and output.
|
|
16
|
+
- **UI** shows project information, agent activity, Git branch, model, and context usage. The startup header shows three random, category-labelled tips from `lib/ui-tips.json`, preferring different categories and staying stable across redraws. Tips cycle without repeats until the list is exhausted within the extension runtime; `/reload` starts a new cycle. Each JSON entry has `category` and `text` fields; edit them to change the recommendations.
|
|
17
|
+
- **Recommended tools** offers to enable native `grep`, `find`, and `ls` for the project at startup; no slash command is needed.
|
|
18
|
+
- **Agents** provides `plan`, `orchestrator`, `debug`, `ask`, and `code` profiles, all callable through the `subagent` tool. Small delegated tasks can automatically use a cheaper model.
|
|
19
|
+
- **Skills** cover implementation, debugging, review, design, refactoring, validation, and document workflows. Bundled **caveman** adds concise chat style; **ponytail** guides the smallest complete coding change. Only their core instructions are included, not upstream hooks or companion skills. Source revisions and licenses live beside each skill.
|
|
20
|
+
- **Prompts** include `/init`, `/commit`, and `/summary`. `/init` adds workflow guidance to `AGENTS.md`: caveman for responses, grilling for clarification questions, and ponytail for building. These are agent instructions, not runtime enforcement.
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
- **Themes** include warm **orange** and **midnight**, a dark slate palette with cyan, violet, and mint accents. Run `/reload`, then choose a palette in `/settings` → **Theme**.
|
|
23
|
+
|
|
24
|
+
Caveman and ponytail ship with this package; no separate installation is needed. Their upstream standalone installation commands are:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx skills add https://github.com/juliusbrussee/caveman --skill caveman
|
|
28
|
+
npx skills add https://github.com/dietrichgebert/ponytail --skill ponytail
|
|
29
|
+
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Avoid loading standalone copies alongside the bundled skills, which can cause name collisions. In Pi, explicitly invoke the bundled skills with `/skill:caveman` and `/skill:ponytail`.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
## Automatic tool setup
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
On startup in a trusted project with terminal input/output, Piexis offers any pending setup: available, inactive `grep`, `find`, and `ls` tools, or a missing project agent configuration when the agent extension is loaded. Earlier tool approval does not hide the new agent setup; if tools are already configured, the prompt offers only `.pi/agent.json` creation and leaves tool settings unchanged:
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
- **Enable** saves the project choice and tool additions in `.pi/settings.json`, then activates them in the current session without removing active tools or extensions. It also creates `.pi/agent.json` if neither that file nor legacy `.pi/agents.json` exists. The scaffold uses the current model for `model`, `small_model`, and `subagent_model`, preserves user-level agent overrides, and populates all five `subagent_variant_overrides` with `model` and `effort`. Default effort is `high` for plan/orchestrator/debug, `low` for ask, and `medium` for code. Existing agent configuration is never overwritten. If creation fails or no model is selected, tools remain enabled and a warning explains how to configure agents manually.
|
|
38
|
+
- **Not now** (the default) or Esc changes nothing. The prompt is shown at most once per project per extension runtime; a subsequent startup or reload may offer it again.
|
|
39
|
+
- **Don't ask again** records `"disabled"` only for features offered in that prompt: `piexis.recommendedTools` for tool setup and `piexis.agentSetup` for agent setup. It changes neither tools nor agent files. Approval records `"enabled"`. An existing `agent.json` or legacy `agents.json` completes agent setup; if creation failed or the file was removed, startup offers it again unless `agentSetup` is `"disabled"`. Remove the corresponding disabled preference to allow another offer.
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
User-level settings are never written. Other project settings and existing `piexis` keys are preserved. Modern Pi uses additive `defaultTools` entries such as `["+grep", "+find", "+ls"]`. Older Pi releases without additive support use a plain list of the four default tools plus the approved additions, only when no tool selection is configured.
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Setup skips non-TUI/non-TTY runs, untrusted projects, and runs with CLI tool-selection overrides (`--tools`, `--exclude-tools`, `--no-tools`, or `--no-builtin-tools`, including their short forms). Explicit `defaultTools` allowlists, including an empty list, are left alone; they can still receive an agent-only setup offer. Additive `-tool` exclusions remain respected. Only Pi's available built-in tools are offered. Malformed or non-regular settings files and settings changed during the prompt are not overwritten; a warning explains why setup was skipped.
|
|
40
44
|
|
|
41
|
-
`
|
|
45
|
+
The setup extension is `extensions/recommended-tools.ts`; package resource filtering can omit it. Native `find` provides file discovery by glob pattern; no wrapper extension is needed.
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
2. Reject malformed inputs and unresolvable paths.
|
|
45
|
-
3. Apply Plan restrictions, bounded Bash analysis, and Auto non-overridable checks.
|
|
46
|
-
4. Match eligible scoped approvals and mode defaults. Auto rejects broad/stale grants; unsupported shell forms cannot match a reusable narrow Bash grant.
|
|
47
|
-
5. Return `require-approval`, or deny in Don't Ask. Model review and approval UI are handled by the extension and fail closed.
|
|
47
|
+
## Agents and subagents
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Use `/agent` to select a profile, `/agent plan` to switch directly, or `pi --agent orchestrator` at startup. **Alt+A** cycles `plan → orchestrator → debug → ask → code` while idle; cycling is blocked while an agent is running. The footer shows the active profile (`agent:code`) and, on wider terminals, the shortcut hint. `/hotkeys` lists the shortcut. `code` is the default. Profile choices follow the active session branch. Profiles add instructions rather than replacing repository guidance or changing the main session's tools:
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
| Profile | Purpose |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `plan` | Explore and return a plan; no implementation |
|
|
54
|
+
| `orchestrator` | Delegate focused tasks and combine verified results |
|
|
55
|
+
| `debug` | Establish evidence, diagnose, fix, and verify |
|
|
56
|
+
| `ask` | Answer questions from code/documentation; no changes |
|
|
57
|
+
| `code` | Implement scoped changes and run checks |
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
All five are callable with `subagent({ "agent": "ask", "task": "Locate the configuration loader" })`. The orchestrator chooses when to delegate; delegation is not an automatic pipeline. Each child receives its task, normal Pi resources, and profile instructions, **not** the parent's conversation. Supply necessary context explicitly. Children share the workspace, so calls are sequential; delegation stops at two child levels. A delegated orchestrator can call other subagents at the second level.
|
|
54
60
|
|
|
55
|
-
`
|
|
61
|
+
Optional configuration lives in `~/.pi/agent/agent.json` (or `$PI_CODING_AGENT_DIR/agent.json`) and trusted-project `.pi/agent.json`. Legacy `agents.json` is still read when `agent.json` is absent in that scope; no files are renamed or deleted. Project fields override user fields; per-variant overrides merge by field. Accepting **Enable** during project setup creates the project scaffold only when no project agent configuration exists; user-level files are never written. Defaults use the current model unless user-level configuration specifies another model. New scaffolds set `delegate_exploration` to `false`; edit it to opt in. `/agent-config` creates a missing project config or fills only its missing settings with defaults, preserving every configured value. It uses the active model when it must choose a model. Generated files include `$schema` for editor validation; configuration reloads on each profile switch and delegation. Use exact `provider/model-id` strings from `/model`, not fuzzy aliases:
|
|
56
62
|
|
|
57
63
|
```json
|
|
58
64
|
{
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
"$schema": "https://raw.githubusercontent.com/VarunGaikwad/piexis/main/schemas/agent.schema.json",
|
|
66
|
+
"model": "provider/default-model",
|
|
67
|
+
"small_model": "provider/default-model",
|
|
68
|
+
"subagent_model": "provider/default-model",
|
|
69
|
+
"delegate_exploration": true,
|
|
70
|
+
"subagent_variant_overrides": {
|
|
71
|
+
"plan": { "model": "provider/default-model", "effort": "high" },
|
|
72
|
+
"orchestrator": { "model": "provider/default-model", "effort": "high" },
|
|
73
|
+
"debug": { "model": "provider/default-model", "effort": "high" },
|
|
74
|
+
"ask": { "model": "provider/default-model", "effort": "low" },
|
|
75
|
+
"code": { "model": "provider/default-model", "effort": "medium" }
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
```
|
|
69
79
|
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
## Auto review policy and privacy
|
|
73
|
-
|
|
74
|
-
Auto uses **deterministic policy → one model reviewer**, not a multi-model pipeline. Ordinary local reads/edits do not call the reviewer. Review compares the proposed action against the current user request and earlier user constraints, rather than treating ordinary-looking command names as sufficient authorization.
|
|
75
|
-
|
|
76
|
-
| Review category | Rule |
|
|
77
|
-
| --- | --- |
|
|
78
|
-
| Ordinary development | Must serve the current requested task |
|
|
79
|
-
| External filesystem | Intent must cover the external target and operation |
|
|
80
|
-
| Destructive changes | Specific authorization for operation, target, and scope |
|
|
81
|
-
| Credential exploration/exposure | Denied; normal use of an authenticated CLI is distinct |
|
|
82
|
-
| Data egress | Authorization must cover destination, payload, and purpose |
|
|
83
|
-
| Production/shared systems | Explicit target and operation required |
|
|
84
|
-
| Privilege/security weakening | Denied in unattended Auto |
|
|
85
|
-
| Safeguard bypasses | Explicit authorization for the particular bypass |
|
|
86
|
-
| Opaque execution or ambiguity | Denied when effects/authorization cannot be established |
|
|
87
|
-
|
|
88
|
-
The taxonomy lives in `lib/permission-taxonomy.ts`. Shell syntax is boundedly parsed; full program semantics remain model-assessed. These are permission-review policies, not guarantees that every shell effect will be detected. Every model allow must cite the current user message and any supporting user messages using valid IDs. Unknown categories, missing/currently invalid evidence, malformed output, abnormal completion, and provider failure fail closed. The host rejects allows in prohibited categories even if the model returns them. Auto does not add approval dialogs or change modes on denial. Repeated denials stop the run under the limits below.
|
|
89
|
-
|
|
90
|
-
### What is sent
|
|
91
|
-
|
|
92
|
-
For reviewed actions, the configured provider receives:
|
|
93
|
-
- Tool name and Bash command or requested filesystem path.
|
|
94
|
-
- Working directory, workspace boundary, and deterministic path-policy findings.
|
|
95
|
-
- Bounded, provenance-labelled user messages from the active branch.
|
|
96
|
-
- Eligible file/directory approval scopes from that same current request when review is still needed. Invalid or stale Bash grants are not forwarded as authorization.
|
|
97
|
-
- Bounded shell parsing results and risk findings; no script or Git configuration contents.
|
|
98
|
-
- For task creation and worker review, the proposed task/brief, selected model, and delegated path/Bash scope, explicitly marked as untrusted action data—not user authorization.
|
|
99
|
-
|
|
100
|
-
Other than the proposed delegation brief described above, it does **not** receive assistant prose/reasoning, raw tool-output history, worker result summaries, compaction summaries, file contents, edit/write replacement text, environment-variable objects, or Pi authentication credentials. The reviewer has no tool access. User prose, paths, commands, and approval strings can themselves contain secrets: recognition/redaction is best-effort, not a secret-detection guarantee. Do not use Auto when those details must not be disclosed to the selected provider. Recognizably secret-bearing or oversized review payloads are denied locally, without contacting the provider. PieXis does not log review payloads; Pi's normal user-message/session storage still applies.
|
|
80
|
+
**Alt+A** and `/agent` apply the selected profile's `model` and `effort` from `subagent_variant_overrides` to the main session too. Without a profile model override, switching uses the top-level `model` if configured; otherwise it keeps the current model. Missing effort overrides leave the current thinking level alone (subject to Pi's model-capability clamping). `subagent_model` and `small_model` remain delegation-only settings. Invalid configuration or an unavailable model leaves the current profile unchanged and shows a warning.
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
Replace the example model IDs with available, authenticated models. `model` initializes a new main session; explicit CLI model selections and resumed sessions win at startup. An explicit profile switch subsequently applies the configured settings. `subagent_model` defaults to the current main-session model. A per-variant `model` wins over all automatic routing. New scaffolds explicitly pin each variant to the default subagent model; remove the `model` field from a variant's override to let it follow automatic routing or later `subagent_model` changes. Otherwise, short lookups/summaries in `ask` and clearly mechanical typo/comment/label/symbol edits in `code` use `small_model` when configured. Ambiguous, longer, or complex tasks, including planning, debugging, and orchestration, keep the normal subagent model. This is a conservative text heuristic, not a classifier or a guarantee of task difficulty. Omit `small_model` to disable automatic small-task routing.
|
|
103
83
|
|
|
104
|
-
`
|
|
84
|
+
Set `delegate_exploration` to `true` to let every profile offload bounded, read-only codebase discovery to the small model. Call `subagent({ "agent": "ask", "purpose": "explore", "task": "Locate configuration loading and summarize the relevant files" })`. Exploration always uses `small_model` at low effort, even if `ask` has a per-variant model override. It is rejected unless the option and `small_model` are configured. Exploration children receive only `read`, `grep`, `find`, and `ls`; they cannot delegate further. The parent retains planning, judgment, edits, and final verification. This is opt-in because child-process overhead can cost more than a trivial direct read.
|
|
105
85
|
|
|
106
|
-
|
|
86
|
+
Effort accepts `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Variant effort overrides win; small tasks otherwise request `low`, and other tasks inherit the parent's thinking level. Pi clamps effort to the chosen model's capabilities. Results/progress show the selected model, **requested** effort, routing reason, output, and usage. Invalid configuration or unavailable child models fail explicitly rather than silently switching providers.
|
|
107
87
|
|
|
108
|
-
|
|
88
|
+
Child agents run in separate Pi processes with ephemeral sessions and the same OS permissions. Their tool selection is a subset of the parent's active tools; `plan`/`ask` children select only `read`, `grep`, `find`, and `ls`, and disable native MCP access. With `delegate_exploration`, a non-exploration `plan` or `ask` child may also receive `subagent` to perform one nested exploration; exploration children never receive it. `plan`/`ask` parents may delegate only to those two profiles. Other children omit `AskQuestion` and return questions to the parent. Main profile instructions and child tool selection are **not sandboxing**. Normal configured extensions and explicitly supplied CLI extensions load in children, with the parent's project-trust decision. Runtime-only provider/tool registrations, CLI `--api-key` overrides, credentials supplied only to an embedded SDK session, and in-memory state in other extensions are not copied to child processes. Use Pi's stored or environment-based authentication for delegation. Other extensions remain responsible for their own restrictions; verify their subprocess behavior before using delegation in a restricted host.
|
|
109
89
|
|
|
110
|
-
|
|
90
|
+
Abort and shutdown terminate active children (including their process group on Unix). Windows termination is best-effort for descendants. Model-facing output is capped at 50,000 characters; tool details retain up to 1 MB. JSON/print mode supports subagents but has no profile picker or child terminal interaction. Avoid loading another extension that registers `subagent`, `/agent`, or `--agent` alongside this one.
|
|
111
91
|
|
|
112
|
-
|
|
92
|
+
## AskQuestion
|
|
113
93
|
|
|
114
|
-
|
|
94
|
+
Existing `{ questions: [{ question, options, multiSelect?, allowOther? }] }` calls remain supported. New optional fields:
|
|
115
95
|
|
|
116
|
-
|
|
96
|
+
- Question `id`, `type` (`single`, `multi`, `text`), and `required` (defaults to true). Explicit text questions may omit `options`; legacy optionless questions require `allowOther: true`.
|
|
97
|
+
- Option `id`, `label`, and optional `description`. Missing IDs become `question-1` / `option-1`, etc.; IDs must be unique within their scope. Labels need not be unique.
|
|
98
|
+
- `defaultSelection`: option ID or array of IDs. `minSelections` / `maxSelections` apply to multi-select answers. Required questions always need an answer.
|
|
99
|
+
- `maxLength`: maximum text length in Unicode code points. Text is not trimmed; whitespace-only required answers are rejected. Text input is single-line, using Pi's native Input component.
|
|
117
100
|
|
|
118
|
-
|
|
101
|
+
Use ↑↓ to navigate, Space to toggle multiple choices, Enter to confirm, and Esc/Ctrl+C to cancel the interaction. For related questions, Tab/Shift+Tab revisits questions without losing drafts; all questions must be confirmed before the final review can be submitted. “Type something else” is available only with `allowOther`; its answer replaces, rather than mixes with, selected options. Selection-count limits do not apply to custom text.
|
|
119
102
|
|
|
120
|
-
|
|
103
|
+
Results have `status`: `answered`, `cancelled`, `aborted`, `requires_interaction`, or `error`. Both model-facing JSON and tool `details` contain the same result. Legacy `answers` and `cancelled` fields remain; each confirmed answer retains `question` and label-based `answer`, with added question `id`, `optionIds`, and `custom`. Optional empty answers are `""` or `[]`, never cancellation. Cancellation returns only already-confirmed answers. `cancelled` remains true for every non-answered outcome; use `status` to distinguish them.
|
|
121
104
|
|
|
122
|
-
|
|
123
|
-
- `/permissions` or `/permissions list` — inspect grants, IDs, mode, and denial count.
|
|
124
|
-
- `/permissions revoke <id>` — revoke one grant.
|
|
125
|
-
- `/permissions clear` — remove all grants.
|
|
105
|
+
Pi owns stdin, raw mode, cursor state, and focus restoration. Agent abort and session shutdown dismiss the screen; input closure/errors also settle it. Concurrent AskQuestion calls are explicitly rejected with `status: "error"`, in addition to Pi's sequential execution. Other extensions must respect Pi's UI ownership; this extension does not coordinate unrelated prompts. Non-TUI/non-TTY runs return `requires_interaction` without reading input.
|
|
126
106
|
|
|
127
|
-
|
|
107
|
+
## Removed features
|
|
128
108
|
|
|
129
|
-
|
|
109
|
+
PieXis no longer provides permission modes, tool approvals, classifier review, plan-to-execution handoffs, filtered search, or background worktree tasks. Their commands, shortcuts, flags, and configuration initialization have been removed. Tool execution follows Pi and any other installed extensions; PieXis does not provide sandboxing or access restrictions.
|
|
130
110
|
|
|
131
|
-
|
|
111
|
+
Existing `piexis` settings, legacy configuration files, session records, and retained task worktrees are not deleted automatically. Old classifier, execution-model, and background-model settings are no longer read. Remove obsolete configuration manually if desired, preserving unrelated settings and any work you need. Remove explicit references to deleted extensions from your Pi resource settings or launch arguments.
|
|
132
112
|
|
|
133
|
-
|
|
113
|
+
## PIXIS WebUI (preview)
|
|
134
114
|
|
|
135
|
-
|
|
115
|
+
PIXIS 2.0 adds a local WebUI without replacing the Pi TUI. Each browser session observes a backend-owned, long-lived Pi RPC process. After installing or updating Piexis, start Pi inside the project and run:
|
|
136
116
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
### Delegation and approvals
|
|
142
|
-
|
|
143
|
-
- Status, cancellation, and diff inspection are recognized operations, including in Plan. Creation, application, and cleanup are denied in Plan/Don't Ask. Unknown custom tools remain unknown.
|
|
144
|
-
- Manual/Accept Edits require one-time approval for agent-requested task creation. A direct `/task <work>` command supplies explicit user authorization. Auto reviews creation against verified user intent; direct task commands receive their own in-memory user-request identifier.
|
|
145
|
-
- The proposed brief is untrusted action data. Optional `brief` fields include goal, context, constraints, files, acceptance criteria (`acceptanceCriteria`), decisions, and verification. Task plus brief is limited to 20,000 characters and is never silently truncated. Keep secrets out of descriptions: briefs go to the worker, may go to the classifier, and are stored in local task metadata.
|
|
146
|
-
- Optional `paths` narrows file-tool scope to relative files/directories (default `["."]`, maximum 32 entries). Traversal, external symlink targets, credentials/protected path-tool writes, uncertain recursive searches, and tools outside the worker capability set are denied. There are no recursive task-delegation tools.
|
|
147
|
-
- Every worker tool call goes through the parent broker and shared evaluator. Workers inherit no foreground session grants. Auto additionally reviews worker mutations against both the original user intent and the delegated brief. Manual approvals are one-time; missing UI, unavailable/expired brokers, and classifier failures deny rather than escalate.
|
|
148
|
-
- New user input, mode changes, grant revocation, branch navigation, session replacement, and shutdown invalidate outstanding task authorizations and cancel queued/running workers. Existing worktrees are retained. Worker denial limits inherit the parent's launch settings and stop that worker, not unrelated tasks.
|
|
149
|
-
|
|
150
|
-
### Opt-in command execution
|
|
151
|
-
|
|
152
|
-
Start Pi with `--background-bash` to expose worker Bash for tests/builds/debugging. This is **permission-controlled command execution**, not sandboxing. It uses the same parent approval/classifier pipeline. Bash requires whole-worktree scope; narrower `paths` cannot be combined with this flag. Known literal external/protected targets are rejected, but parsing/model review cannot constrain arbitrary script effects or descendants. Cancellation revokes future approvals and terminates the worker process; it does not promise that all spawned descendants have stopped.
|
|
153
|
-
|
|
154
|
-
Workers load only their explicit guard extension and worker-only tool wrappers, with built-ins disabled, so a missing guard cannot fall back to unguarded tools. Project trust-gated resources and automatic context-file discovery are disabled; needed repository instructions can be read through the broker. Task text is passed as literal prompt data, not CLI options or `@file` arguments. Pi's trusted user-level runtime and authentication are still used.
|
|
155
|
-
|
|
156
|
-
### Inspect, apply, and clean
|
|
117
|
+
```text
|
|
118
|
+
/webui
|
|
119
|
+
```
|
|
157
120
|
|
|
158
|
-
|
|
121
|
+
Pi displays a local URL containing a per-launch access token; open that URL in a browser. The TUI remains usable. The package also exposes a PowerShell-friendly command for starting outside Pi:
|
|
159
122
|
|
|
160
|
-
|
|
123
|
+
```powershell
|
|
124
|
+
piexis-web --workspace C:\path\to\your-project
|
|
125
|
+
```
|
|
161
126
|
|
|
162
|
-
|
|
127
|
+
The server uses the requested workspace and starts Pi with documented `--mode rpc`. Pi must be installed, authenticated, and available as `pi` (or set `PIXIS_PI_BIN`). Use `.env.example` as the configuration reference. `npm run dev` remains available only for developing Piexis itself and serves the Vite development client on port 5173.
|
|
163
128
|
|
|
164
|
-
|
|
129
|
+
The current preview supports genuine streamed assistant text, tool lifecycle cards, abort, Pi extension dialog forwarding, multiple observed backend sessions, and read-only Git status. It binds the agent server to `127.0.0.1`, requires a per-launch token and exact browser origin, validates WebSocket messages, confines file paths before Git diffs, and never sends secrets to the browser. Localhost alone is not treated as authentication.
|
|
165
130
|
|
|
166
|
-
|
|
131
|
+
Current limitations: session list is limited to sessions created in the WebUI process (it does not yet enumerate historical Pi session files); file explorer, Monaco editor/diff rendering, and terminal panel are planned; Git controls are read-only. The existing Pi TUI remains unchanged and can run separately.
|
|
167
132
|
|
|
168
133
|
## Select package resources
|
|
169
134
|
|
|
170
|
-
|
|
135
|
+
Use Pi's native resource filtering with `pi config` (or `pi config --local` for a trusted project), or narrow resources in settings:
|
|
171
136
|
|
|
172
137
|
```json
|
|
173
138
|
{
|
|
174
139
|
"packages": [
|
|
175
140
|
{
|
|
176
141
|
"source": "npm:@preapexis/piexis",
|
|
177
|
-
"extensions": ["extensions/
|
|
142
|
+
"extensions": ["extensions/ask-question.ts"],
|
|
178
143
|
"skills": ["skills/code-review", "skills/diagnosing-bugs", "skills/safe-refactor"],
|
|
179
144
|
"themes": []
|
|
180
145
|
}
|
|
@@ -182,25 +147,14 @@ PieXis uses Pi's native package resource filtering; it does not add its own plug
|
|
|
182
147
|
}
|
|
183
148
|
```
|
|
184
149
|
|
|
185
|
-
Omit a resource type to load all resources of that type; use `[]` to load none
|
|
150
|
+
Omit a resource type to load all declared resources of that type; use `[]` to load none. Glob exclusions such as `"!skills/docx/**"` can omit document workflows.
|
|
186
151
|
|
|
187
152
|
## Development
|
|
188
153
|
|
|
189
154
|
```sh
|
|
155
|
+
npm ci --ignore-scripts
|
|
190
156
|
npm run check
|
|
191
157
|
npm test
|
|
192
|
-
npm run test:runtime
|
|
193
|
-
npm run eval:permissions -- --validate
|
|
194
158
|
```
|
|
195
159
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`npm run test:runtime` launches the resolved peer dependency's packaged Pi CLI with all manifest extensions, disposable repositories and agent directories, and a deterministic loopback provider. It uses no real credentials or paid model calls. Tests cover permission dialogs, six-mode behavior, reviewer failures/timeouts, worker Bash opt-in, patch confirmation, stale approvals, cancellation, and shutdown. The Linux PTY check exercises the bundled UI and mode shortcuts; it requires `script` and `stty` and is explicitly skipped elsewhere. See [runtime validation results and limits](tests/runtime/README.md).
|
|
199
|
-
|
|
200
|
-
### Classifier evaluation
|
|
201
|
-
|
|
202
|
-
[`eval/permission-cases.json`](eval/permission-cases.json) contains 26 synthetic, labeled cases covering all policy categories, paired authorization changes, injection attempts, and worker scope. The offline runner validates the corpus, exports unlabeled requests using the production reviewer prompt, and scores externally collected model responses. It never executes fixture commands or automatically contacts a provider.
|
|
203
|
-
|
|
204
|
-
See [`eval/README.md`](eval/README.md) for the opt-in live-evaluation procedure, response format, coverage map, and remaining manual release checks. Raw false allows, host policy overrides, false denials, invalid responses, and missing cases are reported separately. Scorer unit tests are not evidence of live model accuracy; no live-model result is claimed.
|
|
205
|
-
|
|
206
|
-
There is currently no `tests/mode-sandbox.test.mjs`; consequently `npm run test:sandbox` cannot establish sandbox coverage. Unit tests do not establish containment or protection of host paths/network.
|
|
160
|
+
Tests cover retained extension/resource contracts, agent configuration/routing/profile state, child-process protocol/cancellation, AskQuestion validation/lifecycle, recommended-tool setup/settings preservation, and skill routing. On Unix with Python 3, automated PTY tests drive the real Pi TUI renderer and native selector, checking keyboard interactions, startup choices, cancellation, cursor/raw-mode restoration, and listener cleanup. They use UI adapters rather than a live agent session; unsupported hosts explicitly skip these tests. Tests do not establish OS containment or live-model behavior. The permission evaluation, sandbox, and background-task runtime suites have been removed with those features.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import Fastify from "fastify";
|
|
5
|
+
import websocket from "@fastify/websocket";
|
|
6
|
+
import fastifyStatic from "@fastify/static";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { SessionManager, type ManagedSession } from "./session-manager.ts";
|
|
9
|
+
import { gitDiff, gitStatus } from "./workspace.ts";
|
|
10
|
+
import type { ClientCommand, ServerMessage } from "../../../packages/shared/src/index.ts";
|
|
11
|
+
|
|
12
|
+
const port = Number(process.env.PIXIS_PORT || 4317);
|
|
13
|
+
const workspace = path.resolve(process.env.PIXIS_WORKSPACE || process.cwd());
|
|
14
|
+
const token = process.env.PIXIS_TOKEN || randomBytes(32).toString("base64url");
|
|
15
|
+
const allowedOrigin = process.env.PIXIS_ORIGIN || `http://127.0.0.1:${port}`;
|
|
16
|
+
const webRoot = path.resolve(process.env.PIXIS_WEB_ROOT || path.join(path.dirname(fileURLToPath(import.meta.url)), "../../web/dist"));
|
|
17
|
+
const clients = new Set<{ send(data: string): void; readyState: number }>();
|
|
18
|
+
const app = Fastify({ logger: { level: process.env.PIXIS_LOG_LEVEL || "warn" } });
|
|
19
|
+
|
|
20
|
+
const commandSchema = z.discriminatedUnion("type", [
|
|
21
|
+
z.object({ type: z.literal("list_sessions") }),
|
|
22
|
+
z.object({ type: z.literal("subscribe"), sessionId: z.string().uuid() }),
|
|
23
|
+
z.object({ type: z.literal("create_session") }),
|
|
24
|
+
z.object({ type: z.literal("prompt"), sessionId: z.string().uuid(), message: z.string().trim().min(1).max(100_000) }),
|
|
25
|
+
z.object({ type: z.literal("abort"), sessionId: z.string().uuid() }),
|
|
26
|
+
z.object({ type: z.literal("extension_ui_response"), sessionId: z.string().uuid(), id: z.string().min(1), value: z.string().optional(), confirmed: z.boolean().optional(), cancelled: z.boolean().optional() }),
|
|
27
|
+
z.object({ type: z.literal("git_status"), sessionId: z.string().uuid() }),
|
|
28
|
+
z.object({ type: z.literal("git_diff"), sessionId: z.string().uuid(), path: z.string().min(1).max(4096) })
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function secureEqual(a: string, b: string) {
|
|
32
|
+
const left = createHash("sha256").update(a).digest();
|
|
33
|
+
const right = createHash("sha256").update(b).digest();
|
|
34
|
+
return timingSafeEqual(left, right);
|
|
35
|
+
}
|
|
36
|
+
function send(client: { send(data: string): void; readyState: number }, message: ServerMessage) {
|
|
37
|
+
if (client.readyState === 1) client.send(JSON.stringify(message));
|
|
38
|
+
}
|
|
39
|
+
function broadcast(message: ServerMessage) { for (const client of clients) send(client, message); }
|
|
40
|
+
|
|
41
|
+
const manager = new SessionManager(workspace, (session, type, data) => {
|
|
42
|
+
broadcast(type === "pi_event" ? { type, sessionId: session.id, event: data } : { type, sessionId: session.id, request: data });
|
|
43
|
+
broadcast({ type: "session", session: manager.summary(session) });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await app.register(websocket);
|
|
47
|
+
await app.register(fastifyStatic, { root: webRoot, wildcard: false });
|
|
48
|
+
app.get("/health", async () => ({ ok: true }));
|
|
49
|
+
app.get("/ws", { websocket: true }, (socket, request) => {
|
|
50
|
+
const origin = request.headers.origin;
|
|
51
|
+
const suppliedToken = (request.query as { token?: string }).token;
|
|
52
|
+
if (origin !== allowedOrigin || !suppliedToken || !secureEqual(suppliedToken, token)) {
|
|
53
|
+
socket.close(1008, "Unauthorized PIXIS client");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
clients.add(socket);
|
|
57
|
+
socket.on("close", () => clients.delete(socket));
|
|
58
|
+
socket.on("message", async (raw: Buffer) => {
|
|
59
|
+
try {
|
|
60
|
+
const command = commandSchema.parse(JSON.parse(raw.toString("utf8"))) as ClientCommand;
|
|
61
|
+
await handle(socket, command);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
send(socket, { type: "error", message: error instanceof Error ? error.message : "Invalid request" });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
async function handle(client: { send(data: string): void; readyState: number }, command: ClientCommand) {
|
|
69
|
+
if (command.type === "list_sessions") {
|
|
70
|
+
const first = manager.sessions.values().next().value as ManagedSession | undefined;
|
|
71
|
+
if (first) return snapshot(client, first);
|
|
72
|
+
return send(client, { type: "snapshot", sessions: [], sessionId: "", state: {}, messages: [] });
|
|
73
|
+
}
|
|
74
|
+
if (command.type === "create_session") {
|
|
75
|
+
const session = await manager.create();
|
|
76
|
+
broadcast({ type: "session", session: manager.summary(session) });
|
|
77
|
+
return snapshot(client, session);
|
|
78
|
+
}
|
|
79
|
+
const session = manager.get(command.sessionId);
|
|
80
|
+
if (command.type === "subscribe") return snapshot(client, session);
|
|
81
|
+
if (command.type === "prompt") await manager.prompt(session.id, command.message);
|
|
82
|
+
if (command.type === "abort") await manager.abort(session.id);
|
|
83
|
+
if (command.type === "extension_ui_response") session.client.send({ type: "extension_ui_response", id: command.id, value: command.value, confirmed: command.confirmed, cancelled: command.cancelled });
|
|
84
|
+
if (command.type === "git_status") send(client, { type: "workspace", sessionId: session.id, action: "git_status", data: await gitStatus(workspace) });
|
|
85
|
+
if (command.type === "git_diff") send(client, { type: "workspace", sessionId: session.id, action: "git_diff", data: await gitDiff(workspace, command.path) });
|
|
86
|
+
broadcast({ type: "session", session: manager.summary(session) });
|
|
87
|
+
}
|
|
88
|
+
function snapshot(client: { send(data: string): void; readyState: number }, session: ManagedSession) {
|
|
89
|
+
send(client, { type: "snapshot", sessions: [...manager.sessions.values()].map((item) => manager.summary(item)), sessionId: session.id, state: session.state, messages: session.messages });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const shutdown = async () => { manager.dispose(); await app.close(); };
|
|
93
|
+
process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown);
|
|
94
|
+
await app.listen({ port, host: "127.0.0.1" });
|
|
95
|
+
console.log(`PIXIS server listening on http://127.0.0.1:${port}`);
|
|
96
|
+
console.log(`Open http://127.0.0.1:${port}/#token=${token}`);
|
|
97
|
+
if (!process.env.PIXIS_TOKEN) console.log(`PIXIS token (set PIXIS_TOKEN to persist it): ${token}`);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { PiRpcClient } from "../../../packages/pi-integration/src/rpc-client.ts";
|
|
3
|
+
import type { SessionStatus, SessionSummary } from "../../../packages/shared/src/index.ts";
|
|
4
|
+
|
|
5
|
+
export interface ManagedSession {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
status: SessionStatus;
|
|
9
|
+
client: PiRpcClient;
|
|
10
|
+
state: unknown;
|
|
11
|
+
messages: unknown[];
|
|
12
|
+
sessionFile?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SessionManager {
|
|
17
|
+
readonly sessions = new Map<string, ManagedSession>();
|
|
18
|
+
constructor(private readonly cwd: string, private readonly emit: (session: ManagedSession, type: "pi_event" | "extension_ui", data: unknown) => void) {}
|
|
19
|
+
|
|
20
|
+
async create(): Promise<ManagedSession> {
|
|
21
|
+
const id = randomUUID();
|
|
22
|
+
const client = new PiRpcClient(this.cwd);
|
|
23
|
+
const session: ManagedSession = { id, name: "New session", status: "starting", client, state: {}, messages: [] };
|
|
24
|
+
this.sessions.set(id, session);
|
|
25
|
+
client.on("record", (record) => {
|
|
26
|
+
if (record.type === "extension_ui_request") {
|
|
27
|
+
this.emit(session, "extension_ui", record);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.#applyEvent(session, record);
|
|
31
|
+
this.emit(session, "pi_event", record);
|
|
32
|
+
});
|
|
33
|
+
client.on("exit", (error) => {
|
|
34
|
+
session.status = "failed";
|
|
35
|
+
this.emit(session, "pi_event", { type: "runtime_error", message: error.message });
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
const response = await client.command({ type: "get_state" });
|
|
39
|
+
if (!response.success) throw new Error(response.error || "Pi rejected get_state");
|
|
40
|
+
session.state = response.data ?? {};
|
|
41
|
+
const data = response.data as { sessionId?: string; sessionFile?: string; sessionName?: string; model?: { id?: string } };
|
|
42
|
+
session.sessionFile = data.sessionFile;
|
|
43
|
+
session.name = data.sessionName || "New session";
|
|
44
|
+
session.model = data.model?.id;
|
|
45
|
+
const messages = await client.command({ type: "get_messages" });
|
|
46
|
+
if (messages.success) session.messages = ((messages.data as { messages?: unknown[] })?.messages ?? []);
|
|
47
|
+
session.status = "ready";
|
|
48
|
+
return session;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
session.status = "failed";
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async prompt(id: string, message: string): Promise<void> {
|
|
56
|
+
const session = this.get(id);
|
|
57
|
+
if (session.status === "running" || session.status === "cancelling") throw new Error("This session already has an active operation. Stop it before sending another prompt.");
|
|
58
|
+
session.status = "running";
|
|
59
|
+
const response = await session.client.command({ type: "prompt", message });
|
|
60
|
+
if (!response.success) {
|
|
61
|
+
session.status = "ready";
|
|
62
|
+
throw new Error(response.error || "Pi rejected prompt");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async abort(id: string): Promise<void> {
|
|
67
|
+
const session = this.get(id);
|
|
68
|
+
if (session.status !== "running") return;
|
|
69
|
+
session.status = "cancelling";
|
|
70
|
+
const response = await session.client.command({ type: "abort" });
|
|
71
|
+
if (!response.success) throw new Error(response.error || "Pi failed to stop");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get(id: string): ManagedSession {
|
|
75
|
+
const session = this.sessions.get(id);
|
|
76
|
+
if (!session) throw new Error("Unknown session");
|
|
77
|
+
return session;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
summary(session: ManagedSession): SessionSummary {
|
|
81
|
+
return { id: session.id, name: session.name, status: session.status, model: session.model, sessionFile: session.sessionFile };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
dispose(): void { for (const session of this.sessions.values()) session.client.dispose(); }
|
|
85
|
+
|
|
86
|
+
#applyEvent(session: ManagedSession, event: Record<string, unknown>): void {
|
|
87
|
+
if (event.type === "agent_start") session.status = "running";
|
|
88
|
+
if (event.type === "agent_settled") session.status = event.aborted === true ? "ready" : "ready";
|
|
89
|
+
if (event.type === "message_end") {
|
|
90
|
+
const message = event.message as { role?: string } | undefined;
|
|
91
|
+
if (message?.role === "assistant" || message?.role === "user") session.messages.push(event.message);
|
|
92
|
+
}
|
|
93
|
+
if (event.type === "session_info_changed" && typeof event.name === "string") session.name = event.name;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
export async function workspacePath(root: string, candidate: string): Promise<string> {
|
|
9
|
+
if (!candidate || path.isAbsolute(candidate) || candidate.includes("\0")) throw new Error("Invalid workspace path");
|
|
10
|
+
const base = await realpath(root);
|
|
11
|
+
const resolved = path.resolve(base, candidate);
|
|
12
|
+
if (resolved !== base && !resolved.startsWith(`${base}${path.sep}`)) throw new Error("Path escapes workspace");
|
|
13
|
+
const actual = await realpath(resolved);
|
|
14
|
+
if (actual !== base && !actual.startsWith(`${base}${path.sep}`)) throw new Error("Path escapes workspace");
|
|
15
|
+
return actual;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function git(root: string, args: string[]): Promise<string> {
|
|
19
|
+
try {
|
|
20
|
+
const { stdout } = await execFileAsync("git", ["-C", root, ...args], { windowsHide: true, maxBuffer: 2 * 1024 * 1024 });
|
|
21
|
+
return stdout;
|
|
22
|
+
} catch (error: unknown) {
|
|
23
|
+
const message = error instanceof Error ? error.message : "Git command failed";
|
|
24
|
+
throw new Error(message.includes("not a git repository") ? "This workspace is not a Git repository." : message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function gitStatus(root: string) {
|
|
29
|
+
const [branch, porcelain] = await Promise.all([git(root, ["branch", "--show-current"]), git(root, ["status", "--porcelain=v1"])]);
|
|
30
|
+
return {
|
|
31
|
+
branch: branch.trim() || "detached HEAD",
|
|
32
|
+
files: porcelain.split("\n").filter(Boolean).map((line) => ({ status: line.slice(0, 2).trim() || "?", path: line.slice(3) }))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function gitDiff(root: string, relativePath: string) {
|
|
37
|
+
await workspacePath(root, relativePath);
|
|
38
|
+
return git(root, ["diff", "--no-ext-diff", "--", relativePath]);
|
|
39
|
+
}
|