@timqi/pier 0.0.28 → 0.1.0
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/dist/agent/config-sync.js +12 -1
- package/dist/agent/config.js +53 -23
- package/dist/agent/events.js +27 -4
- package/dist/agent/listing.js +114 -16
- package/dist/agent/pi.js +158 -16
- package/dist/channels/lark-panel.js +1 -1
- package/dist/core/inbox.js +1 -1
- package/dist/core/router.js +14 -0
- package/dist/core/types.js +13 -1
- package/dist/db.js +48 -0
- package/dist/drain.js +1 -1
- package/dist/extensions/web/content.js +2 -2
- package/dist/main.js +8 -1
- package/dist/secrets.js +1 -1
- package/dist/service.js +2 -2
- package/dist/settings.js +31 -10
- package/dist/tasks/agent.js +109 -73
- package/dist/tasks/callbacks.js +1 -3
- package/dist/tasks/command.js +27 -8
- package/dist/tasks/execution.js +6 -4
- package/dist/tasks/groups.js +35 -26
- package/dist/tasks/messages.js +33 -27
- package/dist/tasks/outbox.js +48 -21
- package/dist/tasks/routes.js +1 -4
- package/dist/tasks/runs.js +10 -4
- package/dist/tasks/service.js +24 -12
- package/dist/tasks/store.js +17 -3
- package/dist/tasks/tool.js +97 -25
- package/dist/tools.js +2 -2
- package/dist/update.js +1 -1
- package/dist/web/instance.js +10 -3
- package/dist/web/providers.js +9 -2
- package/dist/web/public/assets/{activity-DVamP8fs.js → activity-Bl3vZukb.js} +2 -2
- package/dist/web/public/assets/activity-Bl3vZukb.js.br +0 -0
- package/dist/web/public/assets/activity-Bl3vZukb.js.gz +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js +1 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.br +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.gz +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js +4 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js.br +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js.gz +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js +85 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js.br +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js.gz +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css +2 -0
- package/dist/web/public/assets/index-DzmMzvi_.css.br +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css.gz +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js +1 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.br +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.gz +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js +5 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.br +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.gz +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js +3 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.br +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.gz +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js +4 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js.br +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js.gz +0 -0
- package/dist/web/public/index.html +103 -134
- 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/server.js +55 -36
- package/dist/web/session-state.js +54 -33
- package/package.json +2 -1
- package/skills/pier-tasks/SKILL.md +146 -127
- package/dist/web/public/assets/activity-DVamP8fs.js.br +0 -0
- package/dist/web/public/assets/activity-DVamP8fs.js.gz +0 -0
- package/dist/web/public/assets/boards-BKtjPwus.js +0 -1
- package/dist/web/public/assets/boards-BKtjPwus.js.br +0 -0
- package/dist/web/public/assets/boards-BKtjPwus.js.gz +0 -0
- package/dist/web/public/assets/explorer-CjO9NAKQ.js +0 -4
- package/dist/web/public/assets/explorer-CjO9NAKQ.js.br +0 -0
- package/dist/web/public/assets/explorer-CjO9NAKQ.js.gz +0 -0
- package/dist/web/public/assets/index-C23sXyS3.css +0 -2
- package/dist/web/public/assets/index-C23sXyS3.css.br +0 -0
- package/dist/web/public/assets/index-C23sXyS3.css.gz +0 -0
- package/dist/web/public/assets/index-DzWnvhXb.js +0 -85
- package/dist/web/public/assets/index-DzWnvhXb.js.br +0 -0
- package/dist/web/public/assets/index-DzWnvhXb.js.gz +0 -0
- package/dist/web/public/assets/runs-CxIZAx1s.js +0 -1
- package/dist/web/public/assets/runs-CxIZAx1s.js.br +0 -0
- package/dist/web/public/assets/runs-CxIZAx1s.js.gz +0 -0
- package/dist/web/public/assets/settings-B8JWLbpL.js +0 -5
- package/dist/web/public/assets/settings-B8JWLbpL.js.br +0 -0
- package/dist/web/public/assets/settings-B8JWLbpL.js.gz +0 -0
- package/dist/web/public/assets/task-runs-BZ1brkLC.js +0 -3
- package/dist/web/public/assets/task-runs-BZ1brkLC.js.br +0 -0
- package/dist/web/public/assets/task-runs-BZ1brkLC.js.gz +0 -0
- package/dist/web/public/assets/tasks-BoYiBQpq.js +0 -4
- package/dist/web/public/assets/tasks-BoYiBQpq.js.br +0 -0
- package/dist/web/public/assets/tasks-BoYiBQpq.js.gz +0 -0
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// What the workbench decided about a session, and nothing a transcript already
|
|
2
|
-
// knows.
|
|
3
|
-
//
|
|
2
|
+
// knows. A finished turn nobody has looked at (`unread`), and the rail's
|
|
3
|
+
// working set: the rank of a row kept on top (`sort`).
|
|
4
|
+
//
|
|
5
|
+
// The working set replaced pinning and dragging. Ordering the rail by activity
|
|
6
|
+
// made it jump — a scheduled task, a subagent, anyone's Slack message moved the
|
|
7
|
+
// row somebody was about to click — and ordering it by hand asked for the
|
|
8
|
+
// arranging. So: a session enters the set at the front when a human speaks to
|
|
9
|
+
// it and it is not in the set already, and members never move relative to each
|
|
10
|
+
// other until one is pushed out of the last slot. A three-week-old session is
|
|
11
|
+
// back on top the moment it is used; the four you switch between hold still.
|
|
12
|
+
//
|
|
13
|
+
// Remembered rather than derived on purpose: "the last time a human spoke" is
|
|
14
|
+
// there in the transcript and still jumps, because it reorders on every
|
|
15
|
+
// message. Only an order nothing recomputes can hold still.
|
|
4
16
|
//
|
|
5
17
|
// It used to mirror the summary too — cwd, title, created_at, last_active —
|
|
6
18
|
// because listing sessions meant parsing every transcript on disk (~237ms) and
|
|
@@ -13,11 +25,19 @@
|
|
|
13
25
|
// `cwd` and `project_sort` stayed as columns nothing reads: they keyed the
|
|
14
26
|
// per-directory order the rail no longer has (migration 19).
|
|
15
27
|
//
|
|
28
|
+
// The rank does not bring that back: it is written only when a session outside
|
|
29
|
+
// the set is spoken to, not once per turn.
|
|
30
|
+
//
|
|
16
31
|
// One row per session rather than two JSON files: the unread flag is written at
|
|
17
32
|
// the end of every turn, and rewriting a whole file on each of those writes
|
|
18
33
|
// loses the entire set when the process dies mid-write — a truncated file reads
|
|
19
|
-
// back as "
|
|
20
|
-
import { pierDb } from "../db.js";
|
|
34
|
+
// back as "nothing on top", which is indistinguishable from a fresh install.
|
|
35
|
+
import { pierDb, transact } from "../db.js";
|
|
36
|
+
/** How many rows the rail keeps on top. The handful anybody actually switches
|
|
37
|
+
* between, and small enough that every member is somewhere a hand can see: a
|
|
38
|
+
* set as deep as the page (20) would count a row nobody has scrolled to as
|
|
39
|
+
* "already up there" and never bring it back. */
|
|
40
|
+
export const WORKING_SET = 5;
|
|
21
41
|
export class SessionStateStore {
|
|
22
42
|
#db;
|
|
23
43
|
constructor(db = pierDb()) {
|
|
@@ -31,46 +51,47 @@ export class SessionStateStore {
|
|
|
31
51
|
this.#db.prepare(`INSERT INTO session_state(session_id, unread) VALUES (?, ?)
|
|
32
52
|
ON CONFLICT(session_id) DO UPDATE SET unread = excluded.unread`).run(sessionId, unread ? 1 : 0);
|
|
33
53
|
}
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
/** A human spoke to this session. Already in the working set → nothing moves
|
|
55
|
+
* and nothing is written; otherwise it takes the front slot and whatever
|
|
56
|
+
* sat in the last one falls out. Answers whether the order changed, so the
|
|
57
|
+
* caller broadcasts a re-list only when there is something to see. */
|
|
58
|
+
promote(sessionId) {
|
|
59
|
+
// `session_id` breaks a tie the migration can leave behind: several pinned
|
|
60
|
+
// rows nobody ever dragged share rank -1 until the first promotion
|
|
61
|
+
// renumbers them.
|
|
62
|
+
const ranked = this.#db.prepare("SELECT session_id AS id FROM session_state WHERE sort IS NOT NULL ORDER BY sort, session_id").all().map((r) => r.id);
|
|
63
|
+
if (ranked.includes(sessionId))
|
|
64
|
+
return false;
|
|
65
|
+
const kept = [sessionId, ...ranked].slice(0, WORKING_SET);
|
|
66
|
+
const evicted = ranked.filter((id) => !kept.includes(id));
|
|
67
|
+
const rank = this.#db.prepare(`INSERT INTO session_state(session_id, sort) VALUES (?, ?)
|
|
68
|
+
ON CONFLICT(session_id) DO UPDATE SET sort = excluded.sort`);
|
|
69
|
+
const drop = this.#db.prepare("UPDATE session_state SET sort = NULL WHERE session_id = ?");
|
|
70
|
+
// Every rank rewritten rather than the new one prepended: the places are
|
|
71
|
+
// 0..n and a half-renumbered set is an order nobody would recognize.
|
|
72
|
+
transact(this.#db, () => {
|
|
73
|
+
kept.forEach((id, i) => rank.run(id, i));
|
|
74
|
+
for (const id of evicted)
|
|
75
|
+
drop.run(id);
|
|
76
|
+
});
|
|
77
|
+
return true;
|
|
39
78
|
}
|
|
40
|
-
/**
|
|
41
|
-
* place. */
|
|
42
|
-
reorder(sessions) {
|
|
43
|
-
const bySession = this.#db.prepare("UPDATE session_state SET sort = ? WHERE session_id = ?");
|
|
44
|
-
this.#tx(() => sessions.forEach((id, i) => bySession.run(i, id)));
|
|
45
|
-
}
|
|
46
|
-
/** Drop a session's organization row entirely — pin, order, unread. For
|
|
79
|
+
/** Drop a session's organization row entirely — rank and unread. For
|
|
47
80
|
* ghosts: Pi persists a session only once its first assistant reply lands,
|
|
48
81
|
* so a created-and-never-messaged one cannot be resumed, and its remembered
|
|
49
|
-
* row would otherwise sit in the rail 404ing forever
|
|
82
|
+
* row would otherwise sit in the rail 404ing forever — holding a slot in the
|
|
83
|
+
* working set while it does. */
|
|
50
84
|
forget(sessionId) {
|
|
51
85
|
this.#db.prepare("DELETE FROM session_state WHERE session_id = ?").run(sessionId);
|
|
52
86
|
}
|
|
53
|
-
/** All-or-nothing: a half-written order is a list nobody arranged. */
|
|
54
|
-
#tx(run) {
|
|
55
|
-
this.#db.exec("BEGIN");
|
|
56
|
-
try {
|
|
57
|
-
run();
|
|
58
|
-
this.#db.exec("COMMIT");
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
this.#db.exec("ROLLBACK");
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
87
|
/** What this store knows about the sessions it knows anything about, for a
|
|
66
88
|
* caller holding the listing. */
|
|
67
89
|
flags() {
|
|
68
|
-
const rows = this.#db.prepare(`SELECT session_id AS id,
|
|
69
|
-
FROM session_state WHERE
|
|
90
|
+
const rows = this.#db.prepare(`SELECT session_id AS id, unread, sort
|
|
91
|
+
FROM session_state WHERE unread = 1 OR sort IS NOT NULL`).all();
|
|
70
92
|
return new Map(rows.map((r) => [r.id, {
|
|
71
|
-
pinned: r.pinned === 1,
|
|
72
93
|
unread: r.unread === 1,
|
|
73
|
-
...(r.sort === null ? {} : {
|
|
94
|
+
...(r.sort === null ? {} : { rank: r.sort }),
|
|
74
95
|
}]));
|
|
75
96
|
}
|
|
76
97
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timqi/pier",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A self-hosted workspace for coding agents: web workbench and IM channels in front of Pi sessions",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": "github:timqi/pier",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"dompurify": "^3.4.14",
|
|
51
51
|
"highlight.js": "^11.12.0",
|
|
52
52
|
"hono": "^4.13.3",
|
|
53
|
+
"lucide": "1.43.0",
|
|
53
54
|
"marked": "^18.0.10",
|
|
54
55
|
"typebox": "^1.3.7"
|
|
55
56
|
}
|
|
@@ -3,141 +3,160 @@ 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
|
-
#
|
|
6
|
+
# Pier tasks
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Delegate, then wait
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
```json
|
|
11
|
+
{"operation":"run","prompt":"Review src/auth/*.ts. Return file:line, issue, fix."}
|
|
12
|
+
```
|
|
13
|
+
|
|
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
|
+
|
|
29
|
+
Receipts include `runId`, `taskId`, state. Keep IDs: the callback also names
|
|
30
|
+
`Run:`, and there is no lookup by task. `triggerSource` names the actual invoker
|
|
31
|
+
(`agent/manual/cron/watch/task`); the definition's `trigger` is scheduling
|
|
32
|
+
policy (`manual` means on demand).
|
|
33
|
+
|
|
34
|
+
## Sessions and prompts
|
|
35
|
+
|
|
36
|
+
- Default `fresh`: clean context. `cwd` defaults to your directory, accepts an
|
|
37
|
+
absolute path or one relative to yours, and must exist.
|
|
38
|
+
- `reuse`: existing session by `sessionId`; continues its history after it is
|
|
39
|
+
idle. `launch` applies only to fresh sessions; reused sessions own their
|
|
40
|
+
model/tools. Children otherwise have your tools, including `task`.
|
|
41
|
+
- **The prompt is the handoff**: no implicit copy of your discussion. Include
|
|
42
|
+
the goal, current decisions, latest constraints, absolute paths, acceptance
|
|
43
|
+
criteria, verification and output format. `input` accepts any JSON, appended
|
|
44
|
+
in `<task_input>`.
|
|
45
|
+
- For more work on an existing result, `resume` that run with a delta prompt;
|
|
46
|
+
its session/context survives. Start fresh for unrelated work or stale context.
|
|
47
|
+
- Stored role: `run` with `task_id` and optional `input`;
|
|
48
|
+
`session_mode:"fresh"` overrides a stored reuse policy. Archived tasks refuse.
|
|
49
|
+
|
|
50
|
+
Use a `task` draft for `timeoutSeconds` or reuse:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{"operation":"run","task":{"timeoutSeconds":3600,"action":{"type":"agent","session":{"mode":"reuse","sessionId":"..."},"prompt":"Check the result"}}}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Inline drafts may omit `trigger`; only `manual` is allowed. Their nested
|
|
57
|
+
`callback` is ignored: use top-level delivery options above. Nested callbacks
|
|
58
|
+
matter for saved schedules (below).
|
|
59
|
+
|
|
60
|
+
## Groups and chains
|
|
61
|
+
|
|
62
|
+
`tasks[]` needs 2+ entries: prompt strings, `{prompt,cwd?,launch?,name?}`, full
|
|
63
|
+
drafts, or `{task_id}`. Do not combine it with `task`, `task_id` or `session_mode`.
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{"operation":"run","tasks":["Review correctness","Review test gaps"],"join":"all"}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `join:"all"` (default): one callback with every member's name, state and result
|
|
70
|
+
when all are terminal.
|
|
71
|
+
- `join:"first"`: first **any terminal state**, including failure/overlap skip,
|
|
72
|
+
wins. Others are cancelled; their sessions remain resumable, IDs in callback.
|
|
73
|
+
- Core owns the join; never hand-aggregate member IDs across turns. Members have
|
|
74
|
+
no individual callbacks; `none`/`steer` applies to the group. `cancel` and
|
|
75
|
+
`recover` accept `group_id`. Admission failure (e.g. limit or missing cwd)
|
|
76
|
+
rolls back the group: nothing runs.
|
|
77
|
+
|
|
78
|
+
For a chain: launch step 1 → end turn → receive callback → put needed results
|
|
79
|
+
in step 2's self-contained prompt. Branching and retries are your responsibility.
|
|
80
|
+
|
|
81
|
+
## Model choice
|
|
82
|
+
|
|
83
|
+
Default: inherit your current model. For harder reasoning, raise
|
|
84
|
+
`launch.thinking` first (`off/minimal/low/medium/high/xhigh/max`). For cheap bulk
|
|
85
|
+
work, choose the same provider's smallest current model with low/off thinking;
|
|
86
|
+
for an independent vendor's opinion, choose its flagship.
|
|
87
|
+
|
|
88
|
+
Before naming a model, call `{"operation":"models"}`; never use IDs from memory.
|
|
89
|
+
It returns operator pins (`source:"menu"`, intent notes/usual thinking) or the
|
|
90
|
+
live catalog (`source:"catalog"`). Prefer a matching pin and its thinking level.
|
|
91
|
+
`launch.model` overrides inheritance. Unknown model IDs fail the run and report
|
|
92
|
+
available IDs by callback; invalid `launch.thinking` fails the call.
|
|
20
93
|
|
|
21
|
-
##
|
|
94
|
+
## Control and decisions
|
|
95
|
+
|
|
96
|
+
`message` must be non-empty, <16 KiB. Controls require ownership: your delegated
|
|
97
|
+
trees, or only descendants when you are a subagent.
|
|
98
|
+
|
|
99
|
+
- `steer`: interrupt a child with corrections.
|
|
100
|
+
- `follow_up`: queue guidance after its current turn.
|
|
101
|
+
- `resume`: terminal run only; same session, new run ID/callback, same depth,
|
|
102
|
+
message as prompt. Expires its unanswered decision.
|
|
103
|
+
- `cancel`: run or group; cascades to descendants, terminal runs unchanged.
|
|
104
|
+
|
|
105
|
+
`steer`/`follow_up` require a non-terminal run; undelivered guidance expires when
|
|
106
|
+
it ends. For finished work use `resume`.
|
|
107
|
+
|
|
108
|
+
Child → supervisor:
|
|
22
109
|
|
|
23
110
|
```json
|
|
24
|
-
{"operation":"
|
|
111
|
+
{"operation":"contact","reason":"decision","message":"Use API A or B?"}
|
|
25
112
|
```
|
|
26
113
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Returns a run summary (`runId`, `state:"queued"`, ids). Finish your turn; the
|
|
36
|
-
result arrives as a follow-up. `callback:"none"` silences it.
|
|
37
|
-
|
|
38
|
-
A summary's `triggerSource` is who fired that run (`agent` when you did, plus
|
|
39
|
-
`manual` / `cron` / `watch` / `task`); a definition's `trigger` is only its
|
|
40
|
-
schedule policy, where `manual` means on-demand — by a human or by you.
|
|
41
|
-
|
|
42
|
-
- `fresh`: clean context in a `cwd` (yours when omitted) — how a child
|
|
43
|
-
normally starts.
|
|
44
|
-
`reuse`: sends work to an existing session by id, continuing its history.
|
|
45
|
-
There is no way to copy your context into a child: what the child needs, you
|
|
46
|
-
write down.
|
|
47
|
-
- **The handoff is the prompt.** A child cannot see your discussion, so state:
|
|
48
|
-
the goal; the decisions *currently* in force (not the ones you argued out of);
|
|
49
|
-
the constraints the user added late; absolute paths; what "done" means and how
|
|
50
|
-
to verify it. Writing it is also the check — a decision you cannot state in a
|
|
51
|
-
sentence was not settled.
|
|
52
|
-
- The child inherits your current model unless `launch.model` is set.
|
|
53
|
-
Unknown models fail with the available list in the error. `launch` applies to
|
|
54
|
-
`fresh` only — a `reuse` session owns its own model and tools.
|
|
55
|
-
- The child has the same tools you do.
|
|
56
|
-
- Runs time out after 900s by default; set `timeoutSeconds` in the draft for
|
|
57
|
-
longer jobs.
|
|
58
|
-
|
|
59
|
-
## Parallel fan-out (core-joined)
|
|
60
|
-
|
|
61
|
-
Pass `tasks[]` (each entry a prompt string, `{"prompt","cwd"?}`, a full
|
|
62
|
-
draft, or `{"task_id":"..."}`) and Pier joins the group in core — you never
|
|
63
|
-
track run ids across turns:
|
|
114
|
+
Requires an active delegated Agent run with a supervisor (scheduled cron/watch
|
|
115
|
+
runs have none). `progress` is fire-and-forget; `decision` steers the supervisor
|
|
116
|
+
and permits one open question per run. State what you await and **end your
|
|
117
|
+
turn**, never spin/poll. A finished run with an open question suppresses its
|
|
118
|
+
completion callback: the question is the notification.
|
|
119
|
+
|
|
120
|
+
Supervisor → child:
|
|
64
121
|
|
|
65
122
|
```json
|
|
66
|
-
{"operation":"
|
|
67
|
-
"Review src/ for correctness bugs. Output: file:line + issue.",
|
|
68
|
-
{"prompt":"Review the tests for gaps. Output: missing case + file.","cwd":"packages/web"}]}
|
|
123
|
+
{"operation":"reply","message_id":"...","message":"Use API A"}
|
|
69
124
|
```
|
|
70
125
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- `steer` — interrupt a running child with corrected instructions
|
|
110
|
-
- `follow_up` — queue a message for after its current turn
|
|
111
|
-
- `resume` — continue a finished child in its same session; this expires any
|
|
112
|
-
unanswered decision on that run
|
|
113
|
-
- `cancel` — stop a run (or a whole group via `group_id`); cancellation
|
|
114
|
-
cascades to the run's own children
|
|
115
|
-
|
|
116
|
-
## Supervisor decisions (as a child)
|
|
117
|
-
|
|
118
|
-
`{"operation":"contact","reason":"decision","message":"..."}` returns a
|
|
119
|
-
receipt immediately. State what you are waiting for and **end your turn** —
|
|
120
|
-
never spin or poll. The answer is steered into your current turn if your run is
|
|
121
|
-
still active; if your run already finished, Pier resumes your session with the
|
|
122
|
-
reply as the prompt. One open decision per run.
|
|
123
|
-
|
|
124
|
-
As a parent: the question arrives as a system input with a message id, steered
|
|
125
|
-
into your current turn at the next step boundary so a blocked child is not
|
|
126
|
-
waiting on the end of your turn; answer
|
|
127
|
-
with `{"operation":"reply","message_id":"...","message":"..."}`. A run that
|
|
128
|
-
finished while awaiting your answer shows `pendingDecisionId` in `get` and
|
|
129
|
-
sends no completion callback — the question is the notification.
|
|
130
|
-
|
|
131
|
-
`reason:"progress"` is fire-and-forget status for the parent.
|
|
132
|
-
|
|
133
|
-
## Rules
|
|
134
|
-
|
|
135
|
-
- One concern per child. For fan-out, let core join via `tasks[]` — never
|
|
136
|
-
hand-aggregate run ids across turns.
|
|
137
|
-
- `create` is for definitions that outlive one job: a schedule (`cron` /
|
|
138
|
-
`watch`), or a role you will run by `task_id` again and again. It files a
|
|
139
|
-
task the operator sees and has to archive by hand — never use it for a
|
|
140
|
-
one-off; that is `run` with a prompt.
|
|
141
|
-
- Inline drafts must use `action.type:"agent"`; triggers are forced manual;
|
|
142
|
-
do not target another session with `reuse` from an inline draft.
|
|
143
|
-
- After launching work, end your turn. The callback starts your next one.
|
|
126
|
+
Only the addressed supervisor may reply. Same text twice is a no-op; different
|
|
127
|
+
text errors. The answer steers an active child or auto-resumes a terminal one
|
|
128
|
+
with the reply as prompt; the continuation's callback returns to the replier.
|
|
129
|
+
|
|
130
|
+
## Recover lost information only
|
|
131
|
+
|
|
132
|
+
`recover` requires `run_id` or `group_id` plus `reason` (e.g. text truncated,
|
|
133
|
+
context lost to compaction). **Never use it to check progress.**
|
|
134
|
+
|
|
135
|
+
- Run must be terminal and its callback settled: delivered, abandoned, or none.
|
|
136
|
+
Group members wait for their group callback; a terminal race winner is
|
|
137
|
+
readable while losers are still cancelling.
|
|
138
|
+
- Whole-group recovery additionally requires every member terminal. An open
|
|
139
|
+
decision directs you to `reply`. Other premature requests get a refusal
|
|
140
|
+
that reveals no state.
|
|
141
|
+
- Callbacks truncate each result at 8000 characters; recovered groups truncate
|
|
142
|
+
member results at 2000. Notes point to `recover run_id`, which returns full
|
|
143
|
+
text. `none` remains a no-result preference, not a polling workflow.
|
|
144
|
+
|
|
145
|
+
## Persistence and limits
|
|
146
|
+
|
|
147
|
+
`create` files an operator-visible definition only for recurring roles or
|
|
148
|
+
cron/watch schedules; the operator must archive it. `update` takes the entire
|
|
149
|
+
draft, including `trigger`. Schedules notify only with nested
|
|
150
|
+
`callback:{"type":"session","sessionId":"..."}`; `origin` needs an invoker,
|
|
151
|
+
which scheduled runs lack.
|
|
152
|
+
|
|
153
|
+
- Inside a run: Agent actions only (stored/inline), no reuse or create/update.
|
|
154
|
+
- Depth 0–2: at most 3 runs below the invoking session; a fourth errors. Each
|
|
155
|
+
root allows 16 descendant runs (depth ≥1, resumes included). Your direct
|
|
156
|
+
children are separate roots and do not count toward that limit.
|
|
157
|
+
- 4 Agent runs execute instance-wide; others queue without error. Timeout
|
|
158
|
+
starts **at enqueue**, so a run can time out before starting.
|
|
159
|
+
- Default timeout 900s; `timeoutSeconds:1–86400` in draft form only. Timeout
|
|
160
|
+
reports `failed / task timed out`.
|
|
161
|
+
- Restart marks queued/running runs `interrupted`; callbacks still apply.
|
|
162
|
+
During drain, new roots are refused: retry after restart.
|
|
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-DzWnvhXb.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-DzWnvhXb.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
|