@timqi/pier 0.0.27 → 0.0.29
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 +39 -8
- package/dist/agent/config.js +72 -15
- package/dist/agent/events.js +24 -4
- package/dist/core/identity.js +17 -1
- package/dist/core/router.js +10 -0
- package/dist/db.js +27 -0
- package/dist/main.js +1 -0
- package/dist/settings.js +4 -1
- package/dist/tasks/service.js +4 -0
- package/dist/tasks/store.js +12 -0
- package/dist/tools.js +17 -3
- package/dist/web/public/assets/{activity-B44nbhXy.js → activity-D3m4L2IL.js} +2 -2
- package/dist/web/public/assets/activity-D3m4L2IL.js.br +0 -0
- package/dist/web/public/assets/activity-D3m4L2IL.js.gz +0 -0
- package/dist/web/public/assets/boards-BIObcQeX.js +1 -0
- package/dist/web/public/assets/boards-BIObcQeX.js.br +0 -0
- package/dist/web/public/assets/boards-BIObcQeX.js.gz +0 -0
- package/dist/web/public/assets/explorer-C_rSWPNB.js +4 -0
- package/dist/web/public/assets/explorer-C_rSWPNB.js.br +0 -0
- package/dist/web/public/assets/explorer-C_rSWPNB.js.gz +0 -0
- package/dist/web/public/assets/index-CX3fYZY5.css +2 -0
- package/dist/web/public/assets/index-CX3fYZY5.css.br +0 -0
- package/dist/web/public/assets/index-CX3fYZY5.css.gz +0 -0
- package/dist/web/public/assets/index-uFsZkKOQ.js +85 -0
- package/dist/web/public/assets/index-uFsZkKOQ.js.br +0 -0
- package/dist/web/public/assets/index-uFsZkKOQ.js.gz +0 -0
- package/dist/web/public/assets/runs-Ch6DZq6O.js +1 -0
- package/dist/web/public/assets/runs-Ch6DZq6O.js.br +0 -0
- package/dist/web/public/assets/runs-Ch6DZq6O.js.gz +0 -0
- package/dist/web/public/assets/settings-BWcEIEcv.js +5 -0
- package/dist/web/public/assets/settings-BWcEIEcv.js.br +0 -0
- package/dist/web/public/assets/settings-BWcEIEcv.js.gz +0 -0
- package/dist/web/public/assets/{task-runs-BW06ZEVz.js → task-runs-DPkwv2UE.js} +2 -2
- package/dist/web/public/assets/task-runs-DPkwv2UE.js.br +0 -0
- package/dist/web/public/assets/task-runs-DPkwv2UE.js.gz +0 -0
- package/dist/web/public/assets/tasks-DTiCi2mH.js +4 -0
- package/dist/web/public/assets/tasks-DTiCi2mH.js.br +0 -0
- package/dist/web/public/assets/tasks-DTiCi2mH.js.gz +0 -0
- package/dist/web/public/index.html +41 -27
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/server.js +30 -76
- package/dist/web/session-state.js +55 -52
- package/package.json +1 -1
- package/skills/pier-tasks/SKILL.md +113 -79
- package/dist/web/public/assets/activity-B44nbhXy.js.br +0 -0
- package/dist/web/public/assets/activity-B44nbhXy.js.gz +0 -0
- package/dist/web/public/assets/boards-CeiKKBaY.js +0 -1
- package/dist/web/public/assets/boards-CeiKKBaY.js.br +0 -0
- package/dist/web/public/assets/boards-CeiKKBaY.js.gz +0 -0
- package/dist/web/public/assets/code-I0lxXuvE.js +0 -1
- package/dist/web/public/assets/explorer-BOeDogsY.js +0 -5
- package/dist/web/public/assets/explorer-BOeDogsY.js.br +0 -0
- package/dist/web/public/assets/explorer-BOeDogsY.js.gz +0 -0
- package/dist/web/public/assets/index-BmKvoulH.js +0 -84
- package/dist/web/public/assets/index-BmKvoulH.js.br +0 -0
- package/dist/web/public/assets/index-BmKvoulH.js.gz +0 -0
- package/dist/web/public/assets/index-CZeL3DfU.css +0 -2
- package/dist/web/public/assets/index-CZeL3DfU.css.br +0 -0
- package/dist/web/public/assets/index-CZeL3DfU.css.gz +0 -0
- package/dist/web/public/assets/runs-C4OlQT7W.js +0 -1
- package/dist/web/public/assets/runs-C4OlQT7W.js.br +0 -0
- package/dist/web/public/assets/runs-C4OlQT7W.js.gz +0 -0
- package/dist/web/public/assets/settings-Cn8i_QiP.js +0 -6
- package/dist/web/public/assets/settings-Cn8i_QiP.js.br +0 -0
- package/dist/web/public/assets/settings-Cn8i_QiP.js.gz +0 -0
- package/dist/web/public/assets/task-runs-BW06ZEVz.js.br +0 -0
- package/dist/web/public/assets/task-runs-BW06ZEVz.js.gz +0 -0
- package/dist/web/public/assets/tasks-DZzYlKsd.js +0 -4
- package/dist/web/public/assets/tasks-DZzYlKsd.js.br +0 -0
- package/dist/web/public/assets/tasks-DZzYlKsd.js.gz +0 -0
- package/dist/web/repos.js +0 -75
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
// What the workbench decided about a session, and nothing a transcript already
|
|
2
|
-
// knows.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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.
|
|
5
16
|
//
|
|
6
17
|
// It used to mirror the summary too — cwd, title, created_at, last_active —
|
|
7
18
|
// because listing sessions meant parsing every transcript on disk (~237ms) and
|
|
@@ -11,16 +22,22 @@
|
|
|
11
22
|
// every turn, a title write on the first prompt, a second write on rename.
|
|
12
23
|
// Deriving beats syncing; all of it is gone.
|
|
13
24
|
//
|
|
14
|
-
// `cwd` stayed
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
25
|
+
// `cwd` and `project_sort` stayed as columns nothing reads: they keyed the
|
|
26
|
+
// per-directory order the rail no longer has (migration 19).
|
|
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.
|
|
18
30
|
//
|
|
19
31
|
// One row per session rather than two JSON files: the unread flag is written at
|
|
20
32
|
// the end of every turn, and rewriting a whole file on each of those writes
|
|
21
33
|
// loses the entire set when the process dies mid-write — a truncated file reads
|
|
22
|
-
// back as "
|
|
23
|
-
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. Larger than the handful anybody
|
|
37
|
+
* switches between (3–6), and small enough that every member is somewhere a
|
|
38
|
+
* hand can see: a set as deep as the page (20) would count a row nobody has
|
|
39
|
+
* scrolled to as "already up there" and never bring it back. */
|
|
40
|
+
export const WORKING_SET = 8;
|
|
24
41
|
export class SessionStateStore {
|
|
25
42
|
#db;
|
|
26
43
|
constructor(db = pierDb()) {
|
|
@@ -34,61 +51,47 @@ export class SessionStateStore {
|
|
|
34
51
|
this.#db.prepare(`INSERT INTO session_state(session_id, unread) VALUES (?, ?)
|
|
35
52
|
ON CONFLICT(session_id) DO UPDATE SET unread = excluded.unread`).run(sessionId, unread ? 1 : 0);
|
|
36
53
|
}
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
* the
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
this.#db.prepare(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
order.sessions?.forEach((id, i) => bySession.run(i, id));
|
|
60
|
-
order.projects?.forEach((cwd, i) => byCwd.run(i, cwd));
|
|
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);
|
|
61
76
|
});
|
|
77
|
+
return true;
|
|
62
78
|
}
|
|
63
|
-
/** Drop a session's organization row entirely —
|
|
79
|
+
/** Drop a session's organization row entirely — rank and unread. For
|
|
64
80
|
* ghosts: Pi persists a session only once its first assistant reply lands,
|
|
65
81
|
* so a created-and-never-messaged one cannot be resumed, and its remembered
|
|
66
|
-
* 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. */
|
|
67
84
|
forget(sessionId) {
|
|
68
85
|
this.#db.prepare("DELETE FROM session_state WHERE session_id = ?").run(sessionId);
|
|
69
86
|
}
|
|
70
|
-
/** All-or-nothing: a half-written order is a list nobody arranged. */
|
|
71
|
-
#tx(run) {
|
|
72
|
-
this.#db.exec("BEGIN");
|
|
73
|
-
try {
|
|
74
|
-
run();
|
|
75
|
-
this.#db.exec("COMMIT");
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
this.#db.exec("ROLLBACK");
|
|
79
|
-
throw err;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
87
|
/** What this store knows about the sessions it knows anything about, for a
|
|
83
88
|
* caller holding the listing. */
|
|
84
89
|
flags() {
|
|
85
|
-
const rows = this.#db.prepare(`SELECT session_id AS id,
|
|
86
|
-
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();
|
|
87
92
|
return new Map(rows.map((r) => [r.id, {
|
|
88
|
-
pinned: r.pinned === 1,
|
|
89
93
|
unread: r.unread === 1,
|
|
90
|
-
...(r.sort === null ? {} : {
|
|
91
|
-
...(r.projectSort === null ? {} : { projectSort: r.projectSort }),
|
|
94
|
+
...(r.sort === null ? {} : { rank: r.sort }),
|
|
92
95
|
}]));
|
|
93
96
|
}
|
|
94
97
|
}
|
package/package.json
CHANGED
|
@@ -9,14 +9,25 @@ description: Delegate work to Pier subagents with the task tool — one-shot, pa
|
|
|
9
9
|
|
|
10
10
|
- Every run executes a persisted task definition. A `run` without `task_id`
|
|
11
11
|
— a bare `prompt`, or a full `task` draft — creates and runs a one-shot
|
|
12
|
-
subagent atomically
|
|
13
|
-
|
|
12
|
+
subagent atomically: `kind:"subagent"`, hidden from `list`, named by the
|
|
13
|
+
prompt's first line. You never file it.
|
|
14
14
|
- **Nothing blocks.** Every operation returns immediately; results, group
|
|
15
|
-
joins
|
|
16
|
-
|
|
17
|
-
- The child session survives the run — `resume` continues it with
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
20
31
|
|
|
21
32
|
## Single delegation (the common case)
|
|
22
33
|
|
|
@@ -24,43 +35,49 @@ description: Delegate work to Pier subagents with the task tool — one-shot, pa
|
|
|
24
35
|
{"operation":"run","prompt":"Review src/auth/*.ts for injection risks. Output: file:line + issue + fix."}
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
That is the whole call: a fresh session in **your own directory**.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`
|
|
31
|
-
`task` draft form
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{"operation":"run","task":{"name":"long build","timeoutSeconds":3600,"action":{"type":"agent","session":{"mode":"fresh","cwd":"/abs/path"},"prompt":"..."}}}
|
|
47
|
+
```
|
|
48
|
+
|
|
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
|
|
54
71
|
`fresh` only — a `reuse` session owns its own model and tools.
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
58
75
|
|
|
59
76
|
## Parallel fan-out (core-joined)
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
draft, or `{"task_id":"..."}
|
|
63
|
-
track run ids
|
|
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:
|
|
64
81
|
|
|
65
82
|
```json
|
|
66
83
|
{"operation":"run","join":"all","tasks":[
|
|
@@ -68,21 +85,21 @@ track run ids across turns:
|
|
|
68
85
|
{"prompt":"Review the tests for gaps. Output: missing case + file.","cwd":"packages/web"}]}
|
|
69
86
|
```
|
|
70
87
|
|
|
71
|
-
- `join:"all"` (default): one aggregated follow-up when every member
|
|
72
|
-
|
|
73
|
-
- `join:"first"`: the first
|
|
74
|
-
their sessions
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
79
96
|
|
|
80
97
|
## Chains
|
|
81
98
|
|
|
82
99
|
Run step 1, end your turn, receive its callback, splice the needed part into
|
|
83
|
-
step 2's prompt. Children share nothing implicitly — every prompt
|
|
84
|
-
self-contained: paths, acceptance criteria,
|
|
85
|
-
turns.
|
|
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.
|
|
86
103
|
|
|
87
104
|
## Choosing a model and thinking level
|
|
88
105
|
|
|
@@ -90,54 +107,71 @@ Omit `launch.model` to inherit your own model — the right default. When the
|
|
|
90
107
|
task profile clearly differs, adjust in this order:
|
|
91
108
|
|
|
92
109
|
- **Harder reasoning → raise `launch.thinking` first**, on the inherited
|
|
93
|
-
model (`off` … `minimal` `low` `medium` `high` `xhigh` `max`)
|
|
94
|
-
|
|
110
|
+
model (`off` … `minimal` `low` `medium` `high` `xhigh` `max`): no model
|
|
111
|
+
name needed, usually cheaper than switching models.
|
|
95
112
|
- **Cheap bulk work** (listings, extraction, simple checks) → the same
|
|
96
|
-
provider's smallest current model,
|
|
113
|
+
provider's smallest current model, thinking low or off.
|
|
97
114
|
- **Cross-vendor second opinion** → another vendor's flagship.
|
|
98
115
|
|
|
99
116
|
Never write a model id from memory — catalogs move under you.
|
|
100
|
-
`{"operation":"models"}` is the authority:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
start from its thinking level. A wrong id still fails with the available
|
|
105
|
-
list in the error, so a stale guess costs one call.
|
|
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.
|
|
106
121
|
|
|
107
122
|
## Mid-run control
|
|
108
123
|
|
|
109
124
|
- `steer` — interrupt a running child with corrected instructions
|
|
110
125
|
- `follow_up` — queue a message for after its current turn
|
|
111
|
-
- `resume` — continue a finished child in its same session
|
|
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
|
|
112
128
|
unanswered decision on that run
|
|
113
|
-
- `cancel` — stop a run
|
|
114
|
-
|
|
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.
|
|
115
136
|
|
|
116
137
|
## Supervisor decisions (as a child)
|
|
117
138
|
|
|
118
139
|
`{"operation":"contact","reason":"decision","message":"..."}` returns a
|
|
119
|
-
receipt immediately. State what you
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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.
|
|
123
146
|
|
|
124
147
|
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
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
finished while awaiting your answer
|
|
129
|
-
sends no completion callback — the
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
132
155
|
|
|
133
156
|
## Rules
|
|
134
157
|
|
|
135
158
|
- One concern per child. For fan-out, let core join via `tasks[]` — never
|
|
136
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.
|
|
137
168
|
- `create` is for definitions that outlive one job: a schedule (`cron` /
|
|
138
|
-
`watch`), or a role
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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`.
|
|
143
177
|
- After launching work, end your turn. The callback starts your next one.
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{G as e,H as t,L as n,N as r,O as i,P as a,R as o,V as s,W as c,h as l}from"./index-BmKvoulH.js";var u=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function d(d,f){let p=[],m=``,h=n(`header`,`sticky top-0 z-30 flex h-10 items-center gap-3 border-b border-neutral-200 bg-white px-4`,n(`span`,`font-medium max-md:hidden`,`Boards`)),g=n(`div`,`px-4 py-5`);d.append(n(`div`,`min-h-0 flex-1 overflow-y-auto`,h,g));async function _(t,n){let r=await e(`/api/boards/${t.slug}`,{public:n},`PATCH`);if(r.ok)return t.token=(await r.json().catch(()=>({}))).token??``,x();m=await s(r,`Could not change ${t.slug}`),await S()}async function v(e){m=await c(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await S()}function y(e){let t=n(`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(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let s=n(`div`,`flex items-center gap-2`,r,n(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,e.slug)),c=i(``,``,e.public,t=>{e.public=t,_(e,t)});c.className=`ml-auto flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(n(`span`,``,`Public`));let d=l(`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(e.slug);let p=a(`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(e)}`);p.title=`Copy the board's link`,s.append(c,p,d),t.append(s);let m=n(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);e.description&&m.append(n(`span`,`min-w-0 truncate`,e.description)),m.append(n(`span`,`flex-none text-neutral-400`,o(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=l(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>f(t),m.append(e)}if(e.public&&e.token){let t=n(`span`,`flex-none font-mono text-neutral-400`,u(e));t.title=`Public URL — anyone holding this link can read the board, no password`,m.append(t)}return t.append(m),t}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=n(`div`,`mx-auto flex max-w-3xl flex-col`);p.length===0&&e.append(n(`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:r,wanted:i}of b){let a=p.filter(e=>e.public===i);if(a.length===0)continue;let o=n(`section`,`mb-6`);o.append(n(`h2`,`text-[13px] font-semibold text-neutral-700`,t)),o.append(n(`p`,`mb-1 text-[11.5px] leading-snug text-neutral-500`,r));let s=n(`div`,`rounded-xl border border-neutral-200 bg-white px-3`);s.append(...a.map(y)),o.append(s),e.append(o)}g.replaceChildren(...m?[n(`p`,`mb-3 text-[13px] text-red-600`,m)]:[],e)}async function S(){let e=await t(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=r(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{L as e,d as t}from"./index-BmKvoulH.js";var n=2e4,r=e=>e.map((e,t)=>({nums:[t+1],text:e,tone:``}));function i(t,r){let i=t.length<=n?r:null,o=Math.max(2,...t.map(e=>e.nums.reduce((e,t)=>Math.max(e,String(t).length),0))),s=e(`div`,`py-2 font-mono text-[12px] leading-[1.5]`);for(let n of t){let t=e(`div`,`flex pl-2 pr-4 ${n.tone===`add`?`bg-emerald-50`:n.tone===`del`?`bg-red-50`:``}`);for(let r of n.nums){let n=e(`span`,`flex-none select-none pr-1.5 text-right text-neutral-300`,r===``?``:String(r));n.style.width=`calc(${o}ch + 0.375rem)`,t.append(n)}t.append(a(n,i)),s.append(t)}return s}function a(n,r){let i=`min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]`;if(!n.mark||n.mark[0]>=n.mark[1]){let e=t(n.text,r);return e.className=i,e}let[a,o]=n.mark,s=t(n.text.slice(a,o),r);return s.className=`rounded-xs ${n.tone===`add`?`bg-emerald-200/80`:`bg-red-200/70`}`,e(`span`,i,t(n.text.slice(0,a),r),s,t(n.text.slice(o),r))}export{r as n,i as t};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{F as e,L as t,M as n,N as r,R as i,U as a,a as o,i as s,n as c,s as l,u}from"./index-BmKvoulH.js";import{n as d,t as f}from"./code-I0lxXuvE.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 _(e,n){e.onmouseenter=()=>{g();let r=n();if(!r)return;h=t(`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=r,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 v(e,n,r){let a=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,t(`span`,`w-3 flex-none text-indigo-600`,e.value===n?`✓`:``),t(`span`,`flex-none font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&a.append(t(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&a.append(t(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?i(e.at):``}`)),_(a,()=>m(e)),a.onclick=()=>r(e.value),a}function ee(e,n,r,i,a){let{base:c,head:l}=r,u=()=>l&&c===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=n.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...n.refs.map(e=>({value:e.name,subject:e.subject})),...n.commits.map(e=>({...e,value:e.hash}))],m=t(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,n)=>{let r=t(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===n?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return r.onclick=()=>{d=n,h()},r},r=(e,n,r,i)=>t(`div`,`flex min-w-0 flex-1 flex-col`,t(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),t(`div`,`min-h-0 flex-1 overflow-y-auto`,...n.map(e=>v(e,r,i)))),o=d===`compare`?t(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,r(`base`,p(!1),c,e=>{c=e,h(),i(c,l)}),r(`to`,p(!0),l,e=>{l=e,h(),i(c,l)})):t(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...n.commits.map(e=>v({value:e.hash,...e},u(),e=>{c=`${e}~1`,l=e,s(),i(c,l)}))),f=t(`div`,`flex-none border-t border-neutral-200`,v({value:a,label:`↺ ${a} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},c===a&&l===``?a:``,()=>{c=a,l=``,h(),i(c,l)}));m.replaceChildren(t(`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`)),o,...d===`compare`?[f]:[])}h(),o(e,m)}var te=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,ne={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)}},C=(e,n=`indigo`)=>{let r=t(`button`,`min-w-0 cursor-pointer truncate rounded-md px-2 py-0.5 text-left font-mono text-[11.5px] ${n===`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 r.type=`button`,r};function w(i,o,s,p){let h=``,g=``,v={branch:null,refs:[],commits:[],worktrees:[]},y=`HEAD`,b=``,x=new Map,w=!0,T=!1,ae=[],E=new Set,D=null,O=null,k=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),A=t(`div`,`flex-none border-b border-neutral-200`),j=t(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),oe=t(`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=t(`div`,`h-full min-w-0 overflow-auto`),N=t(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),se=t(`section`,`relative min-h-0 min-w-0 flex-1`,M,N);i.append(k,t(`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,n=`text-neutral-400`)=>t(`p`,`px-4 py-3 text-[12.5px] ${n}`,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 w&&v.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 a(F(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...R(t,n)),e.childElementCount||e.append(I(w?`No changes.`:`Empty.`))}catch(n){let r=R(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(I(String(n),`text-red-600`))}}function ce(n,r){let i=L(n),a=[t(`span`,`truncate`,r)];i&&a.push(t(`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=t(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(I(`…`)),z(c,n))};return o.ontoggle=()=>{E[o.open?`add`:`delete`](n),o.open&&u()},(w&&v.branch&&!T||E.has(n))&&(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,n){let r=x.get(e)?.status,i=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,t(`span`,`truncate`,n));return r&&i.append(t(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${ne[r]??`text-neutral-500`}`,r)),i.title=e,e===D&&B(i),i.onclick=()=>{B(i),D=e,de(e)},i}let V=()=>void z(j,``),H=(e,n)=>[...e?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...n?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${n}`)]:[]];function U(e,r,i){let a=x.get(e),o=t(`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`,t(`span`,`truncate`,e));a&&(o.append(t(`span`,`flex-none font-semibold ${ne[a.status]??``}`,a.status)),o.append(...H(a.add,a.del)));let s=t(`div`,`ml-auto flex flex-none items-center gap-2`);if(i&&s.append(i),r){let t=document.createElement(`a`);t.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,t.href=F(`file`,{path:e}),t.download=n(e),t.textContent=`Download`,s.append(t)}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=_e();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)?ve(e,t):J(e,t)}async function J(e,n){if(!q(n))return;G();let r=F(`file`,{path:e}),i=t(`div`,`min-w-0`);if(M.classList.remove(`flex`,`flex-col`),M.replaceChildren(U(e,!0),i),te.test(e)){let e=t(`img`,`max-w-full p-4`);e.src=r,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=r,M.replaceChildren(U(e,!0),t),M.classList.add(`flex`,`flex-col`);return}i.append(I(`…`));let a=await fetch(r);if(!q(n))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 o=(await a.text()).split(`
|
|
4
|
-
`);if(!q(n))return;o.at(-1)===``&&o.pop();let s=await u(e);q(n)&&i.replaceChildren(f(d(o),s))}function fe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
5
|
-
`);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 pe(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 me={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function he(e,n,r){N.replaceChildren(...e.map(e=>{let i=t(`button`,`pointer-events-auto absolute w-full cursor-pointer ${me[e.tone]}`);return i.style.top=`${e.start/n*100}%`,i.style.height=`${Math.max(.4,(e.end-e.start+1)/n*100)}%`,i.title=`line ${ge(r,e.start)}`,i.onclick=()=>r[e.start].scrollIntoView({block:`center`}),i})),N.classList.remove(`hidden`)}let ge=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function _e(){let e=[],n=[],r=-1,a=t(`span`,`flex-none text-[10.5px] text-neutral-400`),o=t=>{e.length&&(r=(r+t+e.length)%e.length,n[e[r].start].scrollIntoView({block:`center`}),a.textContent=`${r+1}/${e.length}`)},s=()=>e.length>0&&!i.classList.contains(`hidden`),c=(e,n,r,i)=>{let a=t(`button`,`icon-btn flex-none`,e);return a.onclick=()=>o(n),l(a,i,r,()=>o(n),s),a};return{el:t(`div`,`flex flex-none items-center gap-1`,a,c(`↑`,-1,`Previous change`,[`p`,`k`]),c(`↓`,1,`Next change`,[`n`,`j`])),set(t,i){e=t,n=i,r=-1,a.textContent=`${t.length} change${t.length===1?``:`s`}`}}}async function ve(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 a(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 J(e,t);let i=fe(r);ue(i);let o=await u(e);if(!q(t))return;let s=f(i,o),c=[...s.children],l=pe(i);W.set(l,c),M.replaceChildren(U(e,n,W.el),s),he(l,i.length,c)}catch(n){q(t)&&M.replaceChildren(U(e,!1),I(String(n),`text-red-600`))}}let Y=()=>b&&y===`${b}~1`?b:``,ye=()=>v.commits.find(e=>e.hash===Y())?.subject??``,be=()=>Y()?`${Y()} — ${ye()||`commit`}`:`${y} ↔ ${b||`working tree`}`,xe=(e,n,r,i)=>{let a=t(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${r?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return a.innerHTML=e,a.title=n,a.onclick=i,a};function X(){let e=xe(`<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`,w,()=>{w=!w,X(),V()}),n=xe(`<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>`,T?`Restore folders`:`Collapse all folders`,T,()=>{if(T=!T,T)ae=[...E],E.clear();else for(let e of ae)E.add(e);X(),V()});if(!v.branch){A.replaceChildren(t(`div`,`flex items-center justify-end gap-1 p-1.5`,n));return}let r=C(be());if(Y()){let e=v.commits.find(e=>e.hash===Y());_(r,()=>e?m(e):``)}else r.title=`Choose what to diff`;r.onclick=async()=>{await Te(),ee(r,v,{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(t(`div`,`flex flex-col gap-1 p-2`,r,t(`div`,`flex items-center gap-1.5 pl-1`,t(`span`,`text-[11px] text-neutral-400`,`${x.size} changed file${x.size===1?``:`s`}`),...H(i,a),t(`span`,`ml-auto`),e,n)))}let Se=()=>{g&&h&&ie(g,{cwd:h,base:y,head:b})},Ce=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||v.refs.some(e=>e.name===t)||v.commits.some(e=>e.hash===t)};async function we(){if(x=new Map,!v.branch)return;let{files:e}=await a(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(){Se();try{await we()}catch(e){X(),j.replaceChildren(I(String(e),`text-red-600`));return}X(),V(),D&&de(D)}function Q(){let e=C(h||`Choose a folder…`,`neutral`);e.className+=` max-w-72`,e.title=`Switch folder`,e.onclick=()=>c(e,v.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),h||void 0,s);let n=t(`button`,`icon-btn`,`✕`);n.type=`button`,n.title=`Close Files`,n.setAttribute(`aria-label`,`Close Files`),n.onclick=p,n.classList.add(`ml-auto`),k.replaceChildren(e,t(`span`,`truncate font-mono text-[11.5px] text-neutral-400`,v.branch?`⎇ ${v.branch}`:`no git`),n)}async function Te(){if(h)try{v=await a(P(`git`,{}),`could not read this repository`),Q()}catch(e){M.replaceChildren(I(String(e),`text-red-600`))}}let $=()=>v.refs.some(e=>e.name===`main`)?`main`:v.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{v=await a(P(`git`,{}),`could not read this repository`)}catch(e){v={branch:null,refs:[],commits:[],worktrees:[]},M.replaceChildren(I(String(e),`text-red-600`))}let e=S(g),t=e&&e.cwd===h&&Ce(e.base)&&Ce(e.head)?e:null;y=t?.base??$(),b=t?.head??``,Q(),await Z()}return r(i,e=>{let t=o(),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{w as createExplorerView};
|
|
Binary file
|
|
Binary file
|