@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,4 +1,5 @@
|
|
|
1
1
|
// Credential-blind projection of the portable agent configuration.
|
|
2
|
+
import { isThinkingLevel } from "../core/types.js";
|
|
2
3
|
/** Never leaves the instance, at any depth of a models.json provider: the
|
|
3
4
|
* credentials, and the endpoint they authenticate against — a sharing link is
|
|
4
5
|
* public. Every other field is metadata Pi's own schema already validates, so
|
|
@@ -52,20 +53,30 @@ function project(value, where, onPrivate) {
|
|
|
52
53
|
return [[key, project(entry, `${where} ${key}`, onPrivate)]];
|
|
53
54
|
}));
|
|
54
55
|
}
|
|
56
|
+
/** A provider id that cannot address anything but its own record. */
|
|
57
|
+
function providerId(id) {
|
|
58
|
+
if (typeof id !== "string" || id.length > 100 || !/^[a-z0-9][a-z0-9._-]*$/.test(id) ||
|
|
59
|
+
["constructor", "prototype"].includes(id)) {
|
|
60
|
+
throw new Error("invalid snapshot provider id");
|
|
61
|
+
}
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
/** A model id both sides can address — in a catalog or as the default. */
|
|
65
|
+
function modelId(id) {
|
|
66
|
+
if (typeof id !== "string" || !id.trim() || id !== id.trim() || id.length > 100) {
|
|
67
|
+
throw new Error("model id must be a non-empty trimmed string");
|
|
68
|
+
}
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
55
71
|
/** The shape both sides must agree on: a provider id that cannot address
|
|
56
72
|
* anything but its own record, and a catalog addressable by model id. */
|
|
57
73
|
function portable(id, value, onPrivate) {
|
|
58
|
-
|
|
59
|
-
throw new Error("invalid snapshot provider id");
|
|
60
|
-
}
|
|
74
|
+
providerId(id);
|
|
61
75
|
const provider = record(project(value, `provider ${id}`, onPrivate), `provider ${id}`);
|
|
62
76
|
const models = provider.models ?? [];
|
|
63
77
|
if (!Array.isArray(models))
|
|
64
78
|
throw new Error("models must be an array");
|
|
65
|
-
const ids = models.map((model) => record(model, "model").id);
|
|
66
|
-
if (ids.some((modelId) => typeof modelId !== "string" || !modelId.trim() || modelId !== modelId.trim())) {
|
|
67
|
-
throw new Error("model id must be a non-empty trimmed string");
|
|
68
|
-
}
|
|
79
|
+
const ids = models.map((model) => modelId(record(model, "model").id));
|
|
69
80
|
if (new Set(ids).size !== ids.length)
|
|
70
81
|
throw new Error("duplicate model ids");
|
|
71
82
|
const overrides = provider.modelOverrides ?? {};
|
|
@@ -91,11 +102,28 @@ export function snapshotProviders(providers = {}) {
|
|
|
91
102
|
}
|
|
92
103
|
return shared;
|
|
93
104
|
}
|
|
105
|
+
/** A default model names a provider and a model; the provider is not required
|
|
106
|
+
* to be in models.json, because Pi's own built-ins never are. */
|
|
107
|
+
function normalizeDefaultModel(raw) {
|
|
108
|
+
if (raw === null)
|
|
109
|
+
return null;
|
|
110
|
+
const ref = record(raw, "default model");
|
|
111
|
+
only(ref, ["provider", "id"], "default model");
|
|
112
|
+
return { provider: providerId(ref.provider), id: modelId(ref.id) };
|
|
113
|
+
}
|
|
114
|
+
/** A default reasoning effort is one of the levels Pi accepts, nothing else. */
|
|
115
|
+
function normalizeDefaultThinkingLevel(raw) {
|
|
116
|
+
if (raw === null)
|
|
117
|
+
return null;
|
|
118
|
+
if (!isThinkingLevel(raw))
|
|
119
|
+
throw new Error("default reasoning effort must be a level Pi accepts");
|
|
120
|
+
return raw;
|
|
121
|
+
}
|
|
94
122
|
/** Strict import boundary: a source that states a private field is refused,
|
|
95
123
|
* never trusted as a mask over the local one. */
|
|
96
124
|
export function normalizeAgentSnapshot(raw) {
|
|
97
125
|
const snapshot = record(raw, "agent snapshot");
|
|
98
|
-
only(snapshot, ["files", "providers"], "agent snapshot");
|
|
126
|
+
only(snapshot, ["files", "providers", "defaultModel", "defaultThinkingLevel"], "agent snapshot");
|
|
99
127
|
const files = record(snapshot.files, "snapshot files");
|
|
100
128
|
only(files, ["SYSTEM.md", "AGENTS.md"], "snapshot files");
|
|
101
129
|
for (const name of ["SYSTEM.md", "AGENTS.md"]) {
|
|
@@ -110,6 +138,9 @@ export function normalizeAgentSnapshot(raw) {
|
|
|
110
138
|
return {
|
|
111
139
|
files: { "SYSTEM.md": files["SYSTEM.md"], "AGENTS.md": files["AGENTS.md"] },
|
|
112
140
|
providers,
|
|
141
|
+
...(snapshot.defaultModel !== undefined ? { defaultModel: normalizeDefaultModel(snapshot.defaultModel) } : {}),
|
|
142
|
+
...(snapshot.defaultThinkingLevel !== undefined
|
|
143
|
+
? { defaultThinkingLevel: normalizeDefaultThinkingLevel(snapshot.defaultThinkingLevel) } : {}),
|
|
113
144
|
};
|
|
114
145
|
}
|
|
115
146
|
/** A model or override arrives whole; only the local private fields survive it. */
|
package/dist/agent/config.js
CHANGED
|
@@ -9,7 +9,10 @@ import { pierPath } from "../paths.js";
|
|
|
9
9
|
import { mergeSnapshotProviders, normalizeAgentSnapshot, snapshotProviders } from "./config-sync.js";
|
|
10
10
|
const GLOBAL_FILES = ["SYSTEM.md", "AGENTS.md", "settings.json", "models.json"];
|
|
11
11
|
const PROJECT_FILES = ["AGENTS.md"];
|
|
12
|
-
|
|
12
|
+
// settings.json is on the list for two fields: the default model and its
|
|
13
|
+
// reasoning effort. Everything else in it is machine-local and survives an
|
|
14
|
+
// import untouched.
|
|
15
|
+
const SNAPSHOT_FILES = ["SYSTEM.md", "AGENTS.md", "models.json", "settings.json"];
|
|
13
16
|
const RESOURCE_DEPTH = 3; // extensions/skills nest at most a couple of levels
|
|
14
17
|
/** Pier owns the Pi runtime dir: config lives in the syncable `~/.pier/pi`
|
|
15
18
|
* repo, not `~/.pi`. main.ts exports this as PI_CODING_AGENT_DIR so the SDK's
|
|
@@ -211,13 +214,18 @@ export class PiConfigStore {
|
|
|
211
214
|
}
|
|
212
215
|
exportSnapshot() {
|
|
213
216
|
return this.#withWrite(async () => {
|
|
214
|
-
const [system, agents, raw] = await Promise.all(SNAPSHOT_FILES.map((name) => readNullable(join(this.agentDir, name))));
|
|
217
|
+
const [system, agents, raw, rawSettings] = await Promise.all(SNAPSHOT_FILES.map((name) => readNullable(join(this.agentDir, name))));
|
|
215
218
|
const parsed = raw?.trim() ? parseModels(raw) : {};
|
|
216
219
|
if (!parsed)
|
|
217
220
|
throw new Error("models.json must be valid JSON before exporting configuration");
|
|
221
|
+
const settings = readSettings(rawSettings);
|
|
218
222
|
return normalizeAgentSnapshot({
|
|
219
223
|
files: { "SYSTEM.md": system, "AGENTS.md": agents },
|
|
220
224
|
providers: snapshotProviders(parsed.providers),
|
|
225
|
+
defaultModel: defaultModelRef(settings),
|
|
226
|
+
// Unlike the pair, a lone level needs no reading of its own; the
|
|
227
|
+
// snapshot boundary is the one place that says which ones exist.
|
|
228
|
+
defaultThinkingLevel: settings.defaultThinkingLevel ?? null,
|
|
221
229
|
});
|
|
222
230
|
});
|
|
223
231
|
}
|
|
@@ -233,7 +241,8 @@ export class PiConfigStore {
|
|
|
233
241
|
const providers = mergeSnapshotProviders(parsed.providers, incoming.providers);
|
|
234
242
|
const models = JSON.stringify(providers) === JSON.stringify(parsed.providers ?? {})
|
|
235
243
|
? raw : `${JSON.stringify({ ...parsed, providers }, null, 2)}\n`;
|
|
236
|
-
const
|
|
244
|
+
const settings = withDefaults(before[3], incoming);
|
|
245
|
+
const after = [incoming.files["SYSTEM.md"], incoming.files["AGENTS.md"], models, settings];
|
|
237
246
|
const changes = names.flatMap((name, index) => before[index] === after[index] ? [] : [{
|
|
238
247
|
path: join(this.agentDir, name), before: before[index], after: after[index],
|
|
239
248
|
temp: join(this.agentDir, `${name}.${process.pid}.${randomUUID()}.tmp`),
|
|
@@ -450,21 +459,69 @@ function restoreHeaders(incoming, current) {
|
|
|
450
459
|
headers[name] = original;
|
|
451
460
|
}
|
|
452
461
|
}
|
|
453
|
-
|
|
462
|
+
/** settings.json is read for the deployment defaults and never rewritten
|
|
463
|
+
* wholesale. Malformed JSON is refused rather than read as "no default":
|
|
464
|
+
* that would publish, or import, a cleared default nobody asked for. */
|
|
465
|
+
function readSettings(raw) {
|
|
466
|
+
if (!raw?.trim())
|
|
467
|
+
return {};
|
|
468
|
+
const parsed = parseObject(raw);
|
|
469
|
+
if (!parsed)
|
|
470
|
+
throw new Error("settings.json must be valid JSON before synchronizing configuration");
|
|
471
|
+
return parsed;
|
|
472
|
+
}
|
|
473
|
+
/** Pi writes defaultProvider and defaultModel as a pair; half of one is a hand
|
|
474
|
+
* edit that a shared default cannot represent, so it is refused. */
|
|
475
|
+
function defaultModelRef(settings) {
|
|
476
|
+
const provider = settings.defaultProvider;
|
|
477
|
+
const id = settings.defaultModel;
|
|
478
|
+
if (provider === undefined && id === undefined)
|
|
479
|
+
return null;
|
|
480
|
+
if (typeof provider !== "string" || typeof id !== "string") {
|
|
481
|
+
throw new Error("settings.json must name defaultProvider and defaultModel together as strings");
|
|
482
|
+
}
|
|
483
|
+
return { provider, id };
|
|
484
|
+
}
|
|
485
|
+
/** The incoming defaults replace their own fields and nothing else; a field
|
|
486
|
+
* the source never states (an older one) leaves the local settings.json
|
|
487
|
+
* alone, and `null` is a stated "no default" that clears it. Unchanged
|
|
488
|
+
* content returns the original bytes so the import stays a no-op. */
|
|
489
|
+
function withDefaults(raw, incoming) {
|
|
490
|
+
if (incoming.defaultModel === undefined && incoming.defaultThinkingLevel === undefined)
|
|
491
|
+
return raw;
|
|
492
|
+
const settings = readSettings(raw);
|
|
493
|
+
const next = { ...settings };
|
|
494
|
+
const set = (key, value) => {
|
|
495
|
+
if (value === undefined)
|
|
496
|
+
delete next[key];
|
|
497
|
+
else
|
|
498
|
+
next[key] = value;
|
|
499
|
+
};
|
|
500
|
+
if (incoming.defaultModel !== undefined) {
|
|
501
|
+
set("defaultProvider", incoming.defaultModel?.provider);
|
|
502
|
+
set("defaultModel", incoming.defaultModel?.id);
|
|
503
|
+
}
|
|
504
|
+
if (incoming.defaultThinkingLevel !== undefined) {
|
|
505
|
+
set("defaultThinkingLevel", incoming.defaultThinkingLevel ?? undefined);
|
|
506
|
+
}
|
|
507
|
+
return JSON.stringify(next) === JSON.stringify(settings) ? raw : `${JSON.stringify(next, null, 2)}\n`;
|
|
508
|
+
}
|
|
509
|
+
const parseObject = (raw) => {
|
|
454
510
|
try {
|
|
455
|
-
|
|
456
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
457
|
-
return null;
|
|
458
|
-
const providers = parsed.providers;
|
|
459
|
-
if (providers !== undefined) {
|
|
460
|
-
if (typeof providers !== "object" || providers === null || Array.isArray(providers))
|
|
461
|
-
return null;
|
|
462
|
-
if (Object.values(providers).some((provider) => typeof provider !== "object" || provider === null || Array.isArray(provider)))
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
return parsed;
|
|
511
|
+
return asRecord(JSON.parse(raw)) ?? null;
|
|
466
512
|
}
|
|
467
513
|
catch {
|
|
468
514
|
return null;
|
|
469
515
|
}
|
|
516
|
+
};
|
|
517
|
+
function parseModels(raw) {
|
|
518
|
+
const parsed = parseObject(raw);
|
|
519
|
+
if (!parsed)
|
|
520
|
+
return null;
|
|
521
|
+
if (parsed.providers !== undefined) {
|
|
522
|
+
const providers = asRecord(parsed.providers);
|
|
523
|
+
if (!providers || Object.values(providers).some((provider) => !asRecord(provider)))
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
return parsed;
|
|
470
527
|
}
|
package/dist/agent/events.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// and Pi types never leak past the seam. The golden-table test in
|
|
4
4
|
// events.test.ts is the mapping's spec; extend types.ts before adding events.
|
|
5
5
|
import { isThinkingLevel, MAX_STEP_OUTPUT } from "../core/types.js";
|
|
6
|
+
const hasToolCalls = (message) => Array.isArray(message?.content) && message.content.some((part) => part.type === "toolCall");
|
|
6
7
|
export function textOf(content) {
|
|
7
8
|
if (typeof content === "string")
|
|
8
9
|
return content;
|
|
@@ -109,6 +110,7 @@ export function toChatTurns(messages) {
|
|
|
109
110
|
const turns = [];
|
|
110
111
|
let steps = []; // activity seen since the last emitted turn
|
|
111
112
|
const pendingTools = new Map();
|
|
113
|
+
let candidate; // text-only reply, until another assistant message follows
|
|
112
114
|
const flush = (role, text, meta, origin, at) => {
|
|
113
115
|
const turn = { role, text };
|
|
114
116
|
if (meta)
|
|
@@ -142,6 +144,7 @@ export function toChatTurns(messages) {
|
|
|
142
144
|
}
|
|
143
145
|
const origin = systemOrigin(m);
|
|
144
146
|
if (origin) {
|
|
147
|
+
candidate = undefined;
|
|
145
148
|
const text = textOf(m.content);
|
|
146
149
|
if (text)
|
|
147
150
|
flush("system", text, undefined, origin, m.timestamp);
|
|
@@ -149,9 +152,22 @@ export function toChatTurns(messages) {
|
|
|
149
152
|
}
|
|
150
153
|
if (m.role !== "user" && m.role !== "assistant")
|
|
151
154
|
continue;
|
|
155
|
+
if (m.role === "user")
|
|
156
|
+
candidate = undefined;
|
|
157
|
+
else if (candidate) {
|
|
158
|
+
// Some providers separate commentary from the following tool-call message.
|
|
159
|
+
turns.pop();
|
|
160
|
+
steps = candidate.steps ?? [];
|
|
161
|
+
steps.push({ kind: "progress", text: candidate.text });
|
|
162
|
+
candidate = undefined;
|
|
163
|
+
}
|
|
164
|
+
const hasTools = m.role === "assistant" && hasToolCalls(m);
|
|
152
165
|
if (m.role === "assistant" && Array.isArray(m.content)) {
|
|
153
166
|
for (const part of m.content) {
|
|
154
|
-
if (part.type === "
|
|
167
|
+
if (hasTools && part.type === "text" && part.text) {
|
|
168
|
+
steps.push({ kind: "progress", text: part.text });
|
|
169
|
+
}
|
|
170
|
+
else if (part.type === "thinking" && part.thinking) {
|
|
155
171
|
steps.push({ kind: "thinking", text: part.thinking });
|
|
156
172
|
}
|
|
157
173
|
else if (part.type === "toolCall") {
|
|
@@ -168,10 +184,12 @@ export function toChatTurns(messages) {
|
|
|
168
184
|
}
|
|
169
185
|
}
|
|
170
186
|
const text = textOf(m.content);
|
|
171
|
-
//
|
|
172
|
-
if (!text)
|
|
187
|
+
// Tool-bearing messages are intermediate work, even when they include text.
|
|
188
|
+
if (!text || hasTools)
|
|
173
189
|
continue;
|
|
174
190
|
flush(m.role, text, m.role === "assistant" ? turnMetaAt(messages, i) : undefined, undefined, m.timestamp);
|
|
191
|
+
if (m.role === "assistant")
|
|
192
|
+
candidate = turns[turns.length - 1];
|
|
175
193
|
}
|
|
176
194
|
// Activity with no answer after it (aborted run) still belongs on the page.
|
|
177
195
|
if (steps.length)
|
|
@@ -199,7 +217,7 @@ export function toSessionEvents(e) {
|
|
|
199
217
|
? final.errorMessage || "unknown agent error"
|
|
200
218
|
: undefined;
|
|
201
219
|
const out = [
|
|
202
|
-
{ type: "turn-end", text: textOf(final?.content), ...(failure ? { error: failure } : {}) },
|
|
220
|
+
{ type: "turn-end", text: hasToolCalls(final) ? "" : textOf(final?.content), ...(failure ? { error: failure } : {}) },
|
|
203
221
|
];
|
|
204
222
|
if (failure)
|
|
205
223
|
out.push({ type: "error", message: failure });
|
|
@@ -221,6 +239,8 @@ export function toSessionEvents(e) {
|
|
|
221
239
|
const m = e.message;
|
|
222
240
|
if (!m)
|
|
223
241
|
return [];
|
|
242
|
+
if (m.role === "assistant")
|
|
243
|
+
return [{ type: "text-start" }];
|
|
224
244
|
const origin = systemOrigin(m);
|
|
225
245
|
const text = textOf(m.content);
|
|
226
246
|
if (origin)
|
package/dist/core/identity.js
CHANGED
|
@@ -90,6 +90,12 @@ export function splitSpeaker(text) {
|
|
|
90
90
|
text: text.slice(m[0].length),
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
/** Trailing lines that open a `[name](file://` link — or a last line that is
|
|
94
|
+
* only `[name` because the clip fell inside it. */
|
|
95
|
+
const ATTACHMENT_LINES = /(?:\n\[[^\]\n]*(?:\]\(\s*<?file:\/\/[^\n]*|$))+$/;
|
|
96
|
+
/** A long code span that opens the message, when something else follows it.
|
|
97
|
+
* Long, because `npm test` before "fails" is the subject, not a paste. */
|
|
98
|
+
const LEADING_CODE_SPAN = /^\s*`[^`\n]{40,}`\s+(\S[\s\S]*)$/;
|
|
93
99
|
/**
|
|
94
100
|
* A session titled by its first prompt inherits that prompt's header, and the
|
|
95
101
|
* header is for the model: anything a person reads — a list row, a session
|
|
@@ -97,6 +103,11 @@ export function splitSpeaker(text) {
|
|
|
97
103
|
* the workbench ever opened. So the speaker comes off and what they said is
|
|
98
104
|
* the title. Here rather than in a UI module because the push notification
|
|
99
105
|
* needs the same answer and a second copy of this would drift (AGENTS.md §3).
|
|
106
|
+
*
|
|
107
|
+
* Two more things a first prompt carries that a title should not: the
|
|
108
|
+
* attachment lines a channel appended (`[name](file:///…)`, often cut mid-name
|
|
109
|
+
* by the title clip), and a pasted log line in backticks ahead of the actual
|
|
110
|
+
* question — dropped only when a question follows; a paste alone stays.
|
|
100
111
|
*/
|
|
101
112
|
export function readableTitle(title) {
|
|
102
113
|
if (!title)
|
|
@@ -106,5 +117,10 @@ export function readableTitle(title) {
|
|
|
106
117
|
// change what the sidebar's search is matching against for nothing.
|
|
107
118
|
if (text === title)
|
|
108
119
|
return title;
|
|
109
|
-
|
|
120
|
+
const said = text
|
|
121
|
+
.replace(ATTACHMENT_LINES, "")
|
|
122
|
+
.replace(LEADING_CODE_SPAN, "$1")
|
|
123
|
+
.replace(/\s+/g, " ")
|
|
124
|
+
.trim();
|
|
125
|
+
return said || title;
|
|
110
126
|
}
|
package/dist/core/router.js
CHANGED
|
@@ -78,6 +78,7 @@ export class Router {
|
|
|
78
78
|
* the process exits when the drain ends — `endDrain` exists for the one
|
|
79
79
|
* caller that drains *speculatively* and may not get to exit. */
|
|
80
80
|
draining = false;
|
|
81
|
+
spokenTo;
|
|
81
82
|
constructor(hub,
|
|
82
83
|
/** Create or resume the session owning a conversation (wired in main.ts). */
|
|
83
84
|
resolve) {
|
|
@@ -87,6 +88,14 @@ export class Router {
|
|
|
87
88
|
registerChannel(channel) {
|
|
88
89
|
this.channels.set(channel.id, channel);
|
|
89
90
|
}
|
|
91
|
+
/** Told which session a human just spoke to — every message from a chat or
|
|
92
|
+
* the workbench passes through `dispatch`, and nothing a task or a subagent
|
|
93
|
+
* starts does. Registered rather than a constructor argument: the one
|
|
94
|
+
* listener is the rail's working set (web/session-state.ts), built with the
|
|
95
|
+
* web surface long after the router. */
|
|
96
|
+
onSpokenTo(listener) {
|
|
97
|
+
this.spokenTo = listener;
|
|
98
|
+
}
|
|
90
99
|
/**
|
|
91
100
|
* Tell the conversation something went wrong, then the event stream.
|
|
92
101
|
*
|
|
@@ -602,6 +611,7 @@ export class Router {
|
|
|
602
611
|
// closed must not start the turn the drain just declared finished with.
|
|
603
612
|
if (this.draining)
|
|
604
613
|
this.refuseDraining(msg.key);
|
|
614
|
+
this.spokenTo?.(session.id);
|
|
605
615
|
const { action, text } = decide(msg, session.state);
|
|
606
616
|
// A group chat is many people talking into one session; without a speaker
|
|
607
617
|
// line the agent cannot tell them apart or mention anyone back. Emitted
|
package/dist/db.js
CHANGED
|
@@ -322,6 +322,33 @@ const MIGRATIONS = [
|
|
|
322
322
|
-- all of them: the query is by the delegating session, which lived only in
|
|
323
323
|
-- the JSON — without this every session open was a full scan of task_runs.
|
|
324
324
|
CREATE INDEX task_runs_invoked_by ON task_runs(json_extract(json, '$.invokedBySessionId'), queued_at DESC);
|
|
325
|
+
`,
|
|
326
|
+
// 19 — the rail is one flat list, and pinned means "stuck to the top".
|
|
327
|
+
`
|
|
328
|
+
-- pinned used to mean "listed under Projects", and every session created in
|
|
329
|
+
-- the workbench was. Now it means on top of the list, and nobody put a
|
|
330
|
+
-- historical session there: kept as-is, every web session ever made would
|
|
331
|
+
-- land in the pinned section. project_sort stays as a column nothing reads
|
|
332
|
+
-- — a SQLite column drop rewrites the table for a NULL nobody pays for.
|
|
333
|
+
UPDATE session_state SET pinned = 0;
|
|
334
|
+
`,
|
|
335
|
+
// 20 — the top of the rail is maintained, not arranged: no pin, no drag.
|
|
336
|
+
`
|
|
337
|
+
-- sort was the place a hand dragged a pinned row to; it is now the rank in
|
|
338
|
+
-- the working set the rail keeps on top, which a session enters by being
|
|
339
|
+
-- spoken to and leaves by being pushed out of the last slot. The pinned rows
|
|
340
|
+
-- are that set's first members — they are what somebody was working on — in
|
|
341
|
+
-- the order they were arranged in; never-dragged ones sorted first, so -1 is
|
|
342
|
+
-- the rank that keeps them there.
|
|
343
|
+
UPDATE session_state SET sort = -1 WHERE pinned = 1 AND sort IS NULL;
|
|
344
|
+
UPDATE session_state SET sort = NULL WHERE pinned = 0;
|
|
345
|
+
-- The set has a size (web/session-state.ts); more pins than that is a list
|
|
346
|
+
-- the promotion rule would never have built.
|
|
347
|
+
UPDATE session_state SET sort = NULL WHERE session_id IN (
|
|
348
|
+
SELECT session_id FROM session_state WHERE sort IS NOT NULL
|
|
349
|
+
ORDER BY sort, session_id LIMIT -1 OFFSET 8
|
|
350
|
+
);
|
|
351
|
+
ALTER TABLE session_state DROP COLUMN pinned;
|
|
325
352
|
`,
|
|
326
353
|
];
|
|
327
354
|
/**
|
package/dist/main.js
CHANGED
|
@@ -382,6 +382,7 @@ app.route("/", createServer({
|
|
|
382
382
|
reload: () => reloadInstance(true),
|
|
383
383
|
backgroundRuns: (id) => tasks.backgroundRuns(id),
|
|
384
384
|
activeBackgroundRunCounts: () => tasks.activeBackgroundRunCounts(),
|
|
385
|
+
taskSessions: () => tasks.taskSessions(),
|
|
385
386
|
channelOf: (id) => conversations.channelOf(id),
|
|
386
387
|
}));
|
|
387
388
|
const port = Number(process.env.PORT ?? 3141);
|
package/dist/settings.js
CHANGED
|
@@ -113,7 +113,10 @@ export class SettingsStore {
|
|
|
113
113
|
autoUpdate: this.#value("autoUpdate") === "1",
|
|
114
114
|
extensions: this.#json("extensions", normalizeExtensions, "a list of names") ?? [],
|
|
115
115
|
tools: this.#json("tools", normalizeTools, "a list of names") ?? [],
|
|
116
|
-
|
|
116
|
+
// `"drop"`: a stored row whose name the bundled catalog has since taken
|
|
117
|
+
// is redundant, not malformed — rejecting the setting over it would take
|
|
118
|
+
// every other tool declared beside it (see normalizeCustomTools).
|
|
119
|
+
customTools: this.#json("customTools", (raw) => normalizeCustomTools(raw, [], "drop"), "a list of {name, spec}") ?? [],
|
|
117
120
|
};
|
|
118
121
|
}
|
|
119
122
|
/**
|
package/dist/tasks/service.js
CHANGED
|
@@ -217,6 +217,10 @@ export class TaskService {
|
|
|
217
217
|
activeBackgroundRunCounts() {
|
|
218
218
|
return this.store.countActiveBackgroundRunsBySession();
|
|
219
219
|
}
|
|
220
|
+
/** The sessions runs made for themselves, for a list of the operator's own. */
|
|
221
|
+
taskSessions() {
|
|
222
|
+
return this.store.taskOwnedSessionIds();
|
|
223
|
+
}
|
|
220
224
|
run(taskId, input = null, source = "manual", parentRunId = null, provenance = {}) {
|
|
221
225
|
this.refusePaused(parentRunId);
|
|
222
226
|
const task = this.get(taskId);
|
package/dist/tasks/store.js
CHANGED
|
@@ -143,6 +143,18 @@ export class TaskStore {
|
|
|
143
143
|
`).all();
|
|
144
144
|
return new Map(rows.map((row) => [row.session_id, row.n]));
|
|
145
145
|
}
|
|
146
|
+
/** Every session a run created for itself — `fresh` is the one mode that
|
|
147
|
+
* makes a session rather than borrowing one, and a resumed run reuses the
|
|
148
|
+
* session its `fresh` predecessor already stamped. These are the agent's
|
|
149
|
+
* conversations with itself, which the rail does not list. */
|
|
150
|
+
taskOwnedSessionIds() {
|
|
151
|
+
const rows = this.sql(`
|
|
152
|
+
SELECT DISTINCT json_extract(json, '$.context.sessionId') AS id
|
|
153
|
+
FROM task_runs
|
|
154
|
+
WHERE json_extract(json, '$.sessionMode') = 'fresh' AND id IS NOT NULL
|
|
155
|
+
`).all();
|
|
156
|
+
return new Set(rows.map((row) => row.id));
|
|
157
|
+
}
|
|
146
158
|
listRunsForSession(sessionId, limit = 50) {
|
|
147
159
|
return this.#many(`
|
|
148
160
|
SELECT json FROM task_runs
|
package/dist/tools.js
CHANGED
|
@@ -127,7 +127,18 @@ export const CUSTOM_TOOL_RULES = `each custom tool needs a name (letters, digits
|
|
|
127
127
|
export function normalizeCustomTools(raw,
|
|
128
128
|
/** Names this instance already answers to that this file cannot see — the
|
|
129
129
|
* bundled extensions live behind the Pi SDK, so main.ts hands them in. */
|
|
130
|
-
reserved = []
|
|
130
|
+
reserved = [],
|
|
131
|
+
/**
|
|
132
|
+
* What a name Pier already manages means. At the route: a refusal, because
|
|
133
|
+
* the operator is declaring it now and can pick another. Reading a stored
|
|
134
|
+
* row: `"drop"`, because the catalog grew into that name *after* the row was
|
|
135
|
+
* written — jq shipped as a bundled tool and turned the operator's own jq
|
|
136
|
+
* row into a whole setting Pier refused, dropping every *other* tool
|
|
137
|
+
* declared beside it. The bundled row installs the same binary, so the
|
|
138
|
+
* entry is redundant rather than wrong. Left in the row on purpose: a Pier
|
|
139
|
+
* that stops bundling that name finds the declaration still there.
|
|
140
|
+
*/
|
|
141
|
+
managedName = "reject") {
|
|
131
142
|
// Case-insensitively: two names differing only in case are one filename on a
|
|
132
143
|
// case-insensitive filesystem, and one switch whose meaning depends on which
|
|
133
144
|
// ran last.
|
|
@@ -145,8 +156,11 @@ reserved = []) {
|
|
|
145
156
|
const cleanName = name.trim();
|
|
146
157
|
if (!TOOL_NAME.test(cleanName))
|
|
147
158
|
return null;
|
|
148
|
-
if (taken.has(cleanName.toLowerCase()))
|
|
149
|
-
|
|
159
|
+
if (taken.has(cleanName.toLowerCase())) {
|
|
160
|
+
if (managedName === "reject")
|
|
161
|
+
return null;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
150
164
|
if (tools.some((tool) => tool.name.toLowerCase() === cleanName.toLowerCase()))
|
|
151
165
|
return null;
|
|
152
166
|
// The migration: a stored `{name, spec}` is the block it always meant.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{
|
|
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>
|
|
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
|
|
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};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{F as e,G as t,H as n,K as r,P as i,R as a,U as o,g as s,k as c,z as l}from"./index-uFsZkKOQ.js";var u=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function d(d,f){let p=[],m=``,h=a(`header`,`sticky top-0 z-30 flex h-10 items-center gap-3 border-b border-neutral-200 bg-white px-4`,a(`span`,`font-medium max-md:hidden`,`Boards`)),g=a(`div`,`px-4 py-5`);d.append(a(`div`,`min-h-0 flex-1 overflow-y-auto`,h,g));async function _(e,t){let i=await r(`/api/boards/${e.slug}`,{public:t},`PATCH`);if(i.ok)return e.token=(await i.json().catch(()=>({}))).token??``,x();m=await n(i,`Could not change ${e.slug}`),await S()}async function v(e){m=await t(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await S()}function y(t){let n=a(`div`,`group flex flex-col gap-1 border-b border-neutral-200/70 px-1 py-2.5 last:border-b-0`),r=document.createElement(`a`);r.className=`truncate font-medium text-neutral-800 hover:text-indigo-700`,r.href=u(t),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=t.title;let i=a(`div`,`flex items-center gap-2`,r,a(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,t.slug)),o=c(``,``,t.public,e=>{t.public=e,_(t,e)});o.className=`ml-auto flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,o.append(a(`span`,``,`Public`));let d=s(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100`);d.title=`Renames the folder on disk; nothing is erased`,d.onclick=()=>void v(t.slug);let p=e(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100`,()=>`${location.origin}${u(t)}`);p.title=`Copy the board's link`,i.append(o,p,d),n.append(i);let m=a(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);t.description&&m.append(a(`span`,`min-w-0 truncate`,t.description)),m.append(a(`span`,`flex-none text-neutral-400`,l(Date.parse(t.updatedAt))));for(let e of t.sessions){let t=s(e.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);t.title=`Open session ${e}`,t.onclick=()=>f(e),m.append(t)}if(t.public&&t.token){let e=a(`span`,`flex-none font-mono text-neutral-400`,u(t));e.title=`Public URL — anyone holding this link can read the board, no password`,m.append(e)}return n.append(m),n}let b=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Reachable from the Console only.`,wanted:!1}];function x(){let e=a(`div`,`mx-auto flex max-w-3xl flex-col`);p.length===0&&e.append(a(`p`,`rounded-xl border border-dashed border-neutral-300 px-4 py-6 text-center text-[12.5px] text-neutral-500`,`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:n,wanted:r}of b){let i=p.filter(e=>e.public===r);if(i.length===0)continue;let o=a(`section`,`mb-6`);o.append(a(`h2`,`text-[13px] font-semibold text-neutral-700`,t)),o.append(a(`p`,`mb-1 text-[11.5px] leading-snug text-neutral-500`,n));let s=a(`div`,`rounded-xl border border-neutral-200 bg-white px-3`);s.append(...i.map(y)),o.append(s),e.append(o)}g.replaceChildren(...m?[a(`p`,`mb-3 text-[13px] text-red-600`,m)]:[],e)}async function S(){let e=await o(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=i(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{I as e,N as t,P as n,R as r,W as i,a,d as o,f as s,i as c,n as l,s as u,u as d,z as f}from"./index-uFsZkKOQ.js";var p=e=>{let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`},m=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?p(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
|
|
2
|
+
|
|
3
|
+
`),h=null;function g(){h&&(h.remove(),h=null,document.removeEventListener(`pointerdown`,g,!0),window.removeEventListener(`scroll`,g,!0))}function ee(e,t){e.onmouseenter=()=>{g();let n=t();if(!n)return;h=r(`div`,`pointer-events-none fixed z-50 max-w-96 whitespace-pre-wrap rounded-lg bg-neutral-800/95 px-3 py-2 font-sans text-[11.5px] leading-snug text-neutral-100 shadow-lg`),h.textContent=n,document.body.append(h),document.addEventListener(`pointerdown`,g,!0),window.addEventListener(`scroll`,g,!0);let i=e.getBoundingClientRect();h.style.left=`${Math.max(8,Math.min(i.left,window.innerWidth-h.offsetWidth-8))}px`,h.style.top=i.bottom+6+h.offsetHeight>window.innerHeight?`${i.top-h.offsetHeight-6}px`:`${i.bottom+6}px`},e.onmouseleave=g}function _(e,t,n){let i=r(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,r(`span`,`w-3 flex-none text-indigo-600`,e.value===t?`✓`:``),r(`span`,`flex-none font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&i.append(r(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&i.append(r(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?f(e.at):``}`)),ee(i,()=>m(e)),i.onclick=()=>n(e.value),i}function te(e,t,n,i,o){let{base:s,head:l}=n,u=()=>l&&s===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=t.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...t.refs.map(e=>({value:e.name,subject:e.subject})),...t.commits.map(e=>({...e,value:e.hash}))],m=r(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,t)=>{let n=r(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===t?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return n.onclick=()=>{d=t,h()},n},n=(e,t,n,i)=>r(`div`,`flex min-w-0 flex-1 flex-col`,r(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),r(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>_(e,n,i)))),a=d===`compare`?r(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,n(`base`,p(!1),s,e=>{s=e,h(),i(s,l)}),n(`to`,p(!0),l,e=>{l=e,h(),i(s,l)})):r(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...t.commits.map(e=>_({value:e.hash,...e},u(),e=>{s=`${e}~1`,l=e,c(),i(s,l)}))),f=r(`div`,`flex-none border-t border-neutral-200`,_({value:o,label:`↺ ${o} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},s===o&&l===``?o:``,()=>{s=o,l=``,h(),i(s,l)}));m.replaceChildren(r(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,e(`Compare two points`,`compare`),e(`One commit`,`commit`)),a,...d===`compare`?[f]:[])}h(),a(e,m)}var ne=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,v={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},re=30,y=`pier.filesPrefs`,b=50,x=()=>{try{let e=JSON.parse(localStorage.getItem(y)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},S=e=>{let t=e?x()[e]:void 0;return typeof t?.cwd==`string`&&typeof t.base==`string`&&typeof t.head==`string`?{cwd:t.cwd,base:t.base,head:t.head}:null},ie=(e,t)=>{let n=x();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-b)))delete n[e];try{localStorage.setItem(y,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},ae=(e,t=`indigo`)=>{let n=r(`button`,`min-w-0 cursor-pointer truncate rounded-md px-2 py-0.5 text-left font-mono text-[11.5px] ${t===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`,e);return n.type=`button`,n};function C(a,c,f,p){let h=``,g=``,_={branch:null,refs:[],commits:[],worktrees:[]},y=`HEAD`,b=``,x=new Map,C=!0,w=!1,T=[],E=new Set,D=null,O=null,k=r(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),A=r(`div`,`flex-none border-b border-neutral-200`),j=r(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),oe=r(`aside`,`flex w-64 flex-none flex-col border-r border-neutral-200 text-[12.5px] max-md:h-2/5 max-md:w-full max-md:border-b max-md:border-r-0`,A,j),M=r(`div`,`h-full min-w-0 overflow-auto`),N=r(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),se=r(`section`,`relative min-h-0 min-w-0 flex-1`,M,N);a.append(k,r(`div`,`flex min-h-0 flex-1 max-md:flex-col`,oe,se));let P=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:h,...t})}`,F=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:h,...t})}`,I=(e,t=`text-neutral-400`)=>r(`p`,`px-4 py-3 text-[12.5px] ${t}`,e),L=e=>{let t=0;for(let n of x.keys())n.startsWith(`${e}/`)&&t++;return t};function R(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of x){if(i.status!==`D`||!e.startsWith(r))continue;let a=e.slice(r.length).split(`/`)[0];n.has(a)||(n.add(a),t.push({name:a,dir:e.slice(r.length).includes(`/`)}))}let i=t;return C&&_.branch&&(i=t.filter(e=>e.dir?L(`${r}${e.name}`)>0:x.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?ce(`${r}${e.name}`,e.name):le(`${r}${e.name}`,e.name))}async function z(e,t){try{let{entries:n}=await i(F(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...R(t,n)),e.childElementCount||e.append(I(C?`No changes.`:`Empty.`))}catch(n){let r=R(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(I(String(n),`text-red-600`))}}function ce(t,n){let i=L(t),a=[r(`span`,`truncate`,n)];i&&a.push(r(`span`,`ml-auto flex-none rounded bg-amber-100/80 px-1 font-mono text-[10px] text-amber-700`,String(i)));let{el:o,summary:s}=e(``,a);s.className+=` px-2 py-0.5 hover:bg-neutral-100`;let c=r(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(I(`…`)),z(c,t))};return o.ontoggle=()=>{E[o.open?`add`:`delete`](t),o.open&&u()},(C&&_.branch&&!w||E.has(t))&&(o.open=!0,u()),o}function B(e){O?.classList.remove(`bg-indigo-50`,`text-indigo-700`),O=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function le(e,t){let n=x.get(e)?.status,i=r(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,r(`span`,`truncate`,t));return n&&i.append(r(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${v[n]??`text-neutral-500`}`,n)),i.title=e,e===D&&B(i),i.onclick=()=>{B(i),D=e,de(e)},i}let V=()=>void z(j,``),H=(e,t)=>[...e?[r(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[r(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function U(e,n,i){let a=x.get(e),o=r(`div`,`sticky top-0 z-10 flex items-center gap-2 border-b border-neutral-200 bg-white px-4 py-1.5 font-mono text-[11.5px] text-neutral-500`,r(`span`,`truncate`,e));a&&(o.append(r(`span`,`flex-none font-semibold ${v[a.status]??``}`,a.status)),o.append(...H(a.add,a.del)));let s=r(`div`,`ml-auto flex flex-none items-center gap-2`);if(i&&s.append(i),n){let n=document.createElement(`a`);n.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,n.href=F(`file`,{path:e}),n.download=t(e),n.textContent=`Download`,s.append(n)}return o.append(s),o}function ue(e){let t=0;for(;t<e.length;){if(e[t].tone!==`del`){t++;continue}let n=t;for(;t<e.length&&e[t].tone===`del`;)t++;let r=t;for(;t<e.length&&e[t].tone===`add`;)t++;let i=Math.min(r-n,t-r);for(let t=0;t<i;t++){let i=e[n+t],a=e[r+t],o=Math.min(i.text.length,a.text.length),s=0;for(;s<o&&i.text[s]===a.text[s];)s++;let c=0;for(;c<o-s&&i.text[i.text.length-1-c]===a.text[a.text.length-1-c];)c++;s+c!==0&&(i.mark=[s,i.text.length-c],a.mark=[s,a.text.length-c])}}}let W=ve();function G(){N.classList.add(`hidden`),N.replaceChildren(),W.set([],[])}let K=0,q=e=>e===K;function de(e){let t=++K;return x.has(e)?ye(e,t):fe(e,t)}async function fe(e,t){if(!q(t))return;G();let n=F(`file`,{path:e}),i=r(`div`,`min-w-0`);if(M.classList.remove(`flex`,`flex-col`),M.replaceChildren(U(e,!0),i),ne.test(e)){let e=r(`img`,`max-w-full p-4`);e.src=n,e.onerror=()=>i.replaceChildren(I(`Could not load image.`,`text-red-600`)),i.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let t=document.createElement(`iframe`);t.className=`h-full w-full`,t.src=n,M.replaceChildren(U(e,!0),t),M.classList.add(`flex`,`flex-col`);return}i.append(I(`…`));let a=await fetch(n);if(!q(t))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(I(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(I(`Binary file — use Download.`));return}let c=o(await a.text());if(!q(t))return;let l=await s(e);q(t)&&i.replaceChildren(d(c,l))}function pe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
4
|
+
`);a.at(-1)===``&&a.pop();for(let e of a){let a=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(e);if(a){i&&t.push({nums:[``,``],text:`⋯`,tone:``}),[n,r]=[Number(a[1]),Number(a[2])],i=!0;continue}!i||e.startsWith(`\\`)||(e.startsWith(`+`)?t.push({nums:[``,r++],text:e.slice(1),tone:`add`}):e.startsWith(`-`)?t.push({nums:[n++,``],text:e.slice(1),tone:`del`}):t.push({nums:[n++,r++],text:e.slice(1),tone:``}))}return t}function me(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n].tone;if(!r)continue;let i=t.at(-1);i&&i.end===n-1?(i.end=n,(r===`add`&&i.tone===`del`||r===`del`&&i.tone===`add`)&&(i.tone=`mixed`)):t.push({start:n,end:n,tone:r})}return t}let he={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function ge(e,t,n){N.replaceChildren(...e.map(e=>{let i=r(`button`,`pointer-events-auto absolute w-full cursor-pointer ${he[e.tone]}`);return i.style.top=`${e.start/t*100}%`,i.style.height=`${Math.max(.4,(e.end-e.start+1)/t*100)}%`,i.title=`line ${_e(n,e.start)}`,i.onclick=()=>n[e.start].scrollIntoView({block:`center`}),i})),N.classList.remove(`hidden`)}let _e=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function ve(){let e=[],t=[],n=-1,i=r(`span`,`flex-none text-[10.5px] text-neutral-400`),o=r=>{e.length&&(n=(n+r+e.length)%e.length,t[e[n].start].scrollIntoView({block:`center`}),i.textContent=`${n+1}/${e.length}`)},s=()=>e.length>0&&!a.classList.contains(`hidden`),c=(e,t,n,i)=>{let a=r(`button`,`icon-btn flex-none`,e);return a.onclick=()=>o(t),u(a,i,n,()=>o(t),s),a};return{el:r(`div`,`flex flex-none items-center gap-1`,i,c(`↑`,-1,`Previous change`,[`p`,`k`]),c(`↓`,1,`Next change`,[`n`,`j`])),set(r,a){e=r,t=a,n=-1,i.textContent=`${r.length} change${r.length===1?``:`s`}`}}}async function ye(e,t){G(),M.classList.remove(`flex`,`flex-col`);let n=x.get(e)?.status!==`D`;M.replaceChildren(U(e,n),I(`…`));try{let{diff:r}=await i(P(`diff`,{base:y,head:b,file:e,context:`99999`}),`could not read this diff`);if(!q(t))return;if(!r||/^Binary files /m.test(r))return fe(e,t);let a=pe(r);ue(a);let o=await s(e);if(!q(t))return;let c=d(a,o),l=[...c.children],u=me(a);W.set(u,l),M.replaceChildren(U(e,n,W.el),c),ge(u,a.length,l)}catch(n){q(t)&&M.replaceChildren(U(e,!1),I(String(n),`text-red-600`))}}let J=()=>b&&y===`${b}~1`?b:``,be=()=>_.commits.find(e=>e.hash===J())?.subject??``,xe=()=>J()?`${J()} — ${be()||`commit`}`:`${y} ↔ ${b||`working tree`}`,Se=(e,t,n,i)=>{let a=r(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${n?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return a.innerHTML=e,a.title=t,a.onclick=i,a};function Y(){let e=Se(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="M2.5 3h11l-4.25 5v4.5l-2.5 1.25V8L2.5 3z" stroke-linejoin="round"/></svg>`,`Show only files that differ`,C,()=>{C=!C,Y(),V()}),t=Se(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="m4.5 2.5 3.5 3 3.5-3M4.5 13.5l3.5-3 3.5 3" stroke-linecap="round" stroke-linejoin="round"/></svg>`,w?`Restore folders`:`Collapse all folders`,w,()=>{if(w=!w,w)T=[...E],E.clear();else for(let e of T)E.add(e);Y(),V()});if(!_.branch){A.replaceChildren(r(`div`,`flex items-center justify-end gap-1 p-1.5`,t));return}let n=ae(xe());if(J()){let e=_.commits.find(e=>e.hash===J());ee(n,()=>e?m(e):``)}else n.title=`Choose what to diff`;n.onclick=async()=>{await Te(),te(n,_,{base:y,head:b},(e,t)=>{y=e,b=t,Z()},$())};let[i,a]=[0,0];for(let e of x.values())i+=e.add,a+=e.del;A.replaceChildren(r(`div`,`flex flex-col gap-1 p-2`,n,r(`div`,`flex items-center gap-1.5 pl-1`,r(`span`,`text-[11px] text-neutral-400`,`${x.size} changed file${x.size===1?``:`s`}`),...H(i,a),r(`span`,`ml-auto`),e,t)))}let Ce=()=>{g&&h&&ie(g,{cwd:h,base:y,head:b})},X=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||_.refs.some(e=>e.name===t)||_.commits.some(e=>e.hash===t)};async function we(){if(x=new Map,!_.branch)return;let{files:e}=await i(P(`diff`,{base:y,head:b}),`could not read what changed`);for(let t of e)x.set(t.path,{status:t.status,add:t.add,del:t.del});if(x.size<=re)for(let e of x.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)E.add(t.slice(0,e).join(`/`))}}async function Z(){Ce();try{await we()}catch(e){Y(),j.replaceChildren(I(String(e),`text-red-600`));return}Y(),V(),D&&de(D)}function Q(){let e=ae(h||`Choose a folder…`,`neutral`);e.className+=` max-w-72`,e.title=`Switch folder`,e.onclick=()=>l(e,_.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),h||void 0,f);let t=r(`button`,`icon-btn`,`✕`);t.type=`button`,t.title=`Close Files`,t.setAttribute(`aria-label`,`Close Files`),t.onclick=p,t.classList.add(`ml-auto`),k.replaceChildren(e,r(`span`,`truncate font-mono text-[11.5px] text-neutral-400`,_.branch?`⎇ ${_.branch}`:`no git`),t)}async function Te(){if(h)try{_=await i(P(`git`,{}),`could not read this repository`),Q()}catch(e){M.replaceChildren(I(String(e),`text-red-600`))}}let $=()=>_.refs.some(e=>e.name===`main`)?`main`:_.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ee(){if(Q(),D=null,O=null,E.clear(),G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`)),!h){A.replaceChildren(),j.replaceChildren(),M.replaceChildren(I(`No folder yet — pick one from the chip above.`));return}try{_=await i(P(`git`,{}),`could not read this repository`)}catch(e){_={branch:null,refs:[],commits:[],worktrees:[]},M.replaceChildren(I(String(e),`text-red-600`))}let e=S(g),t=e&&e.cwd===h&&X(e.base)&&X(e.head)?e:null;y=t?.base??$(),b=t?.head??``,Q(),await Z()}return n(a,e=>{let t=c(),n=t?.id??``,r=e?.startsWith(`/`)?e:(n===g?h:``)||S(n)?.cwd||t?.cwd||``;if(r===h&&n===g&&j.childElementCount){Q(),Te().then(Z);return}g=n,h=r,Ee()},()=>{G(),M.classList.remove(`flex`,`flex-col`),M.replaceChildren(I(`Select a file.`))})}export{C as createExplorerView};
|
|
Binary file
|
|
Binary file
|