@timqi/pier 0.0.29 → 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.
Files changed (94) hide show
  1. package/dist/agent/config.js +22 -0
  2. package/dist/agent/events.js +4 -1
  3. package/dist/agent/listing.js +114 -16
  4. package/dist/agent/pi.js +158 -16
  5. package/dist/channels/lark-panel.js +1 -1
  6. package/dist/core/inbox.js +1 -1
  7. package/dist/core/router.js +4 -0
  8. package/dist/core/types.js +13 -1
  9. package/dist/db.js +30 -0
  10. package/dist/drain.js +1 -1
  11. package/dist/extensions/web/content.js +2 -2
  12. package/dist/main.js +8 -1
  13. package/dist/secrets.js +1 -1
  14. package/dist/service.js +2 -2
  15. package/dist/settings.js +31 -10
  16. package/dist/tasks/agent.js +109 -73
  17. package/dist/tasks/callbacks.js +1 -3
  18. package/dist/tasks/command.js +27 -8
  19. package/dist/tasks/execution.js +6 -4
  20. package/dist/tasks/groups.js +35 -26
  21. package/dist/tasks/messages.js +33 -27
  22. package/dist/tasks/outbox.js +48 -21
  23. package/dist/tasks/routes.js +1 -4
  24. package/dist/tasks/runs.js +10 -4
  25. package/dist/tasks/service.js +24 -12
  26. package/dist/tasks/store.js +17 -3
  27. package/dist/tasks/tool.js +97 -25
  28. package/dist/tools.js +2 -2
  29. package/dist/update.js +1 -1
  30. package/dist/web/instance.js +10 -3
  31. package/dist/web/providers.js +9 -2
  32. package/dist/web/public/assets/{activity-D3m4L2IL.js → activity-Bl3vZukb.js} +2 -2
  33. package/dist/web/public/assets/activity-Bl3vZukb.js.br +0 -0
  34. package/dist/web/public/assets/activity-Bl3vZukb.js.gz +0 -0
  35. package/dist/web/public/assets/boards-DYuf4Mlj.js +1 -0
  36. package/dist/web/public/assets/boards-DYuf4Mlj.js.br +0 -0
  37. package/dist/web/public/assets/boards-DYuf4Mlj.js.gz +0 -0
  38. package/dist/web/public/assets/explorer-qJH_9nTE.js +4 -0
  39. package/dist/web/public/assets/explorer-qJH_9nTE.js.br +0 -0
  40. package/dist/web/public/assets/explorer-qJH_9nTE.js.gz +0 -0
  41. package/dist/web/public/assets/index-Dqdb-Eqt.js +85 -0
  42. package/dist/web/public/assets/index-Dqdb-Eqt.js.br +0 -0
  43. package/dist/web/public/assets/index-Dqdb-Eqt.js.gz +0 -0
  44. package/dist/web/public/assets/index-DzmMzvi_.css +2 -0
  45. package/dist/web/public/assets/index-DzmMzvi_.css.br +0 -0
  46. package/dist/web/public/assets/index-DzmMzvi_.css.gz +0 -0
  47. package/dist/web/public/assets/runs-BLJu7EXN.js +1 -0
  48. package/dist/web/public/assets/runs-BLJu7EXN.js.br +0 -0
  49. package/dist/web/public/assets/runs-BLJu7EXN.js.gz +0 -0
  50. package/dist/web/public/assets/settings-BrdVh-Zi.js +5 -0
  51. package/dist/web/public/assets/settings-BrdVh-Zi.js.br +0 -0
  52. package/dist/web/public/assets/settings-BrdVh-Zi.js.gz +0 -0
  53. package/dist/web/public/assets/task-runs-CeQS1rxa.js +3 -0
  54. package/dist/web/public/assets/task-runs-CeQS1rxa.js.br +0 -0
  55. package/dist/web/public/assets/task-runs-CeQS1rxa.js.gz +0 -0
  56. package/dist/web/public/assets/tasks-bcb3fYdK.js +4 -0
  57. package/dist/web/public/assets/tasks-bcb3fYdK.js.br +0 -0
  58. package/dist/web/public/assets/tasks-bcb3fYdK.js.gz +0 -0
  59. package/dist/web/public/index.html +76 -120
  60. package/dist/web/public/index.html.br +0 -0
  61. package/dist/web/public/index.html.gz +0 -0
  62. package/dist/web/public/manifest.webmanifest +2 -2
  63. package/dist/web/public/manifest.webmanifest.br +0 -0
  64. package/dist/web/public/manifest.webmanifest.gz +0 -0
  65. package/dist/web/server.js +40 -8
  66. package/dist/web/session-state.js +5 -5
  67. package/package.json +2 -1
  68. package/skills/pier-tasks/SKILL.md +145 -160
  69. package/dist/web/public/assets/activity-D3m4L2IL.js.br +0 -0
  70. package/dist/web/public/assets/activity-D3m4L2IL.js.gz +0 -0
  71. package/dist/web/public/assets/boards-BIObcQeX.js +0 -1
  72. package/dist/web/public/assets/boards-BIObcQeX.js.br +0 -0
  73. package/dist/web/public/assets/boards-BIObcQeX.js.gz +0 -0
  74. package/dist/web/public/assets/explorer-C_rSWPNB.js +0 -4
  75. package/dist/web/public/assets/explorer-C_rSWPNB.js.br +0 -0
  76. package/dist/web/public/assets/explorer-C_rSWPNB.js.gz +0 -0
  77. package/dist/web/public/assets/index-CX3fYZY5.css +0 -2
  78. package/dist/web/public/assets/index-CX3fYZY5.css.br +0 -0
  79. package/dist/web/public/assets/index-CX3fYZY5.css.gz +0 -0
  80. package/dist/web/public/assets/index-uFsZkKOQ.js +0 -85
  81. package/dist/web/public/assets/index-uFsZkKOQ.js.br +0 -0
  82. package/dist/web/public/assets/index-uFsZkKOQ.js.gz +0 -0
  83. package/dist/web/public/assets/runs-Ch6DZq6O.js +0 -1
  84. package/dist/web/public/assets/runs-Ch6DZq6O.js.br +0 -0
  85. package/dist/web/public/assets/runs-Ch6DZq6O.js.gz +0 -0
  86. package/dist/web/public/assets/settings-BWcEIEcv.js +0 -5
  87. package/dist/web/public/assets/settings-BWcEIEcv.js.br +0 -0
  88. package/dist/web/public/assets/settings-BWcEIEcv.js.gz +0 -0
  89. package/dist/web/public/assets/task-runs-DPkwv2UE.js +0 -3
  90. package/dist/web/public/assets/task-runs-DPkwv2UE.js.br +0 -0
  91. package/dist/web/public/assets/task-runs-DPkwv2UE.js.gz +0 -0
  92. package/dist/web/public/assets/tasks-DTiCi2mH.js +0 -4
  93. package/dist/web/public/assets/tasks-DTiCi2mH.js.br +0 -0
  94. package/dist/web/public/assets/tasks-DTiCi2mH.js.gz +0 -0
@@ -1,16 +1,17 @@
1
1
  import { isAbsolute, resolve } from "node:path";
2
2
  import { Type } from "typebox";
3
- import { TaskDefinitions, record, requiredString } from "./definitions.js";
4
- import { TaskMessenger } from "./messages.js";
5
- import { TaskStore } from "./store.js";
3
+ import { logger } from "../log.js";
4
+ import { record, requiredString } from "./definitions.js";
5
+ import { isTerminal } from "./types.js";
6
+ const log = logger("tasks");
6
7
  // JSON-Schema enum emits ~1/3 the tokens of typebox's anyOf-of-consts.
