diff options
| author | Igor Soarez <igor@soarez.org> | 2026-10-28 10:05:45 +0000 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-10-28 10:05:45 +0000 |
| commit | a130fd7689e86c5420a139f334e34fbe605c4441 (patch) | |
| tree | da5ac5a846d3062990d2b61dc1603754fc443442 | |
| parent | 6daa4893f73cd25496e42433a64f0a5b21ef2aed (diff) | |
pirc-extension 0.1.9-dev+1d13db331154
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | pirc.js | 376 |
2 files changed, 189 insertions, 189 deletions
diff --git a/package.json b/package.json index 0658af2..51fda36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pirc-extension", - "version": "0.1.9-dev+05e39e0be370", + "version": "0.1.9-dev+1d13db331154", "description": "PiRC: puts every interactive pi session on a PiRC relay", "license": "MIT", "homepage": "https://pirc.dev", @@ -1,4 +1,4 @@ -// PiRC extension 0.1.9-dev+05e39e0be370. Third-party code in this file: @bufbuild/protobuf@2.16.0, ws@8.22.0; see NOTICE. +// PiRC extension 0.1.9-dev+1d13db331154. Third-party code in this file: @bufbuild/protobuf@2.16.0, ws@8.22.0; see NOTICE. import { createRequire as __pircCreateRequire } from "node:module"; const require = __pircCreateRequire(import.meta.url); var __create = Object.create; @@ -7152,6 +7152,37 @@ var ConversationItemSchema = /* @__PURE__ */ messageDesc(file_pirc_v1alpha1_sess var ContentBlockSchema = /* @__PURE__ */ messageDesc(file_pirc_v1alpha1_session, 34); var UsageSchema = /* @__PURE__ */ messageDesc(file_pirc_v1alpha1_session, 35); +// protocol/helpers.ts +function errorReply(requestId, code, message) { + return create(SessionMessageSchema, { body: { case: "error", value: { requestId, code, message } } }); +} +function accepted(requestId) { + return create(SessionMessageSchema, { body: { case: "accepted", value: { requestId } } }); +} +function requestIdOf(body) { + return body?.value?.requestId ?? ""; +} +var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join(""); +function sameBytes(a, b) { + return a.length === b.length && a.every((x, i) => x === b[i]); +} +var THINKING_NAMES = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; +var LEVEL = { + off: 1 /* OFF */, + minimal: 2 /* MINIMAL */, + low: 3 /* LOW */, + medium: 4 /* MEDIUM */, + high: 5 /* HIGH */, + xhigh: 6 /* XHIGH */, + max: 7 /* MAX */ +}; +function thinkingLevel(name) { + return name !== void 0 && Object.hasOwn(LEVEL, name) ? LEVEL[name] : void 0; +} +function thinkingName(level) { + return THINKING_NAMES.find((n) => LEVEL[n] === level); +} + // relay-client/keys.ts import { createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign } from "node:crypto"; import { linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; @@ -7460,21 +7491,6 @@ function clip(s, max = MAX_TEXT) { return s.length <= max ? s : s.slice(0, max) + ` [\u2026 ${s.length - max} more characters not sent]`; } -var THINKING = { - off: 1 /* OFF */, - minimal: 2 /* MINIMAL */, - low: 3 /* LOW */, - medium: 4 /* MEDIUM */, - high: 5 /* HIGH */, - xhigh: 6 /* XHIGH */, - max: 7 /* MAX */ -}; -function thinkingLevel(level) { - return level && THINKING[level] || 0 /* UNSPECIFIED */; -} -function thinkingLevelName(level) { - return Object.keys(THINKING).find((k) => THINKING[k] === level); -} function modelRef(m) { return create(ModelSchema, { provider: m.provider, id: m.id, name: m.name ?? "" }); } @@ -7600,16 +7616,19 @@ function historyItems(branch) { if (e.message.role === "user") turnId = e.id; items.push(...messageItems(e.message, { entryId: e.id, turnId, at: atIso(e.timestamp) })); } else if (e.type === "compaction") { - items.push(create(ConversationItemSchema, { - entryId: e.id, - turnId, - at: atIso(e.timestamp), - body: { case: "compaction", value: { summary: clip(e.summary), tokensBefore: count(e.tokensBefore) } } - })); + items.push(compactionItem(e, turnId)); } } return items; } +function compactionItem(e, turnId) { + return create(ConversationItemSchema, { + entryId: e.id, + turnId, + at: atIso(e.timestamp), + body: { case: "compaction", value: { summary: clip(e.summary), tokensBefore: count(e.tokensBefore) } } + }); +} function lastTurnId(branch) { for (let i = branch.length - 1; i >= 0; i--) { const e = branch[i]; @@ -7681,11 +7700,52 @@ var PROMPT_ECHO_MS = 10 * 6e4; var CONFIRM_MS = 3e4; var PERSIST_MS = 2e3; var SHOWN = /* @__PURE__ */ new Set(["user", "assistant", "toolResult"]); -var NEW_SESSION_COMMAND = "pirc:new"; -var FORK_COMMAND = "pirc:fork"; -var CLONE_COMMAND = "pirc:clone"; -var RESUME_COMMAND = "pirc:resume"; -var RELOAD_COMMAND = "pirc:reload"; +var cancelled = (what) => (r) => r.cancelled ? `another extension cancelled the ${what}` : void 0; +var SESSION_COMMANDS = { + new: { + name: "pirc:new", + description: "PiRC: start a new session (used by PiRC's clients)", + reason: "new", + timeout: "pi did not start a new session", + run: async (_args, ctx) => cancelled("new session")(await ctx.newSession()) + }, + // Before a user message, its entry ID the argument. + fork: { + name: "pirc:fork", + description: "PiRC: fork before a user message (used by PiRC's clients)", + reason: "fork", + timeout: "pi did not fork the session", + run: async (id, ctx) => forkProblem(ctx, id, "before") ?? cancelled("fork")(await ctx.fork(id)) + }, + clone: { + name: "pirc:clone", + description: "PiRC: clone the session as it is (used by PiRC's clients)", + reason: "fork", + timeout: "pi did not clone the session", + run: async (_args, ctx) => { + const leaf = ctx.sessionManager.getLeafId() ?? ""; + return forkProblem(ctx, leaf, "at") ?? cancelled("clone")(await ctx.fork(leaf, { position: "at" })); + } + }, + // A saved session, its file the argument. + resume: { + name: "pirc:resume", + description: "PiRC: resume a saved session (used by PiRC's clients)", + reason: "resume", + timeout: "pi did not resume the session", + run: async (path, ctx) => await resumeProblem(ctx, path) ?? cancelled("resume")(await ctx.switchSession(path)) + }, + reload: { + name: "pirc:reload", + description: "PiRC: reload pi's resources (used by PiRC's clients)", + reason: "reload", + timeout: "pi did not reload", + run: async (_args, ctx) => { + await ctx.reload(); + return void 0; + } + } +}; function forkProblem(ctx, id, position) { const sm = ctx.sessionManager; const e = id ? sm.getEntry(id) : void 0; @@ -7751,8 +7811,12 @@ var ACTION_FOR = { compact: "compact", session: "stats" }; +function commandName(text) { + return text.startsWith("/") ? text.slice(1).split(/\s/, 1)[0] : void 0; +} +var isPircCommand = (name) => name === "pirc" || name.startsWith("pirc:"); function refusedCommand(name) { - if (name === "pirc" || name.startsWith("pirc:")) return `/${name} is PiRC's own command; it runs only in pi's terminal`; + if (isPircCommand(name)) return `/${name} is PiRC's own command; it runs only in pi's terminal`; if (!BUILTIN_COMMANDS.has(name)) return void 0; const action = ACTION_FOR[name]; return `pi's /${name} runs only in its terminal` + (action ? `; use the ${action} action` : ""); @@ -7828,7 +7892,7 @@ var Live = class { cwd: this.ctx.cwd, hostname: hostname2(), model: model && modelRef(model), - thinkingLevel: thinkingLevel(this.pi.getThinkingLevel()), + thinkingLevel: thinkingLevel(this.pi.getThinkingLevel()) ?? 0 /* UNSPECIFIED */, working: this.working, compacting: this.compacting, startedAt: started ? atIso(started) : void 0, @@ -8046,12 +8110,7 @@ var Live = class { } onCompact(entry) { this.catchUp(); - this.emitItem(create(ConversationItemSchema, { - entryId: entry.id, - turnId: this.turnId, - at: atIso(entry.timestamp), - body: { case: "compaction", value: { summary: clip(entry.summary), tokensBefore: count(entry.tokensBefore) } } - })); + this.emitItem(compactionItem(entry, this.turnId)); this.onCompactEnd(); } // --- Requests from clients ------------------------------------------ @@ -8088,8 +8147,8 @@ var Live = class { // Whether text runs an extension command, which pi handles at once, even // mid-run, and which starts no run. isCommand(text) { - if (!text.startsWith("/")) return false; - const name = text.slice(1).split(/\s/, 1)[0]; + const name = commandName(text); + if (name === void 0) return false; return this.pi.getCommands().some((c) => c.source === "extension" && c.name === name); } // Why compacting or replacing the session cannot start now, if so. @@ -8100,9 +8159,22 @@ var Live = class { if (this.working || !this.ctx.isIdle()) return "the agent is working; stop it first"; return void 0; } + // Runs then with what a promise of pi's gives, unless the session has + // ended by then; a rejection is answered PI_FAILED. + whenSettled(requestId, where, p, respond, then) { + p.then( + (v) => { + if (this.disposed) return respond(errorReply(requestId, 7 /* SESSION_GONE */, "the session ended")); + this.guard(where, () => then(v))(); + }, + (e) => respond(errorReply(requestId, 13 /* PI_FAILED */, errorText(e))) + ); + } // Replaces the session through one of PiRC's commands, answered when pi - // shuts this session down for `reason` (onShutdown). - replace(requestId, reason, command, timeout, respond) { + // shuts this session down for its reason (onShutdown). + replace(requestId, which, args, respond) { + const { name, reason, timeout } = SESSION_COMMANDS[which]; + const command = args ? `/${name} ${args}` : `/${name}`; const why = this.busy(); if (why) return respond(errorReply(requestId, 8 /* BUSY */, why)); const timer = setTimeout(this.guard("replacement", () => { @@ -8114,17 +8186,18 @@ var Live = class { try { this.pi.sendUserMessage(command, { deliverAs: "followUp", expandPromptTemplates: true }); } catch (e) { - this.onReplaceFailed(e?.message ?? String(e)); + this.onReplaceFailed(errorText(e)); } } // Handles one request, answering it through respond, now or later. - onRequest(clientKey, m, respond) { + onRequest(m, respond) { const body = m.body; switch (body.case) { case "prompt": { const { requestId, text, images, delivery } = body.value; if (!text && images.length === 0) return respond(errorReply(requestId, 4 /* INVALID */, "empty prompt")); - const refused = text.startsWith("/") ? refusedCommand(text.slice(1).split(/\s/, 1)[0]) : void 0; + const name = commandName(text); + const refused = name === void 0 ? void 0 : refusedCommand(name); if (refused) return respond(errorReply(requestId, 4 /* INVALID */, refused)); const command = images.length === 0 && this.isCommand(text); if (!command && this.compacting) { @@ -8169,23 +8242,15 @@ var Live = class { if (!model?.provider || !model.id) return respond(errorReply(requestId, 4 /* INVALID */, "no model given")); const found = this.ctx.modelRegistry.find(model.provider, model.id); if (!found) return respond(errorReply(requestId, 4 /* INVALID */, `pi has no model ${model.provider}/${model.id}`)); - const fail = (why) => respond(errorReply(requestId, 13 /* PI_FAILED */, why)); - this.pi.setModel(found).then( - (ok) => { - if (this.disposed) return respond(errorReply(requestId, 7 /* SESSION_GONE */, "the session ended")); - if (!ok) return fail(`pi has no credentials for ${model.provider}`); - this.guard("set model", () => { - this.onAnnouncementChanged(); - respond(accepted(requestId)); - })(); - }, - (e) => fail(e?.message ?? String(e)) - ); - return; + return this.whenSettled(requestId, "set model", this.pi.setModel(found), respond, (ok) => { + if (!ok) return respond(errorReply(requestId, 13 /* PI_FAILED */, `pi has no credentials for ${model.provider}`)); + this.onAnnouncementChanged(); + respond(accepted(requestId)); + }); } case "setThinkingLevel": { const { requestId, level } = body.value; - const name = thinkingLevelName(level); + const name = thinkingName(level); if (!name) return respond(errorReply(requestId, 4 /* INVALID */, "no thinking level given")); this.pi.setThinkingLevel(name); this.onAnnouncementChanged(); @@ -8221,46 +8286,34 @@ var Live = class { return; } case "newSession": - return this.replace(body.value.requestId, "new", `/${NEW_SESSION_COMMAND}`, "pi did not start a new session", respond); + return this.replace(body.value.requestId, "new", "", respond); case "fork": { const { requestId, entryId } = body.value; const problem = forkProblem(this.ctx, entryId, "before"); if (problem) return respond(errorReply(requestId, 4 /* INVALID */, problem)); - return this.replace(requestId, "fork", `/${FORK_COMMAND} ${entryId}`, "pi did not fork the session", respond); + return this.replace(requestId, "fork", entryId, respond); } case "clone": { const { requestId } = body.value; const problem = forkProblem(this.ctx, this.ctx.sessionManager.getLeafId() ?? "", "at"); if (problem) return respond(errorReply(requestId, 4 /* INVALID */, problem)); - return this.replace(requestId, "fork", `/${CLONE_COMMAND}`, "pi did not clone the session", respond); + return this.replace(requestId, "clone", "", respond); } case "listPastSessions": { const { requestId, allFolders } = body.value; - pastSessions(this.ctx, allFolders).then( - (all) => { - if (this.disposed) return respond(errorReply(requestId, 7 /* SESSION_GONE */, "the session ended")); - this.guard("past sessions", () => respond(this.pastSessions(requestId, all)))(); - }, - (e) => respond(errorReply(requestId, 13 /* PI_FAILED */, e?.message ?? String(e))) - ); - return; + return this.whenSettled(requestId, "past sessions", pastSessions(this.ctx, allFolders), respond, (all) => respond(this.pastSessions(requestId, all))); } case "resume": { const { requestId, path } = body.value; const why = this.busy(); if (why) return respond(errorReply(requestId, 8 /* BUSY */, why)); - resumeProblem(this.ctx, path).then( - (problem) => { - if (this.disposed) return respond(errorReply(requestId, 7 /* SESSION_GONE */, "the session ended")); - if (problem) return respond(errorReply(requestId, 4 /* INVALID */, problem)); - this.guard("resume", () => this.replace(requestId, "resume", `/${RESUME_COMMAND} ${path}`, "pi did not resume the session", respond))(); - }, - (e) => respond(errorReply(requestId, 13 /* PI_FAILED */, e?.message ?? String(e))) - ); - return; + return this.whenSettled(requestId, "resume", resumeProblem(this.ctx, path), respond, (problem) => { + if (problem) return respond(errorReply(requestId, 4 /* INVALID */, problem)); + this.replace(requestId, "resume", path, respond); + }); } case "reload": - return this.replace(body.value.requestId, "reload", `/${RELOAD_COMMAND}`, "pi did not reload", respond); + return this.replace(body.value.requestId, "reload", "", respond); case "statsRequest": { const usage = this.ctx.getContextUsage(); return respond(create(SessionMessageSchema, { @@ -8281,7 +8334,7 @@ var Live = class { case: "commands", value: { requestId: body.value.requestId, - commands: this.pi.getCommands().filter((c) => c.name !== "pirc" && !c.name.startsWith("pirc:")).map((c) => ({ name: c.name, description: c.description ?? "", source: SOURCE[c.source] ?? 0 /* UNSPECIFIED */ })) + commands: this.pi.getCommands().filter((c) => !isPircCommand(c.name)).map((c) => ({ name: c.name, description: c.description ?? "", source: SOURCE[c.source] ?? 0 /* UNSPECIFIED */ })) } } })); @@ -8303,16 +8356,9 @@ var Live = class { modifiedAt: timestampFromDate(s.modified), messageCount: s.messageCount })); - let sessions = shown; - const reply = () => create(SessionMessageSchema, { - body: { case: "pastSessions", value: { requestId, sessions, truncated: sessions.length < shown.length } } - }); - let m = reply(); - while (sessions.length > 0 && toBinary(SessionMessageSchema, m).length > budget) { - sessions = sessions.slice(0, sessions.length - Math.max(1, Math.floor(sessions.length / 8))); - m = reply(); - } - return m; + return fitting(shown.slice().reverse(), budget, (kept) => create(SessionMessageSchema, { + body: { case: "pastSessions", value: { requestId, sessions: kept.slice().reverse(), truncated: kept.length < shown.length } } + })); } // The conversation on the current branch, newest last, within one frame. history(requestId, limit) { @@ -8320,28 +8366,26 @@ var Live = class { this.resolvePending(); const budget = this.sink.maxPayloadBytes() - 1024; const all = historyItems(this.ctx.sessionManager.getBranch()).map((i) => fitItem(i, budget - 256)); - let items = limit > 0 ? all.slice(-limit) : all; - let truncated = items.length < all.length; - const reply = (its) => create(SessionMessageSchema, { body: { case: "history", value: create(HistorySchema, { requestId, items: its, seq: this.seq, truncated }) } }); - let m = reply(items); - while (items.length > 0 && toBinary(SessionMessageSchema, m).length > budget) { - items = items.slice(Math.max(1, Math.floor(items.length / 8))); - truncated = true; - m = reply(items); - } - return m; + const items = limit > 0 ? all.slice(-limit) : all; + return fitting(items, budget, (kept) => create(SessionMessageSchema, { body: { case: "history", value: create(HistorySchema, { requestId, items: kept, seq: this.seq, truncated: kept.length < all.length }) } })); } }; -function accepted(requestId) { - return create(SessionMessageSchema, { body: { case: "accepted", value: { requestId } } }); +function fitting(items, budget, build) { + let kept = items; + let m = build(kept); + while (kept.length > 0 && toBinary(SessionMessageSchema, m).length > budget) { + kept = kept.slice(Math.max(1, Math.floor(kept.length / 8))); + m = build(kept); + } + return m; +} +function errorText(e) { + return e?.message ?? String(e); } function withRequestId(m, requestId) { if (m.body.case === "accepted" || m.body.case === "error") m.body.value.requestId = requestId; return m; } -function errorReply(requestId, code, message) { - return create(SessionMessageSchema, { body: { case: "error", value: { requestId, code, message } } }); -} // extension/src/place.ts import { execFile } from "node:child_process"; @@ -8350,12 +8394,7 @@ var DETECT_MS = 2e3; var MAX_NAME = 128; var PLACE_EVENT = "pirc:place"; function cut(s) { - if (typeof s !== "string") return ""; - const b = Buffer.from(s.trim(), "utf8"); - if (b.length <= MAX_NAME) return s.trim(); - let end = MAX_NAME; - while (end > 0 && (b[end] & 192) === 128) end--; - return b.subarray(0, end).toString("utf8"); + return typeof s === "string" ? cutUtf8(s.trim(), MAX_NAME) : ""; } function num(n) { return typeof n === "number" && Number.isInteger(n) && n >= 0 && n < 2 ** 32 ? n : void 0; @@ -8522,15 +8561,27 @@ function key(p) { } // extension/src/host.ts -var CHANGES = /* @__PURE__ */ new Set(["prompt", "abort", "setModel", "setThinkingLevel", "compact", "newSession", "fork", "clone", "resume", "reload", "rename"]); +var REQUESTS = { + prompt: "change", + abort: "change", + setModel: "change", + setThinkingLevel: "change", + compact: "change", + newSession: "change", + fork: "change", + clone: "change", + resume: "change", + reload: "change", + rename: "change", + historyRequest: "question", + listModels: "question", + statsRequest: "question", + listCommands: "question", + listPastSessions: "question" +}; +var isChange = (c) => c !== void 0 && Object.hasOwn(REQUESTS, c) && REQUESTS[c] === "change"; var IDEMPOTENCY_ENTRIES = 1e3; var RETRYABLE = /* @__PURE__ */ new Set([8 /* BUSY */, 9 /* TIMEOUT */, 13 /* PI_FAILED */, 4 /* INVALID */, 7 /* SESSION_GONE */]); -function hex(b) { - return Buffer.from(b).toString("hex"); -} -function sameKey(a, b) { - return a.length === b.length && a.every((x, i) => x === b[i]); -} var HANDOVER_MS = 3e4; var HANDOVER_AFTER_ANNOUNCE_MS = 2e3; var Host = class _Host { @@ -8804,18 +8855,18 @@ var Host = class _Host { if (session) this.routed(session, clientKey, errorReply("", 4 /* INVALID */, "undecodable message")); return; } - if (!session || !this.key || !sameKey(session.hostKey, this.key.publicKey)) return; + if (!session || !this.key || !sameBytes(session.hostKey, this.key.publicKey)) return; const is = (l) => l !== void 0 && session.sessionId === l.sessionId && session.instanceId === l.instanceId; if (is(this.suspended)) { - this.routed(session, clientKey, errorReply(requestIdOf(m), 8 /* BUSY */, "pi is reloading; try again shortly")); + this.routed(session, clientKey, errorReply(requestIdOf(m.body), 8 /* BUSY */, "pi is reloading; try again shortly")); return; } const live = this.live; if (!is(live)) { - this.routed(session, clientKey, errorReply(requestIdOf(m), 7 /* SESSION_GONE */, "session is no longer live in this process")); + this.routed(session, clientKey, errorReply(requestIdOf(m.body), 7 /* SESSION_GONE */, "session is no longer live in this process")); return; } - const idem = m.body.case && CHANGES.has(m.body.case) ? `${live.sessionId}/${hex(clientKey)}/${requestIdOf(m)}` : void 0; + const idem = isChange(m.body.case) ? `${live.sessionId}/${hex(clientKey)}/${requestIdOf(m.body)}` : void 0; const seen = idem && this.replies.get(idem); if (seen === "pending") return; if (seen) return this.reply(live, clientKey, seen); @@ -8833,10 +8884,10 @@ var Host = class _Host { }; void this.places.refresh(); try { - live.onRequest(clientKey, m, respond); + live.onRequest(m, respond); } catch (e) { this.fail("request", e); - respond(errorReply(requestIdOf(m), 13 /* PI_FAILED */, e?.message ?? String(e))); + respond(errorReply(requestIdOf(m.body), 13 /* PI_FAILED */, errorText(e))); } } remember(key2, v) { @@ -8845,13 +8896,9 @@ var Host = class _Host { if (this.replies.size > IDEMPOTENCY_ENTRIES) this.replies.delete(this.replies.keys().next().value); } }; -function requestIdOf(m) { - const v = m.body.value; - return v?.requestId ?? ""; -} // extension/src/index.ts -var EXTENSION_VERSION = true ? "0.1.9-dev+05e39e0be370" : "dev"; +var EXTENSION_VERSION = true ? "0.1.9-dev+1d13db331154" : "dev"; function piVersion() { try { let dir = dirname3(realpathSync(process.argv[1])); @@ -8866,7 +8913,7 @@ function piVersion() { } return "unknown"; } -var LOAD = true ? "0.1.9-dev+05e39e0be370" : `dev ${randomBytes3(4).toString("hex")}`; +var LOAD = true ? "0.1.9-dev+1d13db331154" : `dev ${randomBytes3(4).toString("hex")}`; var KEY = /* @__PURE__ */ Symbol.for("pirc.extension"); var proc = globalThis[KEY] ??= {}; if (!proc.host || proc.load !== LOAD) { @@ -8929,67 +8976,20 @@ function index_default(pi) { pi.on("session_before_compact", on("session_before_compact", (live) => live.onCompactStart())); pi.on("session_compact", on("session_compact", (live, e) => live.onCompact(e.compactionEntry))); pi.on("session_compact_failed", on("session_compact_failed", (live) => live.onCompactEnd())); - pi.registerCommand(NEW_SESSION_COMMAND, { - description: "PiRC: start a new session (used by PiRC's clients)", - handler: async (_args, ctx) => { - try { - const r = await ctx.newSession(); - if (r.cancelled) proc.host.liveFor(pi)?.onReplaceFailed("another extension cancelled the new session"); - } catch (err) { - proc.host.liveFor(pi)?.onReplaceFailed(err?.message ?? String(err)); - } - } - }); - const failed = (why) => proc.host.liveFor(pi)?.onReplaceFailed(why); - pi.registerCommand(FORK_COMMAND, { - description: "PiRC: fork before a user message (used by PiRC's clients)", - handler: async (args, ctx) => { - const id = args.trim(); - const problem = forkProblem(ctx, id, "before"); - if (problem) return failed(problem); - try { - if ((await ctx.fork(id)).cancelled) failed("another extension cancelled the fork"); - } catch (err) { - failed(err?.message ?? String(err)); - } - } - }); - pi.registerCommand(CLONE_COMMAND, { - description: "PiRC: clone the session as it is (used by PiRC's clients)", - handler: async (_args, ctx) => { - const leaf = ctx.sessionManager.getLeafId() ?? ""; - const problem = forkProblem(ctx, leaf, "at"); - if (problem) return failed(problem); - try { - if ((await ctx.fork(leaf, { position: "at" })).cancelled) failed("another extension cancelled the clone"); - } catch (err) { - failed(err?.message ?? String(err)); - } - } - }); - pi.registerCommand(RESUME_COMMAND, { - description: "PiRC: resume a saved session (used by PiRC's clients)", - handler: async (args, ctx) => { - const path = args.trim(); - try { - const problem = await resumeProblem(ctx, path); - if (problem) return failed(problem); - if ((await ctx.switchSession(path)).cancelled) failed("another extension cancelled the resume"); - } catch (err) { - failed(err?.message ?? String(err)); - } - } - }); - pi.registerCommand(RELOAD_COMMAND, { - description: "PiRC: reload pi's resources (used by PiRC's clients)", - handler: async (_args, ctx) => { - try { - await ctx.reload(); - } catch (err) { - failed(err?.message ?? String(err)); + for (const c of Object.values(SESSION_COMMANDS)) { + pi.registerCommand(c.name, { + description: c.description, + handler: async (args, ctx) => { + let failed; + try { + failed = await c.run(args.trim(), ctx); + } catch (err) { + failed = errorText(err); + } + if (failed) proc.host.liveFor(pi)?.onReplaceFailed(failed); } - } - }); + }); + } pi.registerCommand("pirc", { description: "PiRC: status, enrol <relay-url> [token], reconnect", handler: async (args, ctx) => { |
