@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.
Files changed (165) hide show
  1. package/README.md +58 -125
  2. package/dist/agent/config.js +24 -11
  3. package/dist/agent/credentials.js +11 -23
  4. package/dist/agent/events.js +43 -62
  5. package/dist/agent/listing.js +113 -68
  6. package/dist/agent/pi.js +204 -211
  7. package/dist/boards/boards.js +19 -29
  8. package/dist/channels/attach.js +14 -42
  9. package/dist/channels/chains.js +33 -37
  10. package/dist/channels/chunk.js +8 -28
  11. package/dist/channels/commands.js +3 -14
  12. package/dist/channels/config.js +33 -52
  13. package/dist/channels/control.js +4 -13
  14. package/dist/channels/conversations.js +8 -25
  15. package/dist/channels/dedup.js +8 -17
  16. package/dist/channels/gatekeeper.js +13 -23
  17. package/dist/channels/lark-api.js +23 -63
  18. package/dist/channels/lark-outbound.js +12 -44
  19. package/dist/channels/lark-panel.js +8 -24
  20. package/dist/channels/lark-render.js +18 -62
  21. package/dist/channels/lark.js +52 -141
  22. package/dist/channels/lines.js +13 -15
  23. package/dist/channels/panel.js +16 -36
  24. package/dist/channels/receipts.js +29 -52
  25. package/dist/channels/routes.js +3 -9
  26. package/dist/channels/runtime.js +12 -23
  27. package/dist/channels/slack-api.js +34 -86
  28. package/dist/channels/slack-directory.js +7 -23
  29. package/dist/channels/slack-outbound.js +12 -56
  30. package/dist/channels/slack-panel.js +4 -13
  31. package/dist/channels/slack-render.js +23 -91
  32. package/dist/channels/slack-tool.js +48 -171
  33. package/dist/channels/slack.js +73 -239
  34. package/dist/channels/telegram-api.js +8 -20
  35. package/dist/channels/telegram-panel.js +5 -21
  36. package/dist/channels/telegram-render.js +13 -40
  37. package/dist/channels/telegram.js +54 -146
  38. package/dist/channels/types.js +5 -16
  39. package/dist/cli.js +17 -41
  40. package/dist/config-sync.js +87 -4
  41. package/dist/core/hub.js +7 -20
  42. package/dist/core/identity.js +20 -59
  43. package/dist/core/inbound-file.js +15 -49
  44. package/dist/core/inbox.js +13 -35
  45. package/dist/core/queue.js +3 -5
  46. package/dist/core/reply.js +41 -142
  47. package/dist/core/router.js +209 -260
  48. package/dist/core/types.js +17 -1
  49. package/dist/db.js +98 -252
  50. package/dist/drain.js +58 -51
  51. package/dist/extensions/index.js +3 -11
  52. package/dist/extensions/web/anthropic.js +3 -9
  53. package/dist/extensions/web/artifacts.js +2 -5
  54. package/dist/extensions/web/content.js +6 -14
  55. package/dist/extensions/web/http.js +2 -6
  56. package/dist/extensions/web/language.js +8 -18
  57. package/dist/extensions/web/openai.js +1 -1
  58. package/dist/extensions/web/provider.js +5 -18
  59. package/dist/extensions/web/tools.js +19 -63
  60. package/dist/lock.js +98 -0
  61. package/dist/log.js +9 -26
  62. package/dist/main.js +87 -179
  63. package/dist/paths.js +10 -26
  64. package/dist/secrets.js +19 -46
  65. package/dist/service.js +33 -75
  66. package/dist/settings.js +42 -65
  67. package/dist/tasks/agent.js +129 -114
  68. package/dist/tasks/callbacks.js +9 -19
  69. package/dist/tasks/command.js +29 -14
  70. package/dist/tasks/definitions.js +39 -62
  71. package/dist/tasks/execution.js +46 -42
  72. package/dist/tasks/groups.js +41 -35
  73. package/dist/tasks/messages.js +121 -182
  74. package/dist/tasks/outbox.js +61 -55
  75. package/dist/tasks/routes.js +5 -11
  76. package/dist/tasks/runs.js +14 -13
  77. package/dist/tasks/service.js +53 -54
  78. package/dist/tasks/store.js +53 -30
  79. package/dist/tasks/tool.js +132 -61
  80. package/dist/tools-task.js +20 -60
  81. package/dist/tools.js +100 -327
  82. package/dist/update.js +21 -44
  83. package/dist/web/auth.js +118 -179
  84. package/dist/web/config-sync.js +2 -2
  85. package/dist/web/config.js +3 -7
  86. package/dist/web/explorer.js +10 -21
  87. package/dist/web/fs.js +20 -42
  88. package/dist/web/instance.js +45 -85
  89. package/dist/web/providers.js +14 -13
  90. package/dist/web/public/assets/{activity-D3m4L2IL.js → activity-B89_hH7q.js} +2 -2
  91. package/dist/web/public/assets/activity-B89_hH7q.js.br +0 -0
  92. package/dist/web/public/assets/activity-B89_hH7q.js.gz +0 -0
  93. package/dist/web/public/assets/boards-BeKW0ZXK.js +1 -0
  94. package/dist/web/public/assets/boards-BeKW0ZXK.js.br +0 -0
  95. package/dist/web/public/assets/boards-BeKW0ZXK.js.gz +0 -0
  96. package/dist/web/public/assets/explorer-DIuMlaV3.js +4 -0
  97. package/dist/web/public/assets/explorer-DIuMlaV3.js.br +0 -0
  98. package/dist/web/public/assets/explorer-DIuMlaV3.js.gz +0 -0
  99. package/dist/web/public/assets/index-DzXDXra_.js +85 -0
  100. package/dist/web/public/assets/index-DzXDXra_.js.br +0 -0
  101. package/dist/web/public/assets/index-DzXDXra_.js.gz +0 -0
  102. package/dist/web/public/assets/index-eqQLVS8Q.css +2 -0
  103. package/dist/web/public/assets/index-eqQLVS8Q.css.br +0 -0
  104. package/dist/web/public/assets/index-eqQLVS8Q.css.gz +0 -0
  105. package/dist/web/public/assets/runs-Cwy0mN8i.js +1 -0
  106. package/dist/web/public/assets/runs-Cwy0mN8i.js.br +0 -0
  107. package/dist/web/public/assets/runs-Cwy0mN8i.js.gz +0 -0
  108. package/dist/web/public/assets/settings-DzZLmujq.js +5 -0
  109. package/dist/web/public/assets/settings-DzZLmujq.js.br +0 -0
  110. package/dist/web/public/assets/settings-DzZLmujq.js.gz +0 -0
  111. package/dist/web/public/assets/task-runs-BCakxFk8.js +3 -0
  112. package/dist/web/public/assets/task-runs-BCakxFk8.js.br +0 -0
  113. package/dist/web/public/assets/task-runs-BCakxFk8.js.gz +0 -0
  114. package/dist/web/public/assets/tasks-BlzEbk11.js +4 -0
  115. package/dist/web/public/assets/tasks-BlzEbk11.js.br +0 -0
  116. package/dist/web/public/assets/tasks-BlzEbk11.js.gz +0 -0
  117. package/dist/web/public/index.html +100 -130
  118. package/dist/web/public/index.html.br +0 -0
  119. package/dist/web/public/index.html.gz +0 -0
  120. package/dist/web/public/manifest.webmanifest +2 -2
  121. package/dist/web/public/manifest.webmanifest.br +0 -0
  122. package/dist/web/public/manifest.webmanifest.gz +0 -0
  123. package/dist/web/public/sw.js +14 -2
  124. package/dist/web/public/sw.js.br +0 -0
  125. package/dist/web/public/sw.js.gz +0 -0
  126. package/dist/web/push.js +55 -77
  127. package/dist/web/route.js +3 -7
  128. package/dist/web/server.js +131 -180
  129. package/dist/web/session-state.js +14 -54
  130. package/dist/web/types.js +2 -4
  131. package/dist/web/webpush.js +10 -25
  132. package/docs/deploy.md +115 -330
  133. package/package.json +2 -1
  134. package/skills/pier-boards/SKILL.md +81 -160
  135. package/skills/pier-help/SKILL.md +23 -20
  136. package/skills/pier-slack/SKILL.md +2 -2
  137. package/skills/pier-tasks/SKILL.md +153 -160
  138. package/dist/config-sync-fetch.js +0 -84
  139. package/dist/limits.js +0 -14
  140. package/dist/web/public/assets/activity-D3m4L2IL.js.br +0 -0
  141. package/dist/web/public/assets/activity-D3m4L2IL.js.gz +0 -0
  142. package/dist/web/public/assets/boards-BIObcQeX.js +0 -1
  143. package/dist/web/public/assets/boards-BIObcQeX.js.br +0 -0
  144. package/dist/web/public/assets/boards-BIObcQeX.js.gz +0 -0
  145. package/dist/web/public/assets/explorer-C_rSWPNB.js +0 -4
  146. package/dist/web/public/assets/explorer-C_rSWPNB.js.br +0 -0
  147. package/dist/web/public/assets/explorer-C_rSWPNB.js.gz +0 -0
  148. package/dist/web/public/assets/index-CX3fYZY5.css +0 -2
  149. package/dist/web/public/assets/index-CX3fYZY5.css.br +0 -0
  150. package/dist/web/public/assets/index-CX3fYZY5.css.gz +0 -0
  151. package/dist/web/public/assets/index-uFsZkKOQ.js +0 -85
  152. package/dist/web/public/assets/index-uFsZkKOQ.js.br +0 -0
  153. package/dist/web/public/assets/index-uFsZkKOQ.js.gz +0 -0
  154. package/dist/web/public/assets/runs-Ch6DZq6O.js +0 -1
  155. package/dist/web/public/assets/runs-Ch6DZq6O.js.br +0 -0
  156. package/dist/web/public/assets/runs-Ch6DZq6O.js.gz +0 -0
  157. package/dist/web/public/assets/settings-BWcEIEcv.js +0 -5
  158. package/dist/web/public/assets/settings-BWcEIEcv.js.br +0 -0
  159. package/dist/web/public/assets/settings-BWcEIEcv.js.gz +0 -0
  160. package/dist/web/public/assets/task-runs-DPkwv2UE.js +0 -3
  161. package/dist/web/public/assets/task-runs-DPkwv2UE.js.br +0 -0
  162. package/dist/web/public/assets/task-runs-DPkwv2UE.js.gz +0 -0
  163. package/dist/web/public/assets/tasks-DTiCi2mH.js +0 -4
  164. package/dist/web/public/assets/tasks-DTiCi2mH.js.br +0 -0
  165. 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