7
8
  const strEnum = (...values) => Type.Unsafe({ type: "string", enum: [...values] });
8
9
  /**
9
10
  * Drop the fields with nothing in them instead of sending `null`.
10
11
  *
11
12
  * A run summary has eighteen fields and most are empty for most of a run's
12
- * life; a model reads "absent" and "null" the same way. On a `get` that lists
13
- * several runs this is a third of the payload.
13
+ * life; a model reads "absent" and "null" the same way. On a group summary
14
+ * that lists several runs this is a third of the payload.
14
15
  *
15
16
  * The input names every field — a summary that forgot one would otherwise pass
16
17
  * as "that field was empty" — and the result is the type with the empty ones
@@ -28,6 +29,7 @@ const summarize = (run, pendingDecisionId) => defined({
28
29
  targetSessionId: run.targetSessionId,
29
30
  callbackSessionId: run.callbackSessionId,
30
31
  callbackState: run.callbackState,
32
+ callbackMode: run.callbackMode ?? null,
31
33
  pendingDecisionId,
32
34
  depth: run.depth,
33
35
  queuedAt: run.queuedAt,
@@ -36,9 +38,23 @@ const summarize = (run, pendingDecisionId) => defined({
36
38
  result: run.result,
37
39
  error: run.error,
38
40
  skipReason: run.skipReason,
41
+ next: null,
39
42
  });
40
- /** A list echoes many results at once, so each is capped; a single-run `get`
41
- * stays whole — it is the escape hatch every truncation note points at. */
43
+ /** What a run receipt says happens next. The callback is the whole answer, so
44
+ * the receipt says so — a model that just launched work otherwise reaches
45
+ * for a status call. `none` is stated as what it is: no delivery at all. */
46
+ const receipt = (summary, callbackSessionId, mode, callerSessionId) => ({
47
+ ...summary,
48
+ next: callbackSessionId === null
49
+ ? "callback none: the result is not delivered to anyone"
50
+ : callbackSessionId !== callerSessionId
51
+ ? `the result is delivered to session ${callbackSessionId}; this session will not receive a callback`
52
+ : mode === "steer"
53
+ ? "the result interrupts your running turn as a steer message; nothing to query"
54
+ : "the result arrives as a callback message once your turn ends; nothing to query",
55
+ });
56
+ /** A group echoes many results at once, so each is capped; a single-run
57
+ * `recover` stays whole — it is the escape hatch every truncation note points at. */
42
58
  const trimResult = (summary) => {
43
59
  if (summary.result?.type !== "agent" || summary.result.text.length <= 2000)
44
60
  return summary;
@@ -46,17 +62,32 @@ const trimResult = (summary) => {
46
62
  ...summary,
47
63
  result: {
48
64
  ...summary.result,
49
- text: `${summary.result.text.slice(0, 2000)}\n[truncated — get run_id ${summary.runId} for the full text]`,
65
+ text: `${summary.result.text.slice(0, 2000)}\n[truncated — recover run_id ${summary.runId} with a reason for the full text]`,
50
66
  },
51
67
  };
52
68
  };
69
+ /** Whether a callback record has said its last word: the input is in the
70
+ * recipient's transcript, delivery was given up on and reported, or there
71
+ * was never one to wait for (`callback:"none"`). Anything else is still on
72
+ * its way, and reading the result here would be reading it twice. */
73
+ const settled = (callback) => callback.callbackState === null || callback.callbackState === "delivered" || callback.callbackState === "abandoned";
74
+ /** The one refusal `recover` gives before a result is readable. Deliberately
75
+ * the same words for queued, running, pending and retrying: a refusal that
76
+ * named the state would be the status query this operation replaced. */
77
+ const notRecoverable = (what, callback) => {
78
+ throw new Error(callback.callbackSessionId === null
79
+ ? `${what} is not recoverable yet: it was launched with callback none, so nothing will be delivered; recover reads finished results only and cannot wait for work`
80
+ : `${what} is not recoverable yet: wait for automatic delivery to session ${callback.callbackSessionId}; recover cannot wait for work`);
81
+ };
53
82
  const summarizeGroup = (group, members, messages) => defined({
54
83
  groupId: group.id,
55
84
  join: group.join,
56
85
  state: group.finishedAt ? "finished" : "running",
57
86
  callbackState: group.callbackState,
87
+ callbackMode: group.callbackMode ?? null,
58
88
  winnerRunId: group.winnerRunId,
59
89
  members: members.map((run) => trimResult(summarize(run, messages.openDecisionId(run.id)))),
90
+ next: null,
60
91
  });
61
92
  const LaunchSchema = Type.Object({
62
93
  model: Type.Optional(Type.Object({ provider: Type.String(), id: Type.String() })),
@@ -103,15 +134,15 @@ export function taskToolSpec(execute) {
103
134
  return {
104
135
  name: "task",
105
136
  label: "Pier Task",
106
- description: "Manage durable Pier tasks and subagents. Agent tasks run in a fresh session or a reused one. create files a definition the operator sees in the Console — only for schedules or roles you will run again; a one-off is run with a prompt. Run executes a stored task by task_id, a one-shot subagent from a prompt (shorthand: prompt + optional cwd/launch/name — cwd defaults to your own directory, relative paths resolve against it, name comes from the prompt) or from a full inline task draft, or a core-joined fan-out via tasks[] with join all|first. Get accepts run_id, group_id, or task_id for that task's recent runs. Every operation returns immediately: results, group joins, and decision replies arrive as callback messages. Use steer/follow_up/resume for child control and contact/reply for supervisor decisions. models lists the deployment's model menu (operator pins with intent notes, else the live catalog).",
137
+ description: "Manage durable Pier tasks and subagents. Agent tasks run in a fresh session or a reused one. create files a definition the operator sees in the Console — only for schedules or roles you will run again; a one-off is run with a prompt. Run executes a stored task by task_id, a one-shot subagent from a prompt (shorthand: prompt + optional cwd/launch/name — cwd defaults to your own directory, relative paths resolve against it, name comes from the prompt) or from a full inline task draft, or a core-joined fan-out via tasks[] with join all|first. Every operation returns immediately: results, group joins, and decision replies arrive as callback messages once your turn ends — there is no status query; pass callback 'steer' to have a result interrupt your running turn instead, or 'none' for no callback at all. recover (run_id or group_id, plus a reason) re-reads a finished result after its callback has settled — for truncated text or lost context, never to check progress. Use steer/follow_up/resume for child control and contact/reply for supervisor decisions. models lists the deployment's model menu (operator pins with intent notes, else the live catalog).",
107
138
  parameters: Type.Object({
108
- operation: strEnum("list", "create", "update", "run", "get", "cancel", "steer", "follow_up", "resume", "contact", "reply", "models"),
139
+ operation: strEnum("list", "create", "update", "run", "recover", "cancel", "steer", "follow_up", "resume", "contact", "reply", "models"),
109
140
  task_id: Type.Optional(Type.String()),
110
141
  run_id: Type.Optional(Type.String()),
111
142
  group_id: Type.Optional(Type.String()),
112
143
  message_id: Type.Optional(Type.String()),
113
144
  message: Type.Optional(Type.String()),
114
- reason: Type.Optional(strEnum("progress", "decision")),
145
+ reason: Type.Optional(Type.String({ description: "contact: progress | decision. recover: why the delivered callback is not enough (required)." })),
115
146
  session_mode: Type.Optional(strEnum("fresh")),
116
147
  // The one-shot shorthand: a prompt is the whole delegation, and the
117
148
  // fresh session in the caller's own directory is what it means.
@@ -130,7 +161,7 @@ export function taskToolSpec(execute) {
130
161
  })),
131
162
  join: Type.Optional(strEnum("all", "first")),
132
163
  input: Type.Optional(Type.Unknown()),
133
- callback: Type.Optional(strEnum("origin", "none")),
164
+ callback: Type.Optional(strEnum("origin", "none", "steer")),
134
165
  callback_session_id: Type.Optional(Type.String()),
135
166
  }),
136
167
  execute,
@@ -156,6 +187,9 @@ export async function handleTaskTool(host, definitions, store, messages, raw, ca
156
187
  return definitions.update(requiredString(input.task_id, "task_id"), await expandDraft(definitions, input.task, callerSessionId));
157
188
  }
158
189
  if (input.operation === "run") {
190
+ // One reading of `callback` for both shapes below: a run and a fan-out
191
+ // choose the same way, and two readings are two things to keep in step.
192
+ const callbackMode = input.callback === "steer" ? "steer" : "followUp";
159
193
  if (Array.isArray(input.tasks)) {
160
194
  // Core-joined fan-out: members run detached, one aggregated callback.
161
195
  if (input.task !== undefined || input.task_id !== undefined)
@@ -173,8 +207,9 @@ export async function handleTaskTool(host, definitions, store, messages, raw, ca
173
207
  ? await resolveDraft(definitions, entry, active, callerSessionId)
174
208
  : resolveStored(definitions, entry.task_id, active));
175
209
  }
176
- const { group, runs } = host.runGroup(resolved, input.join === "first" ? "first" : "all", callerSessionId, active?.id ?? null, input.callback === "none" ? null : callerSessionId);
177
- return summarizeGroup(group, runs, messages);
210
+ const groupCallbackSessionId = input.callback === "none" ? null : callerSessionId;
211
+ const { group, runs } = host.runGroup(resolved, input.join === "first" ? "first" : "all", callerSessionId, active?.id ?? null, groupCallbackSessionId, callbackMode);
212
+ return receipt(summarizeGroup(group, runs, messages), groupCallbackSessionId, callbackMode, callerSessionId);
178
213
  }
179
214
  const draft = input.task_id === undefined ? inlineDraft(input) : undefined;
180
215
  const task = draft
@@ -196,23 +231,55 @@ export async function handleTaskTool(host, definitions, store, messages, raw, ca
196
231
  invokedBySessionId: callerSessionId,
197
232
  sourceSessionId: callerSessionId,
198
233
  callbackSessionId,
234
+ callbackMode,
199
235
  background: true,
200
236
  sessionMode,
201
237
  });
202
- return summarize(run, null);
238
+ return receipt(summarize(run, null), callbackSessionId, callbackMode, callerSessionId);
203
239
  }
