@timqi/pier 0.0.29 → 0.1.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/README.md +58 -125
- package/dist/agent/config.js +24 -11
- package/dist/agent/credentials.js +11 -23
- package/dist/agent/events.js +43 -62
- package/dist/agent/listing.js +113 -68
- package/dist/agent/pi.js +204 -211
- package/dist/boards/boards.js +19 -29
- package/dist/channels/attach.js +14 -42
- package/dist/channels/chains.js +33 -37
- package/dist/channels/chunk.js +8 -28
- package/dist/channels/commands.js +3 -14
- package/dist/channels/config.js +33 -52
- package/dist/channels/control.js +4 -13
- package/dist/channels/conversations.js +8 -25
- package/dist/channels/dedup.js +8 -17
- package/dist/channels/gatekeeper.js +13 -23
- package/dist/channels/lark-api.js +23 -63
- package/dist/channels/lark-outbound.js +12 -44
- package/dist/channels/lark-panel.js +8 -24
- package/dist/channels/lark-render.js +18 -62
- package/dist/channels/lark.js +52 -141
- package/dist/channels/lines.js +13 -15
- package/dist/channels/panel.js +16 -36
- package/dist/channels/receipts.js +29 -52
- package/dist/channels/routes.js +3 -9
- package/dist/channels/runtime.js +12 -23
- package/dist/channels/slack-api.js +34 -86
- package/dist/channels/slack-directory.js +7 -23
- package/dist/channels/slack-outbound.js +12 -56
- package/dist/channels/slack-panel.js +4 -13
- package/dist/channels/slack-render.js +23 -91
- package/dist/channels/slack-tool.js +48 -171
- package/dist/channels/slack.js +73 -239
- package/dist/channels/telegram-api.js +8 -20
- package/dist/channels/telegram-panel.js +5 -21
- package/dist/channels/telegram-render.js +13 -40
- package/dist/channels/telegram.js +54 -146
- package/dist/channels/types.js +5 -16
- package/dist/cli.js +17 -41
- package/dist/config-sync.js +87 -4
- package/dist/core/hub.js +7 -20
- package/dist/core/identity.js +20 -59
- package/dist/core/inbound-file.js +15 -49
- package/dist/core/inbox.js +13 -35
- package/dist/core/queue.js +3 -5
- package/dist/core/reply.js +41 -142
- package/dist/core/router.js +209 -260
- package/dist/core/types.js +17 -1
- package/dist/db.js +98 -252
- package/dist/drain.js +58 -51
- package/dist/extensions/index.js +3 -11
- package/dist/extensions/web/anthropic.js +3 -9
- package/dist/extensions/web/artifacts.js +2 -5
- package/dist/extensions/web/content.js +6 -14
- package/dist/extensions/web/http.js +2 -6
- package/dist/extensions/web/language.js +8 -18
- package/dist/extensions/web/openai.js +1 -1
- package/dist/extensions/web/provider.js +5 -18
- package/dist/extensions/web/tools.js +19 -63
- package/dist/lock.js +98 -0
- package/dist/log.js +9 -26
- package/dist/main.js +87 -179
- package/dist/paths.js +10 -26
- package/dist/secrets.js +19 -46
- package/dist/service.js +33 -75
- package/dist/settings.js +42 -65
- package/dist/tasks/agent.js +129 -114
- package/dist/tasks/callbacks.js +9 -19
- package/dist/tasks/command.js +29 -14
- package/dist/tasks/definitions.js +39 -62
- package/dist/tasks/execution.js +46 -42
- package/dist/tasks/groups.js +41 -35
- package/dist/tasks/messages.js +121 -182
- package/dist/tasks/outbox.js +61 -55
- package/dist/tasks/routes.js +5 -11
- package/dist/tasks/runs.js +14 -13
- package/dist/tasks/service.js +53 -54
- package/dist/tasks/store.js +53 -30
- package/dist/tasks/tool.js +132 -61
- package/dist/tools-task.js +20 -60
- package/dist/tools.js +100 -327
- package/dist/update.js +21 -44
- package/dist/web/auth.js +118 -179
- package/dist/web/config-sync.js +2 -2
- package/dist/web/config.js +3 -7
- package/dist/web/explorer.js +10 -21
- package/dist/web/fs.js +20 -42
- package/dist/web/instance.js +45 -85
- package/dist/web/providers.js +14 -13
- package/dist/web/public/assets/{activity-D3m4L2IL.js → activity-B89_hH7q.js} +2 -2
- package/dist/web/public/assets/activity-B89_hH7q.js.br +0 -0
- package/dist/web/public/assets/activity-B89_hH7q.js.gz +0 -0
- package/dist/web/public/assets/boards-BeKW0ZXK.js +1 -0
- package/dist/web/public/assets/boards-BeKW0ZXK.js.br +0 -0
- package/dist/web/public/assets/boards-BeKW0ZXK.js.gz +0 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js +4 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js.br +0 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js.gz +0 -0
- package/dist/web/public/assets/index-DzXDXra_.js +85 -0
- package/dist/web/public/assets/index-DzXDXra_.js.br +0 -0
- package/dist/web/public/assets/index-DzXDXra_.js.gz +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css +2 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.br +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.gz +0 -0
- package/dist/web/public/assets/runs-Cwy0mN8i.js +1 -0
- package/dist/web/public/assets/runs-Cwy0mN8i.js.br +0 -0
- package/dist/web/public/assets/runs-Cwy0mN8i.js.gz +0 -0
- package/dist/web/public/assets/settings-DzZLmujq.js +5 -0
- package/dist/web/public/assets/settings-DzZLmujq.js.br +0 -0
- package/dist/web/public/assets/settings-DzZLmujq.js.gz +0 -0
- package/dist/web/public/assets/task-runs-BCakxFk8.js +3 -0
- package/dist/web/public/assets/task-runs-BCakxFk8.js.br +0 -0
- package/dist/web/public/assets/task-runs-BCakxFk8.js.gz +0 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js +4 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js.br +0 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js.gz +0 -0
- package/dist/web/public/index.html +100 -130
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/manifest.webmanifest +2 -2
- package/dist/web/public/manifest.webmanifest.br +0 -0
- package/dist/web/public/manifest.webmanifest.gz +0 -0
- package/dist/web/public/sw.js +14 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/push.js +55 -77
- package/dist/web/route.js +3 -7
- package/dist/web/server.js +131 -180
- package/dist/web/session-state.js +14 -54
- package/dist/web/types.js +2 -4
- package/dist/web/webpush.js +10 -25
- package/docs/deploy.md +115 -330
- package/package.json +2 -1
- package/skills/pier-boards/SKILL.md +81 -160
- package/skills/pier-help/SKILL.md +23 -20
- package/skills/pier-slack/SKILL.md +2 -2
- package/skills/pier-tasks/SKILL.md +153 -160
- package/dist/config-sync-fetch.js +0 -84
- package/dist/limits.js +0 -14
- package/dist/web/public/assets/activity-D3m4L2IL.js.br +0 -0
- package/dist/web/public/assets/activity-D3m4L2IL.js.gz +0 -0
- package/dist/web/public/assets/boards-BIObcQeX.js +0 -1
- package/dist/web/public/assets/boards-BIObcQeX.js.br +0 -0
- package/dist/web/public/assets/boards-BIObcQeX.js.gz +0 -0
- package/dist/web/public/assets/explorer-C_rSWPNB.js +0 -4
- package/dist/web/public/assets/explorer-C_rSWPNB.js.br +0 -0
- package/dist/web/public/assets/explorer-C_rSWPNB.js.gz +0 -0
- package/dist/web/public/assets/index-CX3fYZY5.css +0 -2
- package/dist/web/public/assets/index-CX3fYZY5.css.br +0 -0
- package/dist/web/public/assets/index-CX3fYZY5.css.gz +0 -0
- package/dist/web/public/assets/index-uFsZkKOQ.js +0 -85
- package/dist/web/public/assets/index-uFsZkKOQ.js.br +0 -0
- package/dist/web/public/assets/index-uFsZkKOQ.js.gz +0 -0
- package/dist/web/public/assets/runs-Ch6DZq6O.js +0 -1
- package/dist/web/public/assets/runs-Ch6DZq6O.js.br +0 -0
- package/dist/web/public/assets/runs-Ch6DZq6O.js.gz +0 -0
- package/dist/web/public/assets/settings-BWcEIEcv.js +0 -5
- package/dist/web/public/assets/settings-BWcEIEcv.js.br +0 -0
- package/dist/web/public/assets/settings-BWcEIEcv.js.gz +0 -0
- package/dist/web/public/assets/task-runs-DPkwv2UE.js +0 -3
- package/dist/web/public/assets/task-runs-DPkwv2UE.js.br +0 -0
- package/dist/web/public/assets/task-runs-DPkwv2UE.js.gz +0 -0
- package/dist/web/public/assets/tasks-DTiCi2mH.js +0 -4
- package/dist/web/public/assets/tasks-DTiCi2mH.js.br +0 -0
- package/dist/web/public/assets/tasks-DTiCi2mH.js.gz +0 -0
|
@@ -3,175 +3,168 @@ name: pier-tasks
|
|
|
3
3
|
description: Delegate work to Pier subagents with the task tool — one-shot, parallel fan-out, chains, mid-run control. Read before delegating to a subagent, coordinating agents, or running long background work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
- Every run executes a persisted task definition. A `run` without `task_id`
|
|
11
|
-
— a bare `prompt`, or a full `task` draft — creates and runs a one-shot
|
|
12
|
-
subagent atomically: `kind:"subagent"`, hidden from `list`, named by the
|
|
13
|
-
prompt's first line. You never file it.
|
|
14
|
-
- **Nothing blocks.** Every operation returns immediately; results, group
|
|
15
|
-
joins and decision replies arrive as system follow-up messages once your
|
|
16
|
-
turn ends (batched when several are due). Ending your turn is the only wait.
|
|
17
|
-
- The child session survives the run — `resume` continues it with context
|
|
18
|
-
intact. `get` polls: `run_id` (full result), `group_id`, or `task_id` (10
|
|
19
|
-
most recent runs). Lists truncate results at 2000 chars, callbacks at 8000,
|
|
20
|
-
both saying so; `get run_id` is whole.
|
|
21
|
-
- Limits: a tree is ≤3 runs deep below the session that started it (depth
|
|
22
|
-
0–2; the fourth `run` errors) and holds ≤16 runs below its root (depth ≥ 1,
|
|
23
|
-
resumes included) — your direct children are each a root, uncounted. 4
|
|
24
|
-
agent runs execute at once instance-wide; the rest wait for a slot without
|
|
25
|
-
error, but the timeout clock starts at queue time, so a run queued behind
|
|
26
|
-
long ones can time out unstarted.
|
|
27
|
-
- Timeout: 900s default → `failed` / `task timed out`; `timeoutSeconds`
|
|
28
|
-
1–86400, draft form only. A Pier restart ends queued/running runs as
|
|
29
|
-
`interrupted` (callback still arrives) and refuses new root runs while
|
|
30
|
-
draining — retry after.
|
|
31
|
-
|
|
32
|
-
## Single delegation (the common case)
|
|
6
|
+
# Pier tasks
|
|
7
|
+
|
|
8
|
+
## Delegate, then wait
|
|
33
9
|
|
|
34
10
|
```json
|
|
35
|
-
{"operation":"run","prompt":"Review src/auth/*.ts
|
|
11
|
+
{"operation":"run","prompt":"Review src/auth/*.ts. Return file:line, issue, fix."}
|
|
36
12
|
```
|
|
37
13
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
14
|
+
One concern per child. `run` with `prompt` or a full `task` draft atomically
|
|
15
|
+
creates a persisted one-shot (`kind:"subagent"`, hidden from `list`); do not
|
|
16
|
+
`create` it. Name defaults to the prompt's first line; `name` overrides it.
|
|
17
|
+
|
|
18
|
+
All operations return immediately. Results and decision replies arrive by
|
|
19
|
+
callback; **end your turn after launching work**. Default callbacks are system
|
|
20
|
+
follow-ups, batched when several are due. A long turn delays them; there is no
|
|
21
|
+
status query. The receipt's `next` tells you where/how delivery happens:
|
|
22
|
+
|
|
23
|
+
- `callback:"origin"` (default): result returns to you after your turn.
|
|
24
|
+
- `callback:"steer"`: interrupts at a step boundary; use when needed mid-turn,
|
|
25
|
+
not for work you launch and forget. Receipt echoes `callbackMode`.
|
|
26
|
+
- `callback:"none"`: no delivery; means you do not want the result, not pull later.
|
|
27
|
+
- Single-run `callback_session_id`: deliver to another existing session.
|
|
28
|
+
Top-level sessions only, and never with `callback:"none"` or `tasks[]`;
|
|
29
|
+
inside a run it is refused, so your result always returns to you.
|
|
30
|
+
|
|
31
|
+
Receipts include `runId`, `taskId`, state. Keep IDs: the callback also names
|
|
32
|
+
`Run:`, and there is no lookup by task. `triggerSource` names the actual invoker
|
|
33
|
+
(`agent/manual/cron/watch/task`); the definition's `trigger` is scheduling
|
|
34
|
+
policy (`manual` means on demand).
|
|
35
|
+
|
|
36
|
+
## Sessions and prompts
|
|
37
|
+
|
|
38
|
+
- Default `fresh`: clean context. `cwd` defaults to your directory, accepts an
|
|
39
|
+
absolute path or one relative to yours, and must exist.
|
|
40
|
+
- `reuse`: existing session by `sessionId`; continues its history after it is
|
|
41
|
+
idle. `launch` applies only to fresh sessions; reused sessions own their
|
|
42
|
+
model/tools. Children otherwise have your tools, including `task`.
|
|
43
|
+
- **The prompt is the handoff**: no implicit copy of your discussion. Include
|
|
44
|
+
the goal, current decisions, latest constraints, absolute paths, acceptance
|
|
45
|
+
criteria, verification and output format. `input` accepts any JSON, appended
|
|
46
|
+
in `<task_input>`.
|
|
47
|
+
- For more work on an existing result, `resume` that run with a delta prompt;
|
|
48
|
+
its session/context survives. Start fresh for unrelated work or stale context.
|
|
49
|
+
- Stored role: `run` with `task_id` and optional `input`;
|
|
50
|
+
`session_mode:"fresh"` overrides a stored reuse policy. Archived tasks refuse.
|
|
51
|
+
|
|
52
|
+
`timeoutSeconds` also works in the shorthand and in a `tasks[]` entry; use a
|
|
53
|
+
`task` draft for reuse:
|
|
44
54
|
|
|
45
55
|
```json
|
|
46
|
-
{"operation":"run","task":{"
|
|
56
|
+
{"operation":"run","task":{"timeoutSeconds":7200,"action":{"type":"agent","session":{"mode":"reuse","sessionId":"..."},"prompt":"Check the result"}}}
|
|
47
57
|
```
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
is
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
59
|
+
Inline drafts may omit `trigger`; only `manual` is allowed. A nested
|
|
60
|
+
`callback` is refused: use the top-level delivery options above. Nested
|
|
61
|
+
callbacks matter for saved schedules (below).
|
|
62
|
+
|
|
63
|
+
## Groups and chains
|
|
64
|
+
|
|
65
|
+
`tasks[]` needs 2+ entries: prompt strings,
|
|
66
|
+
`{prompt,cwd?,launch?,name?,timeoutSeconds?}`, full drafts, or `{task_id}`. Do not combine it with `task`, `task_id` or `session_mode`.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{"operation":"run","tasks":["Review correctness","Review test gaps"],"join":"all"}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `join:"all"` (default): one callback with every member's name, state and result
|
|
73
|
+
when all are terminal.
|
|
74
|
+
- `join:"first"`: first **any terminal state**, including failure/overlap skip,
|
|
75
|
+
wins. Others are cancelled; their sessions remain resumable, IDs in callback.
|
|
76
|
+
- Core owns the join; never hand-aggregate member IDs across turns. Members have
|
|
77
|
+
no individual callbacks; `none`/`steer` applies to the group. `cancel` and
|
|
78
|
+
`recover` accept `group_id`. Admission failure (e.g. limit or missing cwd)
|
|
79
|
+
rolls back the group: nothing runs.
|
|
80
|
+
|
|
81
|
+
For a chain: launch step 1 → end turn → receive callback → put needed results
|
|
82
|
+
in step 2's self-contained prompt. Branching and retries are your responsibility.
|
|
83
|
+
|
|
84
|
+
## Model choice
|
|
85
|
+
|
|
86
|
+
Default: inherit your current model. For harder reasoning, raise
|
|
87
|
+
`launch.thinking` first (`off/minimal/low/medium/high/xhigh/max`). For cheap bulk
|
|
88
|
+
work, choose the same provider's smallest current model with low/off thinking;
|
|
89
|
+
for an independent vendor's opinion, choose its flagship.
|
|
90
|
+
|
|
91
|
+
Before naming a model, call `{"operation":"models"}`; never use IDs from memory.
|
|
92
|
+
It returns operator pins (`source:"menu"`, intent notes/usual thinking) or the
|
|
93
|
+
live catalog (`source:"catalog"`). Prefer a matching pin and its thinking level.
|
|
94
|
+
`launch.model` overrides inheritance. Unknown model IDs fail the run and report
|
|
95
|
+
available IDs by callback; invalid `launch.thinking` fails the call.
|
|
96
|
+
|
|
97
|
+
## Control and decisions
|
|
98
|
+
|
|
99
|
+
`message` must be non-empty, at most 16 KiB. Controls require ownership: your delegated
|
|
100
|
+
trees, or only descendants when you are a subagent.
|
|
101
|
+
|
|
102
|
+
- `steer`: interrupt a child with corrections.
|
|
103
|
+
- `follow_up`: queue guidance after its current turn.
|
|
104
|
+
- `resume`: terminal run only; same session, new run ID/callback, same depth,
|
|
105
|
+
message as prompt. Expires its unanswered decision. Being a new run, it takes
|
|
106
|
+
the same `callback` and `callback_session_id` as `run`, under the same rule:
|
|
107
|
+
a subagent may not redirect them.
|
|
108
|
+
- `cancel`: run or group; cascades to descendants, terminal runs unchanged.
|
|
109
|
+
|
|
110
|
+
`steer`/`follow_up` require a non-terminal run; undelivered guidance expires when
|
|
111
|
+
it ends. For finished work use `resume`.
|
|
112
|
+
|
|
113
|
+
Child → supervisor:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{"operation":"contact","reason":"decision","message":"Use API A or B?"}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Requires an active delegated Agent run with a supervisor (scheduled cron/watch
|
|
120
|
+
runs have none). `progress` is fire-and-forget; `decision` steers the supervisor
|
|
121
|
+
and permits one open question per run. State what you await and **end your
|
|
122
|
+
turn**, never spin/poll. A finished run with an open question suppresses its
|
|
123
|
+
completion callback: the question is the notification.
|
|
124
|
+
|
|
125
|
+
Supervisor → child:
|
|
81
126
|
|
|
82
127
|
```json
|
|
83
|
-
{"operation":"
|
|
84
|
-
"Review src/ for correctness bugs. Output: file:line + issue.",
|
|
85
|
-
{"prompt":"Review the tests for gaps. Output: missing case + file.","cwd":"packages/web"}]}
|
|
128
|
+
{"operation":"reply","message_id":"...","message":"Use API A"}
|
|
86
129
|
```
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
unanswered decision on that run
|
|
129
|
-
- `cancel` — stop a run, or a whole group via `group_id`; cascades to the
|
|
130
|
-
run's children; a terminal run is returned unchanged
|
|
131
|
-
|
|
132
|
-
`steer`/`follow_up` need a non-terminal run (else `resume`); one undelivered
|
|
133
|
-
when the run ends expires. `resume` needs a terminal run. All four accept only
|
|
134
|
-
runs you own — trees you started, or, inside a run, your own descendants.
|
|
135
|
-
`message`: non-empty, <16 KiB.
|
|
136
|
-
|
|
137
|
-
## Supervisor decisions (as a child)
|
|
138
|
-
|
|
139
|
-
`{"operation":"contact","reason":"decision","message":"..."}` returns a
|
|
140
|
-
receipt immediately. State what you await and **end your turn** — never spin
|
|
141
|
-
or poll. The answer is steered into your current turn if your run is still
|
|
142
|
-
active; if it finished, Pier resumes your session with the reply as the
|
|
143
|
-
prompt. One open decision per run. `contact` works only inside a run a session
|
|
144
|
-
delegated — a cron/watch run has no supervisor. `reason:"progress"` is
|
|
145
|
-
fire-and-forget status for the parent.
|
|
146
|
-
|
|
147
|
-
As a parent: the question arrives as a system input with a message id, steered
|
|
148
|
-
into your current turn at the next step boundary so the child does not wait
|
|
149
|
-
out your turn. Answer with `{"operation":"reply","message_id":"...",
|
|
150
|
-
"message":"..."}` — only the addressed supervisor may; same text twice is a
|
|
151
|
-
no-op, different text an error. A run that finished while awaiting your answer
|
|
152
|
-
shows `pendingDecisionId` in `get` and sends no completion callback — the
|
|
153
|
-
question is the notification; your reply resumes it, and that continuation's
|
|
154
|
-
callback comes to you.
|
|
155
|
-
|
|
156
|
-
## Rules
|
|
157
|
-
|
|
158
|
-
- One concern per child. For fan-out, let core join via `tasks[]` — never
|
|
159
|
-
hand-aggregate run ids across turns.
|
|
160
|
-
- **A change to work a child already did → `resume` that run**, not a new
|
|
161
|
-
child: its session still holds the files it read and the decisions it made,
|
|
162
|
-
so the message can be a delta ("same file, also handle the empty input").
|
|
163
|
-
Go `fresh` when the new work shares nothing with the old — different area,
|
|
164
|
-
different goal — or when that session is long and mostly dead ends: clean
|
|
165
|
-
context plus a self-contained prompt beats a child sifting its own noise.
|
|
166
|
-
Lost the `run_id`? `get` with the summary's `task_id` lists that task's
|
|
167
|
-
recent runs — a one-shot subagent has one too.
|
|
168
|
-
- `create` is for definitions that outlive one job: a schedule (`cron` /
|
|
169
|
-
`watch`), or a role run by `task_id` again and again. It files a task the
|
|
170
|
-
operator sees and must archive by hand — never for a one-off; that is `run`
|
|
171
|
-
with a prompt. `update` takes the whole draft again, `trigger` included. A
|
|
172
|
-
schedule reports to nobody unless its `callback` is
|
|
173
|
-
`{"type":"session","sessionId":"..."}` — `origin` is the invoker, which a
|
|
174
|
-
cron run lacks.
|
|
175
|
-
- Inside a run: Agent tasks only (stored or inline), no `reuse`, no
|
|
176
|
-
`create`/`update`.
|
|
177
|
-
- After launching work, end your turn. The callback starts your next one.
|
|
131
|
+
Only the addressed supervisor may reply. Same text twice is a no-op; different
|
|
132
|
+
text errors. The answer steers an active child or auto-resumes a terminal one
|
|
133
|
+
with the reply as prompt; the continuation's callback returns to the replier.
|
|
134
|
+
|
|
135
|
+
## Recover lost information only
|
|
136
|
+
|
|
137
|
+
`recover` requires `run_id` or `group_id` plus `reason` (e.g. text truncated,
|
|
138
|
+
context lost to compaction). **Never use it to check progress.**
|
|
139
|
+
|
|
140
|
+
- Run must be terminal and its callback settled: delivered, abandoned, or none.
|
|
141
|
+
Group members wait for their group callback; a terminal race winner is
|
|
142
|
+
readable while losers are still cancelling.
|
|
143
|
+
- Whole-group recovery additionally requires every member terminal. An open
|
|
144
|
+
decision directs you to `reply`. Other premature requests get a refusal
|
|
145
|
+
that reveals no state.
|
|
146
|
+
- Callbacks truncate each result at 8000 characters; recovered groups truncate
|
|
147
|
+
member results at 2000. Notes point to `recover run_id`, which returns full
|
|
148
|
+
text. `none` remains a no-result preference, not a polling workflow.
|
|
149
|
+
|
|
150
|
+
## Persistence and limits
|
|
151
|
+
|
|
152
|
+
`create` files an operator-visible definition only for recurring roles or
|
|
153
|
+
cron/watch schedules; the operator must archive it. `update` takes the entire
|
|
154
|
+
draft, including `trigger`. Schedules notify only with nested
|
|
155
|
+
`callback:{"type":"session","sessionId":"..."}`; `origin` needs an invoker,
|
|
156
|
+
which scheduled runs lack.
|
|
157
|
+
|
|
158
|
+
- Inside a run: Agent actions only (stored/inline), no reuse or create/update.
|
|
159
|
+
- Depth 0–2: three levels of nesting below the invoking session; a fourth
|
|
160
|
+
level errors. Each root allows 16 descendant runs (depth ≥1, resumes included). Your direct
|
|
161
|
+
children are separate roots and do not count toward that limit.
|
|
162
|
+
- 6 Agent runs execute instance-wide; others queue without error. A queued run
|
|
163
|
+
waits **unbounded**, ended only by cancellation or a restart.
|
|
164
|
+
- Default timeout 3600s; `timeoutSeconds:1–86400` in a draft, the `prompt`
|
|
165
|
+
shorthand or a `tasks[]` entry. It starts when the run does, not at enqueue,
|
|
166
|
+
and reports `failed / task timed out`.
|
|
167
|
+
- Restart marks queued/running runs `interrupted`; callbacks still apply.
|
|
168
|
+
During drain, new roots are refused: retry after restart.
|
|
169
|
+
- Watch probes that matched nothing are kept only 50 deep per watch; older
|
|
170
|
+
ones are deleted unless a message, a pending callback or a resume needs them.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Fetch one configuration document from the operator's source URL: HTTPS with
|
|
2
|
-
// no credentials, redirects followed while they stay HTTPS, JSON capped at
|
|
3
|
-
// 1 MiB so a hostile or broken source cannot exhaust memory.
|
|
4
|
-
export const CONFIG_SYNC_BYTES = 1024 * 1024;
|
|
5
|
-
const MAX_REDIRECTS = 5;
|
|
6
|
-
export function configSourceUrl(raw) {
|
|
7
|
-
let url;
|
|
8
|
-
try {
|
|
9
|
-
url = new URL(raw.trim());
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
throw new Error("A valid HTTPS source URL is required");
|
|
13
|
-
}
|
|
14
|
-
if (url.protocol !== "https:" || url.username || url.password || url.hash || raw.length > 4096) {
|
|
15
|
-
throw new Error("Source must be HTTPS, without credentials or a fragment");
|
|
16
|
-
}
|
|
17
|
-
return url;
|
|
18
|
-
}
|
|
19
|
-
export async function downloadConfig(raw, etag, signal) {
|
|
20
|
-
let url = configSourceUrl(raw);
|
|
21
|
-
for (let hop = 0;; hop++) {
|
|
22
|
-
signal.throwIfAborted();
|
|
23
|
-
let res;
|
|
24
|
-
try {
|
|
25
|
-
res = await fetch(url, {
|
|
26
|
-
method: "GET", redirect: "manual", signal,
|
|
27
|
-
headers: { accept: "application/json", ...(etag ? { "if-none-match": etag } : {}) },
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
throw new Error(signal.aborted ? "Configuration download timed out or was cancelled" : "Could not connect to source");
|
|
32
|
-
}
|
|
33
|
-
const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
|
|
34
|
-
if (location) {
|
|
35
|
-
await res.body?.cancel().catch(() => { });
|
|
36
|
-
if (hop >= MAX_REDIRECTS)
|
|
37
|
-
throw new Error("Source redirected too many times");
|
|
38
|
-
try {
|
|
39
|
-
url = configSourceUrl(new URL(location, url).href);
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
throw new Error("Source redirected to a location that is not HTTPS");
|
|
43
|
-
}
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (res.status !== 200 && res.status !== 304) {
|
|
47
|
-
await res.body?.cancel().catch(() => { });
|
|
48
|
-
throw new Error(res.status === 404 || res.status === 410
|
|
49
|
-
? "Source link was revoked or does not exist"
|
|
50
|
-
: `Source returned HTTP ${String(res.status)}`);
|
|
51
|
-
}
|
|
52
|
-
const status = res.status === 304 ? 304 : 200;
|
|
53
|
-
if (status === 200 && !/^application\/json(?:\s*;|$)/i.test(res.headers.get("content-type") ?? "")) {
|
|
54
|
-
await res.body?.cancel().catch(() => { });
|
|
55
|
-
throw new Error("Source did not return JSON");
|
|
56
|
-
}
|
|
57
|
-
return { status, etag: res.headers.get("etag"), body: await read(res, signal) };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async function read(res, signal) {
|
|
61
|
-
const reader = res.body?.getReader();
|
|
62
|
-
if (!reader)
|
|
63
|
-
return "";
|
|
64
|
-
const chunks = [];
|
|
65
|
-
let size = 0;
|
|
66
|
-
try {
|
|
67
|
-
for (;;) {
|
|
68
|
-
const { done, value } = await reader.read();
|
|
69
|
-
if (done)
|
|
70
|
-
break;
|
|
71
|
-
size += value.length;
|
|
72
|
-
if (size > CONFIG_SYNC_BYTES)
|
|
73
|
-
throw new Error("Configuration exceeds 1 MiB");
|
|
74
|
-
chunks.push(Buffer.from(value));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
await reader.cancel().catch(() => { });
|
|
79
|
-
if (err instanceof Error && err.message === "Configuration exceeds 1 MiB")
|
|
80
|
-
throw err;
|
|
81
|
-
throw new Error(signal.aborted ? "Configuration download timed out or was cancelled" : "Configuration download failed");
|
|
82
|
-
}
|
|
83
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
84
|
-
}
|
package/dist/limits.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// The numbers more than one area has to agree on.
|
|
2
|
-
//
|
|
3
|
-
// Not policy or behaviour — a value several modules must spell the same way,
|
|
4
|
-
// and a wrong copy makes two surfaces disagree about one session: a title
|
|
5
|
-
// truncated to a different length depending on which path derived it.
|
|
6
|
-
//
|
|
7
|
-
// Here because a leaf may be imported by every area and depends on nothing
|
|
8
|
-
// itself, which is the only shape that fits: agent/ derives a title and must
|
|
9
|
-
// not import core/, web/ derives one too and must not import agent/, and the
|
|
10
|
-
// browser needs the same numbers with no runtime behind them.
|
|
11
|
-
/** How much of a message becomes a title, wherever one is derived: the listing
|
|
12
|
-
* reading a transcript (agent/listing.ts), a rename's fallback (agent/pi.ts),
|
|
13
|
-
* the fill at first prompt and the rename boundary (web/). */
|
|
14
|
-
export const SESSION_TITLE_MAX = 80;
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{F as e,G as t,H as n,K as r,P as i,R as a,U as o,g as s,k as c,z as l}from"./index-uFsZkKOQ.js";var u=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function d(d,f){let p=[],m=``,h=a(`header`,`sticky top-0 z-30 flex h-10 items-center gap-3 border-b border-neutral-200 bg-white px-4`,a(`span`,`font-medium max-md:hidden`,`Boards`)),g=a(`div`,`px-4 py-5`);d.append(a(`div`,`min-h-0 flex-1 overflow-y-auto`,h,g));async function _(e,t){let i=await r(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(i.ok)return e.token=(await i.json().catch(()=>({}))).token??``,x();m=await n(i,`Could not change ${e.slug}`),await S()}async function v(e){m=await t(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await S()}function y(t){let n=a(`div`,`group flex flex-col gap-1 border-b border-neutral-200/70 px-1 py-2.5 last:border-b-0`),r=document.createElement(`a`);r.className=`truncate font-medium text-neutral-800 hover:text-indigo-700`,r.href=u(t),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=t.title;let i=a(`div`,`flex items-center gap-2`,r,a(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,t.slug)),o=c(``,``,t.public,e=>{t.public=e,_(t,e)});o.className=`ml-auto flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,o.append(a(`span`,``,`Public`));let d=s(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100`);d.title=`Renames the folder on disk; nothing is erased`,d.onclick=()=>void v(t.slug);let p=e(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100`,()=>`${location.origin}${u(t)}`);p.title=`Copy the board's link`,i.append(o,p,d),n.append(i);let m=a(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);t.description&&m.append(a(`span`,`min-w-0 truncate`,t.description)),m.append(a(`span`,`flex-none text-neutral-400`,l(Date.parse(t.updatedAt))));for(let e of t.sessions){let t=s(e.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);t.title=`Open session ${e}`,t.onclick=()=>f(e),m.append(t)}if(t.public&&t.token){let e=a(`span`,`flex-none font-mono text-neutral-400`,u(t));e.title=`Public URL — anyone holding this link can read the board, no password`,m.append(e)}return n.append(m),n}let b=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Reachable from the Console only.`,wanted:!1}];function x(){let e=a(`div`,`mx-auto flex max-w-3xl flex-col`);p.length===0&&e.append(a(`p`,`rounded-xl border border-dashed border-neutral-300 px-4 py-6 text-center text-[12.5px] text-neutral-500`,`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:n,wanted:r}of b){let i=p.filter(e=>e.public===r);if(i.length===0)continue;let o=a(`section`,`mb-6`);o.append(a(`h2`,`text-[13px] font-semibold text-neutral-700`,t)),o.append(a(`p`,`mb-1 text-[11.5px] leading-snug text-neutral-500`,n));let s=a(`div`,`rounded-xl border border-neutral-200 bg-white px-3`);s.append(...i.map(y)),o.append(s),e.append(o)}g.replaceChildren(...m?[a(`p`,`mb-3 text-[13px] text-red-600`,m)]:[],e)}async function S(){let e=await o(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=i(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{I as e,N as t,P as n,R as r,W as i,a,d as o,f as s,i as c,n as l,s as u,u as d,z as f}from"./index-uFsZkKOQ.js";var p=e=>{let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`},m=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?p(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
|
|
2
|
-
|
|
3
|
-
`),h=null;function g(){h&&(h.remove(),h=null,document.removeEventListener(`pointerdown`,g,!0),window.removeEventListener(`scroll`,g,!0))}function ee(e,t){e.onmouseenter=()=>{g();let n=t();if(!n)return;h=r(`div`,`pointer-events-none fixed z-50 max-w-96 whitespace-pre-wrap rounded-lg bg-neutral-800/95 px-3 py-2 font-sans text-[11.5px] leading-snug text-neutral-100 shadow-lg`),h.textContent=n,document.body.append(h),document.addEventListener(`pointerdown`,g,!0),window.addEventListener(`scroll`,g,!0);let i=e.getBoundingClientRect();h.style.left=`${Math.max(8,Math.min(i.left,window.innerWidth-h.offsetWidth-8))}px`,h.style.top=i.bottom+6+h.offsetHeight>window.innerHeight?`${i.top-h.offsetHeight-6}px`:`${i.bottom+6}px`},e.onmouseleave=g}function _(e,t,n){let i=r(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,r(`span`,`w-3 flex-none text-indigo-600`,e.value===t?`✓`:``),r(`span`,`flex-none font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&i.append(r(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&i.append(r(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?f(e.at):``}`)),ee(i,()=>m(e)),i.onclick=()=>n(e.value),i}function te(e,t,n,i,o){let{base:s,head:l}=n,u=()=>l&&s===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=t.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...t.refs.map(e=>({value:e.name,subject:e.subject})),...t.commits.map(e=>({...e,value:e.hash}))],m=r(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,t)=>{let n=r(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===t?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return n.onclick=()=>{d=t,h()},n},n=(e,t,n,i)=>r(`div`,`flex min-w-0 flex-1 flex-col`,r(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),r(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>_(e,n,i)))),a=d===`compare`?r(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,n(`base`,p(!1),s,e=>{s=e,h(),i(s,l)}),n(`to`,p(!0),l,e=>{l=e,h(),i(s,l)})):r(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...t.commits.map(e=>_({value:e.hash,...e},u(),e=>{s=`${e}~1`,l=e,c(),i(s,l)}))),f=r(`div`,`flex-none border-t border-neutral-200`,_({value:o,label:`↺ ${o} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},s===o&&l===``?o:``,()=>{s=o,l=``,h(),i(s,l)}));m.replaceChildren(r(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,e(`Compare two points`,`compare`),e(`One commit`,`commit`)),a,...d===`compare`?[f]:[])}h(),a(e,m)}var ne=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,v={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},re=30,y=`pier.filesPrefs`,b=50,x=()=>{try{let e=JSON.parse(localStorage.getItem(y)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},S=e=>{let t=e?x()[e]:void 0;return typeof t?.cwd==`string`&&typeof t.base==`string`&&typeof t.head==`string`?{cwd:t.cwd,base:t.base,head:t.head}:null},ie=(e,t)=>{let n=x();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-b)))delete n[e];try{localStorage.setItem(y,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},ae=(e,t=`indigo`)=>{let n=r(`button`,`min-w-0 cursor-pointer truncate rounded-md px-2 py-0.5 text-left font-mono text-[11.5px] ${t===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`,e);return n.type=`button`,n};function C(a,c,f,p){let h=``,g=``,_={branch:null,refs:[],commits:[],worktrees:[]},y=`HEAD`,b=``,x=new Map,C=!0,w=!1,T=[],E=new Set,D=null,O=null,k=r(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),A=r(`div`,`flex-none border-b border-neutral-200`),j=r(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),oe=r(`aside`,`flex w-64 flex-none flex-col border-r border-neutral-200 text-[12.5px] max-md:h-2/5 max-md:w-full max-md:border-b max-md:border-r-0`,A,j),M=r(`div`,`h-full min-w-0 overflow-auto`),N=r(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),se=r(`section`,`relative min-h-0 min-w-0 flex-1`,M,N);a.append(k,r(`div`,`flex min-h-0 flex-1 max-md:flex-col`,oe,se));let P=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:h,...t})}`,F=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:h,...t})}`,I=(e,t=`text-neutral-400`)=>r(`p`,`px-4 py-3 text-[12.5px] ${t}`,e),L=e=>{let t=0;for(let n of x.keys())n.startsWith(`${e}/`)&&t++;return t};function R(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of x){if(i.status!==`D`||!e.startsWith(r))continue;let a=e.slice(r.length).split(`/`)[0];n.has(a)||(n.add(a),t.push({name:a,dir:e.slice(r.length).includes(`/`)}))}let i=t;return C&&_.branch&&(i=t.filter(e=>e.dir?L(`${r}${e.name}`)>0:x.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?ce(`${r}${e.name}`,e.name):le(`${r}${e.name}`,e.name))}async function z(e,t){try{let{entries:n}=await i(F(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...R(t,n)),e.childElementCount||e.append(I(C?`No changes.`:`Empty.`))}catch(n){let r=R(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(I(String(n),`text-red-600`))}}function ce(t,n){let i=L(t),a=[r(`span`,`truncate`,n)];i&&a.push(r(`span`,`ml-auto flex-none rounded bg-amber-100/80 px-1 font-mono text-[10px] text-amber-700`,String(i)));let{el:o,summary:s}=e(``,a);s.className+=` px-2 py-0.5 hover:bg-neutral-100`;let c=r(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(I(`…`)),z(c,t))};return o.ontoggle=()=>{E[o.open?`add`:`delete`](t),o.open&&u()},(C&&_.branch&&!w||E.has(t))&&(o.open=!0,u()),o}function B(e){O?.classList.remove(`bg-indigo-50`,`text-indigo-700`),O=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function le(e,t){let n=x.get(e)?.status,i=r(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,r(`span`,`truncate`,t));return n&&i.append(r(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${v[n]??`text-neutral-500`}`,n)),i.title=e,e===D&&B(i),i.onclick=()=>{B(i),D=e,de(e)},i}let V=()=>void z(j,``),H=(e,t)=>[...e?[r(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[r(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function U(e,n,i){let a=x.get(e),o=r(`div`,`sticky top-0 z-10 flex items-center gap-2 border-b border-neutral-200 bg-white px-4 py-1.5 font-mono text-[11.5px] text-neutral-500`,r(`span`,`truncate`,e));a&&(o.append(r(`span`,`flex-none font-semibold ${v[a.status]??``}`,a.status)),o.append(...H(a.add,a.del)));let s=r(`div`,`ml-auto flex flex-none items-center gap-2`);if(i&&s.append(i),n){let n=document.createElement(`a`);n.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,n.href=F(`file`,{path:e}),n.download=t(e),n.textContent=`Download`,s.append(n)}return o.append(s),o}function ue(e){let t=0;for(;t<e.length;){if(e[t].tone!==`del`){t++;continue}let n=t;for(;t<e.length&&e[t].tone===`del`;)t++;let r=t;for(;t<e.length&&e[t].tone===`add`;)t++;let i=Math.min(r-n,t-r);for(let t=0;t<i;t++){let i=e[n+t],a=e[r+t],o=Math.min(i.text.length,a.text.length),s=0;for(;s<o&&i.text[s]===a.text[s];)s++;let c=0;for(;c<o-s&&i.text[i.text.length-1-c]===a.text[a.text.length-1-c];)c++;s+c!==0&&(i.mark=[s,i.text.length-c],a.mark=[s,a.text.length-c])}}}let W=ve();function G(){N.classList.add(`hidden`),N.replaceChildren(),W.set([],[])}let K=0,q=e=>e===K;function de(e){let t=++K;return x.has(e)?ye(e,t):fe(e,t)}async function fe(e,t){if(!q(t))return;G();let n=F(`file`,{path:e}),i=r(`div`,`min-w-0`);if(M.classList.remove(`flex`,`flex-col`),M.replaceChildren(U(e,!0),i),ne.test(e)){let e=r(`img`,`max-w-full p-4`);e.src=n,e.onerror=()=>i.replaceChildren(I(`Could not load image.`,`text-red-600`)),i.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let t=document.createElement(`iframe`);t.className=`h-full w-full`,t.src=n,M.replaceChildren(U(e,!0),t),M.classList.add(`flex`,`flex-col`);return}i.append(I(`…`));let a=await fetch(n);if(!q(t))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(I(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(I(`Binary file — use Download.`));return}let c=o(await a.text());if(!q(t))return;let l=await s(e);q(t)&&i.replaceChildren(d(c,l))}function pe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
4
|
-
`);a.at(-1)===``&&a.pop();for(let e of a){let a=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(e);if(a){i&&t.push({nums:[``,``],text:`⋯`,tone:``}),[n,r]=[Number(a[1]),Number(a[2])],i=!0;continue}!i||e.startsWith(`\\`)||(e.startsWith(`+`)?t.push({nums:[``,r++],text:e.slice(1),tone:`add`}):e.startsWith(`-`)?t.push({nums:[n++,``],text:e.slice(1),tone:`del`}):t.push({nums:[n++,r++],text:e.slice(1),tone:``}))}return t}function me(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n].tone;if(!r)continue;let i=t.at(-1);i&&i.end===n-1?(i.end=n,(r===`add`&&i.tone===`del`||r===`del`&&i.tone===`add`)&&(i.tone=`mixed`)):t.push({start:n,end:n,tone:r})}return t}let he={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function ge(e,t,n){N.replaceChildren(...e.map(e=>{let i=r(`button`,`pointer-events-auto absolute w-full cursor-pointer ${he[e.tone]}`);return i.style.top=`${e.start/t*100}%`,i.style.height=`${Math.max(.4,(e.end-e.start+1)/t*100)}%`,i.title=`line ${_e(n,e.start)}`,i.onclick=()=>n[e.start].scrollIntoView({block:`center`}),i})),N.classList.remove(`hidden`)}let _e=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function ve(){let e=[],t=[],n=-1,i=r(`span`,`flex-none text-[10.5px] text-neutral-400`),o=r=>{e.length&&(n=(n+r+e.length)%e.length,t[e[n].start].scrollIntoView({block:`center`}),i.textContent=`${n+1}/${e.length}`)},s=()=>e.length>0&&!a.classList.contains(`hidden`),c=(e,t,n,i)=>{let a=r(`button`,`icon-btn flex-none`,e);return a.onclick=()=>o(t),u(a,i,n,()=>o(t),s),a};return{el:r(`div`,`flex flex-none items-center gap-1`,i,c(`↑`,-1,`Previous change`,[`p`,`k`]),c(`↓`,1,`Next change`,[`n`,`j`])),set(r,a){e=r,t=a,n=-1,i.textContent=`${r.length} change${r.length===1?``:`s`}`}}}async function ye(e,t){G(),M.classList.remove(`flex`,`flex-col`);let n=x.get(e)?.status!==`D`;M.replaceChildren(U(e,n),I(`…`));try{let{diff:r}=await i(P(`diff`,{base:y,head:b,file:e,context:`99999`}),`could not read this diff`);if(!q(t))return;if(!r||/^Binary files /m.test(r))return fe(e,t);let a=pe(r);ue(a);let o=await s(e);if(!q(t))return;let c=d(a,o),l=[...c.children],u=me(a);W.set(u,l),M.replaceChildren(U(e,n,W.el),c),ge(u,a.length,l)}catch(n){q(t)&&M.replaceChildren(U(e,!1),I(String(n),`text-red-600`))}}let J=()=>b&&y===`${b}~1`?b:``,be=()=>_.commits.find(e=>e.hash===J())?.subject??``,xe=()=>J()?`${J()} — ${be()||`commit`}`:`${y} ↔ ${b||`working tree`}`,Se=(e,t,n,i)=>{let a=r(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${n?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return a.innerHTML=e,a.title=t,a.onclick=i,a};function Y(){let e=Se(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="M2.5 3h11l-4.25 5v4.5l-2.5 1.25V8L2.5 3z" stroke-linejoin="round"/></svg>`,`Show only files that differ`,C,()=>{C=!C,Y(),V()}),t=Se(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="m4.5 2.5 3.5 3 3.5-3M4.5 13.5l3.5-3 3.5 3" stroke-linecap="round" stroke-linejoin="round"/></svg>`,w?`Restore folders`:`Collapse all folders`,w,()=>{if(w=!w,w)T=[...E],E.clear();else for(let e of T)E.add(e);Y(),V()});if(!_.branch){A.replaceChildren(r(`div`,`flex items-center justify-end gap-1 p-1.5`,t));return}let n=ae(xe());if(J()){let e=_.commits.find(e=>e.hash===J());ee(n,()=>e?m(e):``)}else n.title=`Choose what to diff`;n.onclick=async()=>{await Te(),te(n,_,{base:y,head:b},(e,t)=>{y=e,b=t,Z()},$())};let[i,a]=[0,0];for(let e of x.values())i+=e.add,a+=e.del;A.replaceChildren(r(`div`,`flex flex-col gap-1 p-2`,n,r(`div`,`flex items-center gap-1.5 pl-1`,r(`span`,`text-[11px] text-neutral-400`,`${x.size} changed file${x.size===1?``:`s`}`),...H(i,a),r(`span`,`ml-auto`),e,t)))}let Ce=()=>{g&&h&&ie(g,{cwd:h,base:y,head:b})},X=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||_.refs.some(e=>e.name===t)||_.commits.some(e=>e.hash===t)};async function we(){if(x=new Map,!_.branch)return;let{files:e}=await i(P(`diff`,{base:y,head:b}),`could not read what changed`);for(let t of e)x.set(t.path,{status:t.status,add:t.add,del:t.del});if(x.size<=re)for(let e of x.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)E.add(t.slice(0,e).join(`/`))}}async function Z(){Ce();try{await we()}catch(e){Y(),j.replaceChildren(I(String(e),`text-red-600`));return}Y(),V(),D&&de(D)}function Q(){let e=ae(h||`Choose a folder…`,`neutral`);e.className+=` max-w-72`,e.title=`Switch folder`,e.onclick=()=>l(e,_.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),h||void 0,f);let t=r(`button`,`icon-btn`,`✕`);t.type=`button`,t.title=`Close Files`,t.setAttribute(`aria-label`,`Close Files`),t.onclick=p,t.classList.add(`ml-auto`),k.replaceChildren(e,r(`span`,`truncate font-mono text-[11.5px] text-neutral-400`,_.branch?`⎇ ${_.branch}`:`no git`),t)}async function Te(){if(h)try{_=await i(P(`git`,{}),`could not read this repository`),Q()}catch(e){M.replaceChildren(I(String(e),`text-red-600`))}}let $=()=>_.refs.some(e=>e.name===`main`)?`main`:_.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ee(){if(Q(),D=null,O=null,E.clear(),G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`)),!h){A.replaceChildren(),j.replaceChildren(),M.replaceChildren(I(`No folder yet — pick one from the chip above.`));return}try{_=await i(P(`git`,{}),`could not read this repository`)}catch(e){_={branch:null,refs:[],commits:[],worktrees:[]},M.replaceChildren(I(String(e),`text-red-600`))}let e=S(g),t=e&&e.cwd===h&&X(e.base)&&X(e.head)?e:null;y=t?.base??$(),b=t?.head??``,Q(),await Z()}return n(a,e=>{let t=c(),n=t?.id??``,r=e?.startsWith(`/`)?e:(n===g?h:``)||S(n)?.cwd||t?.cwd||``;if(r===h&&n===g&&j.childElementCount){Q(),Te().then(Z);return}g=n,h=r,Ee()},()=>{G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`))})}export{C as createExplorerView};
|
|
Binary file
|
|
Binary file
|