- # Orchestrating Pier tasks
7
-
8
- ## Core model
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 for injection risks. Output: file:line + issue + fix."}
11
+ {"operation":"run","prompt":"Review src/auth/*.ts. Return file:line, issue, fix."}
36
12
  ```
37
13
 
38
- That is the whole call: a fresh session in **your own directory**. `cwd`
39
- places it elsewhere — absolute, or relative to yours (`"cwd":"../pier.x"` for
40
- a sibling worktree); it must exist. `launch` picks model/thinking, `name`
41
- overrides the label, `input` (any JSON) is appended to the prompt as
42
- `<task_input>`. The `task` draft form is for what the shorthand cannot say —
43
- `timeoutSeconds`, or `reuse` of an existing session:
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":{"name":"long build","timeoutSeconds":3600,"action":{"type":"agent","session":{"mode":"fresh","cwd":"/abs/path"},"prompt":"..."}}}
56
+ {"operation":"run","task":{"timeoutSeconds":7200,"action":{"type":"agent","session":{"mode":"reuse","sessionId":"..."},"prompt":"Check the result"}}}
47
57
  ```
48
58
 
49
- Omit `trigger` in a draft (anything but `manual` errors); a draft's `callback`
50
- is ignored on runs you fire — the result comes to you — and matters only for
51
- `create`d schedules. Returns a run summary (`runId`, `taskId`,
52
- `state:"queued"`, …); `callback:"none"` silences the result,
53
- `callback_session_id` routes it to another existing session. A summary's
54
- `triggerSource` is who fired the run (`agent` / `manual` / `cron` / `watch` /
55
- `task`); a definition's `trigger` is only its schedule, `manual` meaning
56
- on-demand — by a human or by you.
57
-
58
- - `fresh`: clean context in a `cwd` (yours when omitted) — the normal start.
59
- `reuse`: an existing session by id, continuing its history; the run waits
60
- for it to go idle. Nothing copies your context into a child: what the child
61
- needs, you write down.
62
- - **The handoff is the prompt.** A child cannot see your discussion, so state
63
- the goal, the decisions *currently* in force (not the ones argued out of),
64
- late constraints, absolute paths, what "done" means and how to verify it.
65
- Writing it is the check — a decision you cannot state in a sentence was not
66
- settled.
67
- - The child inherits your current model unless `launch.model` is set, and has
68
- the same tools you do, `task` included. An unknown model id fails the *run*,
69
- not the call — the error, with the first available ids, arrives in the
70
- callback; an unknown `launch.thinking` fails the call. `launch` applies to
71
- `fresh` only — a `reuse` session owns its own model and tools.
72
- - Stored roles: `run` with `task_id` (+ optional `input`);
73
- `session_mode:"fresh"` runs a `reuse`-mode role in a fresh session instead.
74
- Archived tasks refuse to run.
75
-
76
- ## Parallel fan-out (core-joined)
77
-
78
- `tasks[]` — 2+ entries, each a prompt string, `{"prompt","cwd"?,"launch"?,
79
- "name"?}`, a full draft, or `{"task_id":"..."}`; never with `task`, `task_id`
80
- or `session_mode` — and Pier joins the group in core; you never track run ids:
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":"run","join":"all","tasks":[
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
- - `join:"all"` (default): one aggregated follow-up when every member is
89
- terminal — each member's name, state, result.
90
- - `join:"first"`: the first member to reach *any* terminal state wins, a
91
- failure included; the rest are cancelled, their sessions resumable (run ids
92
- in the callback).
93
- - Members send no individual callbacks; `callback:"none"` silences the
94
- group's. `get`/`cancel` take `group_id` for the whole group. A member that
95
- cannot enqueue (limit, missing `cwd`) rolls the group back — nothing runs.
96
-
97
- ## Chains
98
-
99
- Run step 1, end your turn, receive its callback, splice the needed part into
100
- step 2's prompt. Children share nothing implicitly — every prompt is
101
- self-contained: paths, acceptance criteria, output format. Branching and
102
- retries are your own logic between turns.
103
-
104
- ## Choosing a model and thinking level
105
-
106
- Omit `launch.model` to inherit your own model — the right default. When the
107
- task profile clearly differs, adjust in this order:
108
-
109
- - **Harder reasoning → raise `launch.thinking` first**, on the inherited
110
- model (`off` … `minimal` `low` `medium` `high` `xhigh` `max`): no model
111
- name needed, usually cheaper than switching models.
112
- - **Cheap bulk work** (listings, extraction, simple checks) → the same
113
- provider's smallest current model, thinking low or off.
114
- - **Cross-vendor second opinion** → another vendor's flagship.
115
-
116
- Never write a model id from memory — catalogs move under you.
117
- `{"operation":"models"}` is the authority: operator-pinned models when set
118
- (`source:"menu"`, each with an intent note and often a usual `thinking`
119
- level), else the curated live catalog (`source:"catalog"`). Prefer a pinned
120
- entry whose note matches the task and start from its thinking level.
121
-
122
- ## Mid-run control
123
-
124
- - `steer` — interrupt a running child with corrected instructions
125
- - `follow_up` — queue a message for after its current turn
126
- - `resume` — continue a finished child in its same session, `message` as the
127
- prompt: a new run (new `runId`, own callback, same depth); expires any
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;
@@ -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};
@@ -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};