204
- if (input.operation === "get") {
240
+ if (input.operation === "recover") {
241
+ // History only, never status: a result is readable here once its callback
242
+ // has said its last word, so nothing a caller could learn by asking is
243
+ // something it would not have been told. The reason is the friction — a
244
+ // caller states why the callback did not suffice, and the operator sees it.
245
+ const reason = requiredString(input.reason, "reason");
246
+ // A finished run with an open decision sends no completion callback — the
247
+ // question is the notification, and the reply's continuation reports.
248
+ const decisionOpen = (run) => {
249
+ throw new Error(`run ${run.id} finished awaiting your decision ${messages.openDecisionId(run.id) ?? ""}; reply to it — the continuation's callback brings the result`);
250
+ };
251
+ // A member waits for its group's callback, but a race winner need not
252
+ // wait for losing members to finish cancelling before recovering its text.
253
+ const groupReady = (group) => {
254
+ if (!group.finishedAt || !settled(group))
255
+ notRecoverable(`group ${group.id}`, group);
256
+ };
205
257
  if (typeof input.group_id === "string") {
206
258
  const { group, members } = host.getGroup(input.group_id);
259
+ groupReady(group);
260
+ if (!members.every((run) => isTerminal(run.state))) {
261
+ throw new Error(`recover cannot inspect active members; the race has settled — recover its winning result with run_id ${group.winnerRunId ?? "from the callback"} and a reason`);
262
+ }
263
+ const asking = members.find((run) => messages.openDecisionId(run.id));
264
+ if (asking)
265
+ decisionOpen(asking);
266
+ log.info(`recover group ${group.id} by ${callerSessionId}: ${reason}`);
207
267
  return summarizeGroup(group, members, messages);
208
268
  }
209
- // Run history by task: without it, checking what a task did (or whether a
210
- // cascade landed) means leaving the tool for the database.
211
- if (input.run_id === undefined && typeof input.task_id === "string") {
212
- return host.listRuns(input.task_id, 10).map((run) => trimResult(summarize(run, messages.openDecisionId(run.id))));
213
- }
214
269
  const run = host.getRun(requiredString(input.run_id, "run_id"));
215
- return summarize(run, messages.openDecisionId(run.id));
270
+ if (run.groupId) {
271
+ const { group } = host.getGroup(run.groupId);
272
+ groupReady(group);
273
+ if (!isTerminal(run.state))
274
+ throw new Error("the group has reported its outcome; recover reads finished results only and cannot wait for this member");
275
+ }
276
+ else if (!isTerminal(run.state) || !settled(run)) {
277
+ notRecoverable(`run ${run.id}`, run);
278
+ }
279
+ if (messages.openDecisionId(run.id))
280
+ decisionOpen(run);
281
+ log.info(`recover run ${run.id} by ${callerSessionId}: ${reason}`);
282
+ return summarize(run, null);
216
283
  }
217
284
  if (input.operation === "cancel") {
218
285
  if (typeof input.group_id === "string") {
@@ -235,17 +302,22 @@ export async function handleTaskTool(host, definitions, store, messages, raw, ca
235
302
  if (input.operation === "resume") {
236
303
  const prior = host.getRun(requiredString(input.run_id, "run_id"));
237
304
  assertOwns(store, callerSessionId, active, prior);
305
+ const callbackSessionId = input.callback === "none" ? null : callerSessionId;
238
306
  const run = host.resume(prior.id, requiredString(input.message, "message"), {
239
307
  invokedBySessionId: callerSessionId,
240
- callbackSessionId: input.callback === "none" ? null : callerSessionId,
308
+ callbackSessionId,
241
309
  background: true,
242
310
  });
243
- return summarize(run, null);
311
+ return receipt(summarize(run, null), callbackSessionId, "followUp", callerSessionId);
244
312
  }
245
313
  if (input.operation === "contact") {
246
314
  if (!active)
247
315
  throw new Error("contact is only available inside an active Agent run");
248
- const reason = input.reason === "decision" ? "decision" : "progress";
316
+ // The schema no longer narrows `reason` (recover shares the field), so the
317
+ // two names contact accepts are checked here.
318
+ const reason = input.reason === undefined ? "progress" : input.reason;
319
+ if (reason !== "progress" && reason !== "decision")
320
+ throw new Error(`contact reason must be progress or decision, got ${String(reason)}`);
249
321
  return messages.contact(active, callerSessionId, reason, requiredString(input.message, "message"));
250
322
  }
251
323
  if (input.operation === "reply") {
package/dist/tools.js CHANGED
@@ -186,10 +186,10 @@ managedName = "reject") {
186
186
  return tools;
187
187
  }
188
188
  /** `~/.pier/tools/…` — install target, generated ubix config, ubix state. */
189
- export const toolsDir = (...parts) => pierPath("tools", ...parts);
189
+ const toolsDir = (...parts) => pierPath("tools", ...parts);
190
190
  /** The one directory that goes on PATH: ubix installs into it, and everything
191
191
  * Pier spawns inherits it. */
192
- export const toolsBin = () => toolsDir("bin");
192
+ const toolsBin = () => toolsDir("bin");
193
193
  /**
194
194
  * First on PATH, once, at boot. First rather than last on purpose: a tool
195
195
  * switched on in the Console is Pier's copy at Pier's version, whatever the
package/dist/update.js CHANGED
@@ -129,7 +129,7 @@ export function startAutoUpdate(check, auto, pollMs = AUTO_POLL_MS) {
129
129
  timer.unref(); // a pending check must never be what keeps the process alive
130
130
  return () => clearInterval(timer);
131
131
  }
132
- export async function fetchLatestVersion() {
132
+ async function fetchLatestVersion() {
133
133
  // Plain JSON: the abbreviated-packument content type npm uses for a whole
134
134
  // package is a 406 on this endpoint, which answers one version already.
135
135
  const res = await fetch(ENDPOINT, {
@@ -2,7 +2,7 @@
2
2
  // layer-1 secrets control, the browser's error reports. Nothing here touches
3
3
  // a session; server.ts stays the session/event surface.
4
4
  import { logger } from "../log.js";
5
- import { normalizeModelMenu, normalizePublicUrl, } from "../settings.js";
5
+ import { normalizeModelMenu, normalizeModelRef, normalizePublicUrl, } from "../settings.js";
6
6
  /** A rule that failed inside the transaction — its own class so the route can
7
7
  * tell it from a real fault. 400 when the request named something this Pier
8
8
  * does not have, 409 when it collided with the state as it stands. */
@@ -131,11 +131,11 @@ export function registerInstanceRoutes(app, deps) {
131
131
  app.put("/api/settings", async (c) => {
132
132
  const body = await c.req.json().catch(() => null);
133
133
  const fields = body
134
- ? [body.publicUrl, body.modelMenu, body.autoUpdate, body.customTools, body.extension, body.tool]
134
+ ? [body.publicUrl, body.modelMenu, body.titleModel, body.autoUpdate, body.customTools, body.extension, body.tool]
135
135
  : [];
136
136
  if (!fields.some((v) => v !== undefined)) {
137
137
  return c.json({
138
- error: "publicUrl, modelMenu, autoUpdate, customTools, extension or tool required",
138
+ error: "publicUrl, modelMenu, titleModel, autoUpdate, customTools, extension or tool required",
139
139
  }, 400);
140
140
  }
141
141
  // Everything is validated before anything is written, and everything is
@@ -157,6 +157,13 @@ export function registerInstanceRoutes(app, deps) {
157
157
  return refuse("modelMenu must be [{provider, id, note?}] (≤32 entries)");
158
158
  writes.push(() => settings.setModelMenu(menu));
159
159
  }
160
+ if (body?.titleModel !== undefined) {
161
+ // null is the off switch; anything else has to be a model.
162
+ const ref = body.titleModel === null ? null : normalizeModelRef(body.titleModel);
163
+ if (ref === null && body.titleModel !== null)
164
+ return refuse("titleModel must be {provider, id} or null");
165
+ writes.push(() => settings.setTitleModel(ref));
166
+ }
160
167
  if (body?.autoUpdate !== undefined) {
161
168
  const { autoUpdate } = body;
162
169
  if (typeof autoUpdate !== "boolean")
@@ -1,5 +1,5 @@
1
1
  // HTTP boundary for provider configuration.
2
- import { isProviderApi, validateProviderSetup } from "../core/types.js";
2
+ import { isProviderApi, MODEL_EFFORTS, validateProviderSetup } from "../core/types.js";
3
3
  import { ProviderFlows } from "./provider-flows.js";
4
4
  /** Unknown JSON into a trimmed, validated ProviderSetup. Shape lives here;
5
5
  * the rules (id charset, endpoint safety, model limits) are the shared
@@ -26,7 +26,14 @@ function setupFrom(raw) {
26
26
  : "";
27
27
  if (!modelId)
28
28
  return null;
29
- models.push({ id: modelId, reasoning: model.reasoning === true });
29
+ const effort = model.effort;
30
+ if (effort !== undefined && !MODEL_EFFORTS.includes(effort))
31
+ return null;
32
+ models.push({
33
+ id: modelId,
34
+ reasoning: model.reasoning === true,
35
+ ...(effort === undefined ? {} : { effort: effort }),
36
+ });
30
37
  }
31
38
  return {
32
39
  kind: "custom",
@@ -1,5 +1,5 @@
1
- import{A as e,B as t,L as n,P as r,R as i,U as a,V as o,h as s,q as c,w as l,y as u}from"./index-uFsZkKOQ.js";var d=(e,t={})=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},f=e=>e===null?`-`:n(Date.now()-e),p={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},m=()=>p[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function h(n,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=o(async()=>{let e=_,t=await a(`/api/activity?scope=${e}`,`Failed to load activity`);if(!t.ok){y=``,n.replaceChildren(i(`p`,`p-4 text-[13px] text-red-600`,t.error));return}if(e!==_)return;let r=JSON.stringify(t.value);if(`${e}:${r}`===y)return;y=`${e}:${r}`;let o=t.value;v={...o,sessions:o.sessions.map(e=>({...e,title:c(e.title)}))},x()});function x(){let t=e(l([[`Sessions`,`sessions`],[`Relationships`,`dependencies`]],g,e=>{g=e,x()}),i(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${v.sessions.length} sessions · ${v.runs.length} runs`),l([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),r=i(`div`,`min-h-0 flex-1 overflow-auto`);g===`sessions`?S(r):w(r),n.replaceChildren(t,r)}function S(e){let n=document.createElement(`table`);n.className=`w-full table-fixed text-left text-[12.5px]`,n.innerHTML=`<thead class="sticky top-0 bg-neutral-50 text-[10.5px] uppercase tracking-wide text-neutral-400 shadow-[inset_0_-1px_0_var(--color-neutral-200)]"><tr>
1
+ import{G as e,H as t,O as n,P as r,R as i,S as a,V as o,W as s,Z as c,q as l,v as u}from"./index-Dqdb-Eqt.js";var d=(e,t={})=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},f=e=>e===null?`-`:o(Date.now()-e),p={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},m=()=>p[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function h(o,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=e(async()=>{let e=_,n=await l(`/api/activity?scope=${e}`,`Failed to load activity`);if(!n.ok){y=``,o.replaceChildren(t(`p`,`p-4 text-[13px] text-red-600`,n.error));return}if(e!==_)return;let r=JSON.stringify(n.value);if(`${e}:${r}`===y)return;y=`${e}:${r}`;let i=n.value;v={...i,sessions:i.sessions.map(e=>({...e,title:c(e.title)}))},x()});function x(){let e=r(n([[`Sessions`,`sessions`],[`Relationships`,`dependencies`]],g,e=>{g=e,x()}),t(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${v.sessions.length} sessions · ${v.runs.length} runs`),n([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),i=t(`div`,`automation-list min-h-0 flex-1 overflow-auto`);g===`sessions`?S(i):w(i),o.replaceChildren(e,i)}function S(e){let n=document.createElement(`table`);n.className=`w-full table-fixed text-left text-[12.5px]`,n.innerHTML=`<thead class="sticky top-0 bg-neutral-50 text-[10.5px] uppercase tracking-wide text-neutral-400 shadow-[inset_0_-1px_0_var(--color-neutral-200)]"><tr>
2
2
  <th class="w-[50%] px-4 py-2 font-semibold md:w-[34%]">Session</th>
3
3
  <th class="hidden w-[38%] px-2 py-2 font-semibold md:table-cell">Project</th>
4
4
  <th class="px-2 py-2 font-semibold md:w-[14%]">State</th>
5
- <th class="px-2 py-2 font-semibold">In state</th></tr></thead>`;let r=document.createElement(`tbody`);for(let e of v.sessions){let n=document.createElement(`tr`);n.className=`cursor-pointer border-b border-neutral-100 transition-colors hover:bg-neutral-50`,n.onclick=()=>p(e.id);let a=i(`button`,`block w-full cursor-pointer truncate text-left font-medium text-neutral-800`,e.title??t(e.cwd));a.setAttribute(`type`,`button`),a.title=`${e.id}\n${e.cwd}`,a.onclick=t=>{t.stopPropagation(),p(e.id)},n.append(i(`td`,`px-4 py-2.5`,a,i(`div`,`truncate font-mono text-[11px] text-neutral-400 md:hidden`,e.cwd||`-`)),i(`td`,`hidden truncate px-2 py-2.5 font-mono text-[11.5px] text-neutral-500 md:table-cell`,e.cwd||`-`),i(`td`,`px-2 py-2.5`,C(e.state)),i(`td`,`px-2 py-2.5 font-mono text-[11.5px] text-neutral-400`,f(e.stateSince))),r.append(n)}n.append(r),e.append(n),v.sessions.length||e.append(i(`div`,`p-4`,u(_===`active`?`No active sessions.`:`No sessions in the last 24 hours.`)))}function C(e){return e===`streaming`?s(e,`bg-emerald-50 text-emerald-700 ring-emerald-200`,`animate-pulse bg-emerald-500`):s(e,`bg-neutral-100 text-neutral-600 ring-neutral-200`,`bg-neutral-300`)}function w(e){let t=new Map,n=[],r=(e,n,r,i)=>{t.has(e)||t.set(e,{id:e,label:n,kind:r,state:i})};for(let e of v.sessions)r(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of v.runs){let t=e.invokedBySessionId??`scheduler`,i=e.targetSessionId??(e.context.definition.action.type===`bash`?`process:${e.id}`:`task:${e.id}`);r(t,t===`scheduler`?`Scheduler`:t.slice(0,10),t===`scheduler`?`scheduler`:`session`),r(i,e.targetSessionId?e.targetSessionId.slice(0,10):e.context.definition.action.type===`bash`?`Bash process`:`Task`,e.targetSessionId?`session`:e.context.definition.action.type===`bash`?`process`:`task`),n.push({from:t,to:i,run:e,kind:`invocation`}),e.callbackSessionId&&(r(e.callbackSessionId,e.callbackSessionId.slice(0,10),`session`),n.push({from:i,to:e.callbackSessionId,run:e,kind:`callback`}))}for(let e of v.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=v.runs.find(t=>t.id===e.runId);if(t){for(let t of[e.fromSessionId,e.toSessionId])r(t,t===`console`?`Console`:t.slice(0,10),t===`console`?`console`:`session`);n.push({from:e.fromSessionId,to:e.toSessionId,run:t,kind:`message`})}}if(!t.size){e.append(i(`div`,`p-4`,u(_===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`)));return}let a=[...t.values()],o=new Set(n.map(e=>e.to)),s=new Map,c=a.filter(e=>!o.has(e.id)).map(e=>e.id);c.length||c.push(a[0].id);for(let e of c)s.set(e,0);for(;c.length;){let e=c.shift();for(let t of n)t.from!==e||s.has(t.to)||(s.set(t.to,s.get(e)+1),c.push(t.to))}for(let e of a)s.has(e.id)||s.set(e.id,0);let l=new Map;for(let e of a){let t=s.get(e.id);l.set(t,[...l.get(t)??[],e])}let f=Math.max(...l.keys())+1,g=Math.max(...[...l.values()].map(e=>e.length)),y=n.some(e=>e.from!==e.to&&s.get(e.from)===s.get(e.to))?40:0,b=56+f*180+(f-1)*110+y,x=56+g*46+(g-1)*30,S=new Map;for(let[e,t]of l)t.forEach((t,n)=>S.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let C=d(`svg`,{width:String(b),height:String(x),viewBox:`0 0 ${b} ${x}`,class:`block`}),w=d(`defs`),T=m();for(let[e,t]of[[`activity-arrow`,T.edge],[`activity-arrow-cb`,T.callback],[`activity-arrow-msg`,T.message]]){let n=d(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(d(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),w.append(n)}C.append(w);let E=new Set;for(let e of n){let t=`${e.from}→${e.to}:${e.kind}`;if(E.has(t))continue;E.add(t);let n=S.get(e.from),r=S.get(e.to),i=d(`path`,{d:n.x===r.x?`M ${n.x+90} ${n.y} C ${n.x+90+46} ${n.y}, ${r.x+90+46} ${r.y}, ${r.x+90} ${r.y}`:(()=>{let e=r.x>n.x,t=n.x+(e?90:-90),i=r.x+(e?-90:90),a=Math.max(36,Math.abs(i-t)*.45)*(e?1:-1);return`M ${t} ${n.y} C ${t+a} ${n.y}, ${i-a} ${r.y}, ${i} ${r.y}`})(),fill:`none`,stroke:e.kind===`callback`?T.callback:e.kind===`message`?T.message:T.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>h(e.run.id),C.append(i)}let D=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,O=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=D.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of a){let t=S.get(e.id),n=e.kind===`session`?e.state===`streaming`?T.streaming:T.idle:T[e.kind],r=d(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&p(e.id)};let i=d(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`8`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=d(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=d(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=d(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=O(e.label,e.kind===`session`?22:25);let s=d(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),C.append(r)}let k=e=>i(`span`,`inline-block h-2 w-2 rounded-full ${e}`),A=e=>i(`span`,`inline-block h-0 w-6 border-t-2 ${e}`);e.append(i(`div`,`w-max p-4`,C),i(`div`,`flex flex-wrap gap-x-5 gap-y-1.5 border-t border-neutral-200 bg-neutral-50/60 px-4 py-2 text-[11px] text-neutral-500`,i(`span`,`inline-flex items-center gap-2`,A(`border-solid border-neutral-400`),`task invocation`),i(`span`,`inline-flex items-center gap-2 text-cyan-700`,A(`border-dashed border-cyan-500`),`callback`),i(`span`,`inline-flex items-center gap-2 text-amber-700`,A(`border-dotted border-amber-500`),`supervisor / control`),i(`span`,`inline-flex items-center gap-1.5`,k(`bg-emerald-500`),`streaming`),i(`span`,`inline-flex items-center gap-1.5`,k(`bg-neutral-300`),`idle`)))}let T=r(n,e=>{e===`dependencies`?g=`dependencies`:e===`sessions`&&(g=`sessions`),b()},()=>{n.replaceChildren(),v={sessions:[],runs:[],messages:[]},y=``});return window.addEventListener(`pier:theme`,()=>{T.visible&&x()}),Object.assign(T,{refresh(){T.visible&&b()}})}export{h as createActivityView};
5
+ <th class="px-2 py-2 font-semibold">In state</th></tr></thead>`;let r=document.createElement(`tbody`);for(let e of v.sessions){let n=document.createElement(`tr`);n.className=`cursor-pointer border-b border-neutral-100 transition-colors hover:bg-neutral-50`,n.onclick=()=>p(e.id);let i=t(`button`,`block w-full cursor-pointer truncate text-left font-medium text-neutral-800`,e.title??s(e.cwd));i.setAttribute(`type`,`button`),i.title=`${e.id}\n${e.cwd}`,i.onclick=t=>{t.stopPropagation(),p(e.id)},n.append(t(`td`,`px-4 py-2.5`,i,t(`div`,`truncate font-mono text-[11px] text-neutral-400 md:hidden`,e.cwd||`-`)),t(`td`,`hidden truncate px-2 py-2.5 font-mono text-[11.5px] text-neutral-500 md:table-cell`,e.cwd||`-`),t(`td`,`px-2 py-2.5`,C(e.state)),t(`td`,`px-2 py-2.5 font-mono text-[11.5px] text-neutral-400`,f(e.stateSince))),r.append(n)}n.append(r),e.append(n),v.sessions.length||e.append(t(`div`,`p-4`,a(_===`active`?`No active sessions.`:`No sessions in the last 24 hours.`)))}function C(e){return e===`streaming`?u(e,`bg-emerald-50 text-emerald-700 ring-emerald-200`,`animate-pulse bg-emerald-500`):u(e,`bg-neutral-100 text-neutral-600 ring-neutral-200`,`bg-neutral-300`)}function w(e){let n=new Map,r=[],i=(e,t,r,i)=>{n.has(e)||n.set(e,{id:e,label:t,kind:r,state:i})};for(let e of v.sessions)i(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of v.runs){let t=e.invokedBySessionId??`scheduler`,n=e.targetSessionId??(e.context.definition.action.type===`bash`?`process:${e.id}`:`task:${e.id}`);i(t,t===`scheduler`?`Scheduler`:t.slice(0,10),t===`scheduler`?`scheduler`:`session`),i(n,e.targetSessionId?e.targetSessionId.slice(0,10):e.context.definition.action.type===`bash`?`Bash process`:`Task`,e.targetSessionId?`session`:e.context.definition.action.type===`bash`?`process`:`task`),r.push({from:t,to:n,run:e,kind:`invocation`}),e.callbackSessionId&&(i(e.callbackSessionId,e.callbackSessionId.slice(0,10),`session`),r.push({from:n,to:e.callbackSessionId,run:e,kind:`callback`}))}for(let e of v.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=v.runs.find(t=>t.id===e.runId);if(t){for(let t of[e.fromSessionId,e.toSessionId])i(t,t===`console`?`Console`:t.slice(0,10),t===`console`?`console`:`session`);r.push({from:e.fromSessionId,to:e.toSessionId,run:t,kind:`message`})}}if(!n.size){e.append(t(`div`,`p-4`,a(_===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`)));return}let o=[...n.values()],s=new Set(r.map(e=>e.to)),c=new Map,l=o.filter(e=>!s.has(e.id)).map(e=>e.id);l.length||l.push(o[0].id);for(let e of l)c.set(e,0);for(;l.length;){let e=l.shift();for(let t of r)t.from!==e||c.has(t.to)||(c.set(t.to,c.get(e)+1),l.push(t.to))}for(let e of o)c.has(e.id)||c.set(e.id,0);let u=new Map;for(let e of o){let t=c.get(e.id);u.set(t,[...u.get(t)??[],e])}let f=Math.max(...u.keys())+1,g=Math.max(...[...u.values()].map(e=>e.length)),y=r.some(e=>e.from!==e.to&&c.get(e.from)===c.get(e.to))?40:0,b=56+f*180+(f-1)*110+y,x=56+g*46+(g-1)*30,S=new Map;for(let[e,t]of u)t.forEach((t,n)=>S.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let C=d(`svg`,{width:String(b),height:String(x),viewBox:`0 0 ${b} ${x}`,class:`block`}),w=d(`defs`),T=m();for(let[e,t]of[[`activity-arrow`,T.edge],[`activity-arrow-cb`,T.callback],[`activity-arrow-msg`,T.message]]){let n=d(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(d(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),w.append(n)}C.append(w);let E=new Set;for(let e of r){let t=`${e.from}→${e.to}:${e.kind}`;if(E.has(t))continue;E.add(t);let n=S.get(e.from),r=S.get(e.to),i=d(`path`,{d:n.x===r.x?`M ${n.x+90} ${n.y} C ${n.x+90+46} ${n.y}, ${r.x+90+46} ${r.y}, ${r.x+90} ${r.y}`:(()=>{let e=r.x>n.x,t=n.x+(e?90:-90),i=r.x+(e?-90:90),a=Math.max(36,Math.abs(i-t)*.45)*(e?1:-1);return`M ${t} ${n.y} C ${t+a} ${n.y}, ${i-a} ${r.y}, ${i} ${r.y}`})(),fill:`none`,stroke:e.kind===`callback`?T.callback:e.kind===`message`?T.message:T.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>h(e.run.id),C.append(i)}let D=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,O=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=D.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of o){let t=S.get(e.id),n=e.kind===`session`?e.state===`streaming`?T.streaming:T.idle:T[e.kind],r=d(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&p(e.id)};let i=d(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`8`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=d(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=d(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=d(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=O(e.label,e.kind===`session`?22:25);let s=d(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),C.append(r)}let k=e=>t(`span`,`inline-block h-2 w-2 rounded-full ${e}`),A=e=>t(`span`,`inline-block h-0 w-6 border-t-2 ${e}`);e.append(t(`div`,`w-max p-4`,C),t(`div`,`flex flex-wrap gap-x-5 gap-y-1.5 border-t border-neutral-200 bg-neutral-50/60 px-4 py-2 text-[11px] text-neutral-500`,t(`span`,`inline-flex items-center gap-2`,A(`border-solid border-neutral-400`),`task invocation`),t(`span`,`inline-flex items-center gap-2 text-cyan-700`,A(`border-dashed border-cyan-500`),`callback`),t(`span`,`inline-flex items-center gap-2 text-amber-700`,A(`border-dotted border-amber-500`),`supervisor / control`),t(`span`,`inline-flex items-center gap-1.5`,k(`bg-emerald-500`),`streaming`),t(`span`,`inline-flex items-center gap-1.5`,k(`bg-neutral-300`),`idle`)))}let T=i(o,e=>{e===`dependencies`?g=`dependencies`:e===`sessions`&&(g=`sessions`),b()},()=>{o.replaceChildren(),v={sessions:[],runs:[],messages:[]},y=``});return window.addEventListener(`pier:theme`,()=>{T.visible&&x()}),Object.assign(T,{refresh(){T.visible&&b()}})}export{h as createActivityView};
@@ -0,0 +1 @@
1
+ import{E as e,H as t,K as n,N as r,R as i,S as a,U as o,X as s,Y as c,q as l,x as u,y as d,z as f}from"./index-Dqdb-Eqt.js";var p=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function m(m,h){let g=[],_=``,v=t(`header`,`pagehead sticky top-[-8px] z-30 max-md:hidden`,e(`Boards`)),y=t(`div`,`px-4 pb-5 pt-1`);m.append(t(`div`,`min-h-0 flex-1 overflow-y-auto`,v,y));async function b(e,t){let r=await s(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(r.ok)return e.token=(await r.json().catch(()=>({}))).token??``,w();_=await n(r,`Could not change ${e.slug}`),await T()}async function x(e){_=await c(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await T()}function S(e){let n=t(`div`,`group flex flex-col gap-1 py-2.5`),i=document.createElement(`a`);i.className=`min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800 hover:text-indigo-700 max-md:basis-full`,i.href=p(e),i.target=`_blank`,i.rel=`noreferrer`,i.textContent=e.title;let a=t(`div`,`flex flex-wrap items-center gap-x-2 gap-y-1`,i),s=r(``,``,e.public,t=>{e.public=t,b(e,t)});s.className=`flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,s.append(t(`span`,``,`Public`));let c=d(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100 pointer-coarse:opacity-100`);c.title=`Renames the folder on disk; nothing is erased`,c.onclick=()=>void x(e.slug);let l=f(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100 pointer-coarse:opacity-100`,()=>`${location.origin}${p(e)}`);l.title=`Copy the board's link`,a.append(t(`div`,`ml-auto flex flex-none items-center gap-2`,s,l,c)),n.append(a);let u=t(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);u.append(t(`span`,`min-w-0 break-all font-mono text-neutral-400`,e.slug)),e.description&&u.append(t(`span`,`min-w-0 truncate`,e.description)),u.append(t(`span`,`flex-none text-neutral-400`,o(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=d(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>h(t),u.append(e)}if(e.public&&e.token){let n=t(`span`,`min-w-0 break-all font-mono text-neutral-400`,p(e));n.title=`Public URL — anyone holding this link can read the board, no password`,u.append(n)}return n.append(u),n}let C=[{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 w(){let e=t(`div`,`mx-auto flex w-full max-w-3xl flex-col gap-4`);g.length===0&&e.append(a(`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:n,hint:r,wanted:i}of C){let a=g.filter(e=>e.public===i);a.length!==0&&e.append(u(n,r,t(`div`,`flex flex-col divide-y divide-neutral-200/70`,...a.map(S))))}y.replaceChildren(..._?[t(`p`,`mb-3 text-[13px] text-red-600`,_)]:[],e)}async function T(){let e=await l(`/api/boards`,`Could not load boards`);if(!e.ok)return _=e.error,w();g=e.value,w(),_=``}let E=i(m,()=>void T());return window.addEventListener(`focus`,()=>{E.visible&&T()}),E}export{m as createBoardsView};
@@ -0,0 +1,4 @@
1
+ import{$ as e,B as t,H as n,J as r,L as i,R as a,U as o,a as s,c,d as l,et as u,f as d,i as f,it as p,o as m,p as h}from"./index-Dqdb-Eqt.js";var g=[[`path`,{d:`M12 5v14`}],[`path`,{d:`m19 12-7 7-7-7`}]],ee=[[`path`,{d:`m5 12 7-7 7 7`}],[`path`,{d:`M12 19V5`}]],te=[[`path`,{d:`M12 22v-6`}],[`path`,{d:`M12 8V2`}],[`path`,{d:`M4 12H2`}],[`path`,{d:`M10 12H8`}],[`path`,{d:`M16 12h-2`}],[`path`,{d:`M22 12h-2`}],[`path`,{d:`m15 19-3-3-3 3`}],[`path`,{d:`m15 5-3 3-3-3`}]],ne=[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`}]],re=[[`path`,{d:`M15 6a9 9 0 0 0-9 9V3`}],[`circle`,{cx:`18`,cy:`6`,r:`3`}],[`circle`,{cx:`6`,cy:`18`,r:`3`}]],_=[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`}],[`path`,{d:`M3 3v5h5`}]],v=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())}`},y=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?v(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
2
+
3
+ `),b=null;function x(){b&&(b.remove(),b=null,document.removeEventListener(`pointerdown`,x,!0),window.removeEventListener(`scroll`,x,!0))}function ie(e,t){e.onmouseenter=()=>{x();let r=t();if(!r)return;b=n(`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`),b.textContent=r,document.body.append(b),document.addEventListener(`pointerdown`,x,!0),window.addEventListener(`scroll`,x,!0);let i=e.getBoundingClientRect();b.style.left=`${Math.max(8,Math.min(i.left,window.innerWidth-b.offsetWidth-8))}px`,b.style.top=i.bottom+6+b.offsetHeight>window.innerHeight?`${i.top-b.offsetHeight-6}px`:`${i.bottom+6}px`},e.onmouseleave=x}function S(t,r,i){let a=n(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,e(p,`h-3 w-3 text-indigo-600 ${t.value===r?``:`invisible`}`),n(`span`,`inline-flex flex-none items-center gap-1.5 font-mono text-[11.5px] text-neutral-800`,t.label??t.value));return t.subject&&a.append(n(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,t.subject)),t.author&&a.append(n(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${t.author} · ${t.at?o(t.at):``}`)),ie(a,()=>y(t)),a.onclick=()=>i(t.value),a}function ae(t,r,i,a,o){let{base:c,head:l}=i,u=()=>l&&c===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=r.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...r.refs.map(e=>({value:e.name,subject:e.subject})),...r.commits.map(e=>({...e,value:e.hash}))],h=n(`div`,`flex w-[38rem] max-w-full flex-col`);function g(){let t=(e,t)=>{let r=n(`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 r.onclick=()=>{d=t,g()},r},i=(e,t,r,i)=>n(`div`,`flex min-w-0 flex-1 flex-col`,n(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),n(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>S(e,r,i)))),f=d===`compare`?n(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,i(`base`,p(!1),c,e=>{c=e,g(),a(c,l)}),i(`to`,p(!0),l,e=>{l=e,g(),a(c,l)})):n(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...r.commits.map(e=>S({value:e.hash,...e},u(),e=>{c=`${e}~1`,l=e,s(),a(c,l)}))),m=n(`div`,`flex-none border-t border-neutral-200`,S({value:o,label:`${o} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},c===o&&l===``?o:``,()=>{c=o,l=``,g(),a(c,l)}));m.querySelector(`button > span`).prepend(e(_)),h.replaceChildren(n(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,t(`Compare two points`,`compare`),t(`One commit`,`commit`)),f,...d===`compare`?[m]:[])}g(),m(t,h)}var oe=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,se={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},ce=30,C=`pier.filesPrefs`,w=50,T=()=>{try{let e=JSON.parse(localStorage.getItem(C)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},E=e=>{let t=e?T()[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},le=(e,t)=>{let n=T();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-w)))delete n[e];try{localStorage.setItem(C,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},D=(e,t=`indigo`)=>{let r=n(`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 r.type=`button`,r};function O(o,s,p,m){let _=``,v=``,b={branch:null,refs:[],commits:[],worktrees:[]},x=`HEAD`,S=``,C=new Map,w=!0,T=!1,O=[],k=new Set,A=null,j=null,ue=n(`header`,`pagehead`),M=n(`div`,`flex-none border-b border-neutral-200`),N=n(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),de=n(`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`,M,N),P=n(`div`,`h-full min-w-0 overflow-auto`),F=n(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),fe=n(`section`,`relative min-h-0 min-w-0 flex-1`,P,F);o.append(ue,n(`div`,`flex min-h-0 flex-1 max-md:flex-col`,de,fe));let I=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:_,...t})}`,L=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:_,...t})}`,R=(e,t=`text-neutral-400`)=>n(`p`,`px-4 py-3 text-[12.5px] ${t}`,e),z=e=>{let t=0;for(let n of C.keys())n.startsWith(`${e}/`)&&t++;return t};function B(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of C){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&&b.branch&&(i=t.filter(e=>e.dir?z(`${r}${e.name}`)>0:C.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?pe(`${r}${e.name}`,e.name):me(`${r}${e.name}`,e.name))}async function V(e,t){try{let{entries:n}=await r(L(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...B(t,n)),e.childElementCount||e.append(R(w?`No changes.`:`Empty.`))}catch(n){let r=B(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(R(String(n),`text-red-600`))}}function pe(e,r){let i=z(e),a=[n(`span`,`truncate`,r)];i&&a.push(n(`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}=t(``,a);s.className+=` px-2 py-0.5 hover:bg-neutral-100`;let c=n(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(R(`…`)),V(c,e))};return o.ontoggle=()=>{k[o.open?`add`:`delete`](e),o.open&&u()},(w&&b.branch&&!T||k.has(e))&&(o.open=!0,u()),o}function H(e){j?.classList.remove(`bg-indigo-50`,`text-indigo-700`),j=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function me(e,t){let r=C.get(e)?.status,i=n(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,n(`span`,`truncate`,t));return r&&i.append(n(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${se[r]??`text-neutral-500`}`,r)),i.title=e,e===A&&H(i),i.onclick=()=>{H(i),A=e,_e(e)},i}let U=()=>void V(N,``),W=(e,t)=>[...e?[n(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[n(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function G(e,t,r){let a=C.get(e),o=n(`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`,n(`span`,`truncate`,e));a&&(o.append(n(`span`,`flex-none font-semibold ${se[a.status]??``}`,a.status)),o.append(...W(a.add,a.del)));let s=n(`div`,`ml-auto flex flex-none items-center gap-2`);if(r&&s.append(r),t){let t=document.createElement(`a`);t.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,t.href=L(`file`,{path:e}),t.download=i(e),t.textContent=`Download`,s.append(t)}return o.append(s),o}function he(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 K=we();function q(){F.classList.add(`hidden`),F.replaceChildren(),K.set([],[])}let ge=0,J=e=>e===ge;function _e(e){let t=++ge;return C.has(e)?Te(e,t):ve(e,t)}async function ve(e,t){if(!J(t))return;q();let r=L(`file`,{path:e}),i=n(`div`,`min-w-0`);if(P.classList.remove(`flex`,`flex-col`),P.replaceChildren(G(e,!0),i),oe.test(e)){let e=n(`img`,`max-w-full p-4`);e.src=r,e.onerror=()=>i.replaceChildren(R(`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,P.replaceChildren(G(e,!0),t),P.classList.add(`flex`,`flex-col`);return}i.append(R(`…`));let a=await fetch(r);if(!J(t))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(R(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(R(`Binary file — use Download.`));return}let o=d(await a.text());if(!J(t))return;let s=await h(e);J(t)&&i.replaceChildren(l(o,s))}function ye(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 be(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 xe={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function Se(e,t,r){F.replaceChildren(...e.map(e=>{let i=n(`button`,`pointer-events-auto absolute w-full cursor-pointer ${xe[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 ${Ce(r,e.start)}`,i.onclick=()=>r[e.start].scrollIntoView({block:`center`}),i})),F.classList.remove(`hidden`)}let Ce=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function we(){let t=[],r=[],i=-1,a=n(`span`,`flex-none text-[10.5px] text-neutral-400`),s=e=>{t.length&&(i=(i+e+t.length)%t.length,r[t[i].start].scrollIntoView({block:`center`}),a.textContent=`${i+1}/${t.length}`)},l=()=>t.length>0&&!o.classList.contains(`hidden`),u=(t,r,i,a)=>{let o=n(`button`,`icon-btn flex-none`,e(t));return o.onclick=()=>s(r),c(o,a,i,()=>s(r),l),o};return{el:n(`div`,`flex flex-none items-center gap-1`,a,u(ee,-1,`Previous change`,[`p`,`k`]),u(g,1,`Next change`,[`n`,`j`])),set(e,n){t=e,r=n,i=-1,a.textContent=`${e.length} change${e.length===1?``:`s`}`}}}async function Te(e,t){q(),P.classList.remove(`flex`,`flex-col`);let n=C.get(e)?.status!==`D`;P.replaceChildren(G(e,n),R(`…`));try{let{diff:i}=await r(I(`diff`,{base:x,head:S,file:e,context:`99999`}),`could not read this diff`);if(!J(t))return;if(!i||/^Binary files /m.test(i))return ve(e,t);let a=ye(i);he(a);let o=await h(e);if(!J(t))return;let s=l(a,o),c=[...s.children],u=be(a);K.set(u,c),P.replaceChildren(G(e,n,K.el),s),Se(u,a.length,c)}catch(n){J(t)&&P.replaceChildren(G(e,!1),R(String(n),`text-red-600`))}}let Y=()=>S&&x===`${S}~1`?S:``,Ee=()=>b.commits.find(e=>e.hash===Y())?.subject??``,De=()=>Y()?`${Y()} — ${Ee()||`commit`}`:`${x} ↔ ${S||`working tree`}`,Oe=(t,r,i,a)=>{let o=n(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${i?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return o.append(e(t)),o.title=r,o.setAttribute(`aria-label`,r),o.setAttribute(`aria-pressed`,String(i)),o.onclick=a,o};function X(){let e=Oe(ne,`Show only files that differ`,w,()=>{w=!w,X(),U()}),t=Oe(te,T?`Restore folders`:`Collapse all folders`,T,()=>{if(T=!T,T)O=[...k],k.clear();else for(let e of O)k.add(e);X(),U()});if(!b.branch){M.replaceChildren(n(`div`,`flex items-center justify-end gap-1 p-1.5`,t));return}let r=D(De());if(Y()){let e=b.commits.find(e=>e.hash===Y());ie(r,()=>e?y(e):``)}else r.title=`Choose what to diff`;r.onclick=async()=>{await Me(),ae(r,b,{base:x,head:S},(e,t)=>{x=e,S=t,Z()},$())};let[i,a]=[0,0];for(let e of C.values())i+=e.add,a+=e.del;M.replaceChildren(n(`div`,`flex flex-col gap-1 p-2`,r,n(`div`,`flex items-center gap-1.5 pl-1`,n(`span`,`text-[11px] text-neutral-400`,`${C.size} changed file${C.size===1?``:`s`}`),...W(i,a),n(`span`,`ml-auto`),e,t)))}let ke=()=>{v&&_&&le(v,{cwd:_,base:x,head:S})},Ae=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||b.refs.some(e=>e.name===t)||b.commits.some(e=>e.hash===t)};async function je(){if(C=new Map,!b.branch)return;let{files:e}=await r(I(`diff`,{base:x,head:S}),`could not read what changed`);for(let t of e)C.set(t.path,{status:t.status,add:t.add,del:t.del});if(C.size<=ce)for(let e of C.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)k.add(t.slice(0,e).join(`/`))}}async function Z(){ke();try{await je()}catch(e){X(),N.replaceChildren(R(String(e),`text-red-600`));return}X(),U(),A&&_e(A)}function Q(){let t=D(_||`Choose a folder…`,`neutral`);t.className+=` max-w-72`,t.title=`Switch folder`,t.onclick=()=>f(t,b.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),_||void 0,p);let r=n(`button`,`icon-btn`,e(u));r.type=`button`,r.title=`Close Files`,r.setAttribute(`aria-label`,`Close Files`),r.onclick=m,r.classList.add(`ml-auto`),ue.replaceChildren(t,n(`span`,`flex min-w-0 items-center gap-1 truncate font-mono text-[11.5px] text-neutral-400`,...b.branch?[e(re),n(`span`,`truncate`,b.branch)]:[`no git`]),r)}async function Me(){if(_)try{b=await r(I(`git`,{}),`could not read this repository`),Q()}catch(e){P.replaceChildren(R(String(e),`text-red-600`))}}let $=()=>b.refs.some(e=>e.name===`main`)?`main`:b.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ne(){if(Q(),A=null,j=null,k.clear(),q(),P.classList.remove(`flex`,`flex-col`),P.replaceChildren(R(`Select a file.`)),!_){M.replaceChildren(),N.replaceChildren(),P.replaceChildren(R(`No folder yet — pick one from the chip above.`));return}try{b=await r(I(`git`,{}),`could not read this repository`)}catch(e){b={branch:null,refs:[],commits:[],worktrees:[]},P.replaceChildren(R(String(e),`text-red-600`))}let e=E(v),t=e&&e.cwd===_&&Ae(e.base)&&Ae(e.head)?e:null;x=t?.base??$(),S=t?.head??``,Q(),await Z()}return a(o,e=>{let t=s(),n=t?.id??``,r=e?.startsWith(`/`)?e:(n===v?_:``)||E(n)?.cwd||t?.cwd||``;if(r===_&&n===v&&N.childElementCount){Q(),Me().then(Z);return}v=n,_=r,Ne()},()=>{q(),P.classList.remove(`flex`,`flex-col`),P.replaceChildren(R(`Select a file.`))})}export{O as createExplorerView};