dsh-packycode-api 1.2.6 → 1.2.8

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/lib/index.js CHANGED
@@ -8,8 +8,8 @@ import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdtempSync, op
8
8
  import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
9
9
  import { createServer } from "node:http";
10
10
  import { defineTool } from "@deepseek-ai/dsh-tools";
11
- import { credentialKey } from "@deepseek-ai/dsh-credentials";
12
11
  import { fileURLToPath } from "node:url";
12
+ import { credentialKey } from "@deepseek-ai/dsh-credentials";
13
13
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
14
14
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
15
15
  import { lazyStream } from "@earendil-works/pi-ai/api/lazy";
@@ -1334,7 +1334,9 @@ function pickKeyForModel(modelId, active, usable, covers) {
1334
1334
  *
1335
1335
  * A session-title request is not a delegation: it runs on the small model,
1336
1336
  * read-only and outside the workspace, with the title instructions appended,
1337
- * so naming a session does not cost an Opus agent turn.
1337
+ * so naming a session does not cost an Opus agent turn. Under the CLI's own
1338
+ * system prompt those instructions are easily outweighed, so the prompt ends
1339
+ * by restating them and the answer is unwrapped before DSH reads it.
1338
1340
  *
1339
1341
  * @module dsh-packycode-api/claude-adapter
1340
1342
  */
@@ -1796,6 +1798,87 @@ function renderAuxiliaryPrompt(options) {
1796
1798
  return (Array.isArray(options?.messages) ? options.messages : []).filter((message) => message !== null && typeof message === "object" && message.role !== "system").flatMap((message) => Array.isArray(message.content) ? message.content : []).filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
1797
1799
  }
1798
1800
  /**
1801
+ * Languages named by their script. The caller frames a title request in
1802
+ * English, so text in one of these scripts is the user's own.
1803
+ */
1804
+ const TITLE_SCRIPTS = Object.freeze([
1805
+ ["Korean", /\p{Script=Hangul}/u],
1806
+ ["Japanese", /[\p{Script=Hiragana}\p{Script=Katakana}]/u],
1807
+ ["Chinese", /\p{Script=Han}/u]
1808
+ ]);
1809
+ /**
1810
+ * The prompt of a CLI title request: the caller's framed input, verbatim,
1811
+ * then the two rules the small model breaks under the CLI's own system
1812
+ * prompt. There the caller's title instructions are only an appendix and its
1813
+ * input is English framing around a JSON array, so a short message came back
1814
+ * as JSON or in English (`{"title": "Session greeting check"}` for 在吗).
1815
+ * @param options - the assembled title request.
1816
+ * @returns the prompt text.
1817
+ */
1818
+ function renderTitlePrompt(options) {
1819
+ const framed = renderAuxiliaryPrompt(options);
1820
+ const language = TITLE_SCRIPTS.find(([, script]) => script.test(framed))?.[0];
1821
+ return [
1822
+ framed,
1823
+ "",
1824
+ "Reply with the title alone, as plain text on one line: not JSON, not quoted, with no label such as \"Title:\".",
1825
+ language === void 0 ? "Write it in the language of the messages' text, not in the language of these instructions." : `Write it in ${language}, the language of the messages' text, not in the language of these instructions.`
1826
+ ].join("\n");
1827
+ }
1828
+ /** Pairs a reply wraps a whole title in. Book-title marks belong to a title. */
1829
+ const TITLE_WRAPPERS = Object.freeze([
1830
+ ["\"", "\""],
1831
+ ["'", "'"],
1832
+ ["“", "”"],
1833
+ ["‘", "’"],
1834
+ ["「", "」"],
1835
+ ["『", "』"],
1836
+ ["```", "```"],
1837
+ ["`", "`"],
1838
+ ["**", "**"],
1839
+ ["«", "»"]
1840
+ ]);
1841
+ const TITLE_LABEL = /^[*"'`]*(?:(?:session\s+)?title|(?:会话)?标题)[*"'`]*\s*[::]\**\s*/iu;
1842
+ /** A reply that names the field instead of filling it. */
1843
+ const TITLE_PLACEHOLDER = /^(?:(?:(?:session|conversation|chat)\s+)?title|(?:会话|对话|聊天)?标题)$/iu;
1844
+ /** The title inside a JSON reply: a string, or an object's `title` string. */
1845
+ function jsonTitle(text) {
1846
+ if (!/^[{"]/u.test(text)) return void 0;
1847
+ let value;
1848
+ try {
1849
+ value = JSON.parse(text);
1850
+ } catch {
1851
+ return;
1852
+ }
1853
+ if (typeof value === "string") return value.trim();
1854
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
1855
+ const field = Object.keys(value).find((key) => key.toLowerCase() === "title");
1856
+ return field !== void 0 && typeof value[field] === "string" ? value[field].trim() : void 0;
1857
+ }
1858
+ /**
1859
+ * Recover the bare title from a reply that wrapped it: in a code fence, as a
1860
+ * JSON object's `title` or a JSON string, after a "Title:" label, or in
1861
+ * quotes. Anything else comes back trimmed.
1862
+ *
1863
+ * Some replies hold no usable title: several lines (the model wrote out its
1864
+ * thinking or an explanation, and which line is the title cannot be told), a
1865
+ * placeholder such as "Session title", or an empty wrapper. Those yield no
1866
+ * text, so DSH keeps its own fallback title, the first message's words.
1867
+ * @param text - a title request's whole text answer.
1868
+ * @returns the title text, or '' when the reply holds none.
1869
+ */
1870
+ function unwrapTitle(text) {
1871
+ const raw = typeof text === "string" ? text.trim() : "";
1872
+ const body = /^```[^\n]*\n([\s\S]*?)\n?```$/u.exec(raw)?.[1].trim() ?? raw;
1873
+ const lines = body.split("\n").map((line) => line.trim()).filter((line) => line !== "");
1874
+ const json = jsonTitle(body) ?? jsonTitle(lines[0] ?? "");
1875
+ if (json === void 0 && lines.length > 1) return "";
1876
+ let title = (json ?? lines[0] ?? "").replace(TITLE_LABEL, "");
1877
+ const wrapper = TITLE_WRAPPERS.find(([open, close]) => title.length > open.length + close.length && title.startsWith(open) && title.endsWith(close));
1878
+ if (wrapper !== void 0) title = title.slice(wrapper[0].length, title.length - wrapper[1].length).trim();
1879
+ return TITLE_PLACEHOLDER.test(title) ? "" : title;
1880
+ }
1881
+ /**
1799
1882
  * Emit text and reasoning as well-formed harness blocks.
1800
1883
  *
1801
1884
  * The harness's stream grammar requires every delta to address an open block
@@ -1948,14 +2031,15 @@ function retryNote(event) {
1948
2031
  }
1949
2032
  /**
1950
2033
  * How one request runs on the CLI: a title request on the small model with
1951
- * the caller's instructions appended, anything else as a delegated turn.
2034
+ * the caller's instructions appended and restated, anything else as a
2035
+ * delegated turn.
1952
2036
  *
1953
2037
  * The CLI's own system prompt and tool set stay in place even for a title:
1954
2038
  * they are part of what the gateway sees from the official client.
1955
2039
  */
1956
2040
  async function cliCall(options, { titleModel, cwdOf, modeOf, attachmentsOf, imageLimits }) {
1957
2041
  if (options?.purpose === "session-title") return {
1958
- prompt: renderAuxiliaryPrompt(options),
2042
+ prompt: renderTitlePrompt(options),
1959
2043
  model: titleModel,
1960
2044
  mode: "read-only",
1961
2045
  enableSearch: false,
@@ -2040,6 +2124,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
2040
2124
  }
2041
2125
  async function* cliStream(options) {
2042
2126
  const quiet = options?.purpose === "session-title";
2127
+ let titleText = "";
2043
2128
  const blocks = createBlockWriter();
2044
2129
  if (!quiet) yield* blocks.note("▸ 启动 Claude Code CLI…\n");
2045
2130
  let call;
@@ -2112,10 +2197,12 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
2112
2197
  if (!quiet) yield* blocks.note(` ↻ ${retryNote(event)}\n`);
2113
2198
  break;
2114
2199
  case "block":
2200
+ if (quiet && event.kind === "text") titleText = "";
2115
2201
  blocks.boundary(event.kind);
2116
2202
  break;
2117
2203
  case "text":
2118
- yield* blocks.write("text", event.text);
2204
+ if (quiet) titleText += event.text;
2205
+ else yield* blocks.write("text", event.text);
2119
2206
  break;
2120
2207
  case "reasoning":
2121
2208
  yield* blocks.write("reasoning", event.text);
@@ -2150,6 +2237,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
2150
2237
  };
2151
2238
  return;
2152
2239
  }
2240
+ if (quiet) yield* blocks.write("text", unwrapTitle(titleText));
2153
2241
  yield* blocks.close();
2154
2242
  yield {
2155
2243
  type: "usage",
@@ -5439,6 +5527,221 @@ function createClaudeCodeTool({ runner, modeCeiling, planActive }) {
5439
5527
  });
5440
5528
  }
5441
5529
  //#endregion
5530
+ //#region src/diagnostics.js
5531
+ /**
5532
+ * A support summary of one DSH session, for reports such as "已达到输出 token
5533
+ * 上限" or failing requests: which route, reasoning effort and output ceiling
5534
+ * each request used, how every model step ended and what it consumed, and
5535
+ * which retries and failures happened.
5536
+ *
5537
+ * The summary carries numbers and identifiers only. Message text, reasoning,
5538
+ * tool arguments and results, titles, paths and key material never enter it;
5539
+ * error messages are redacted and shortened. A user can paste it into a bug
5540
+ * report as it is.
5541
+ *
5542
+ * @module dsh-packycode-api/diagnostics
5543
+ */
5544
+ const DIAGNOSTICS_FORMAT = "packycode-diagnostics/1";
5545
+ const SESSION_LIST_LIMIT = 20;
5546
+ const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
5547
+ const MESSAGE_CHARS = 160;
5548
+ /** How many of each record the summary keeps, newest last. */
5549
+ const DIAGNOSTICS_LIMITS = Object.freeze({
5550
+ requests: 8,
5551
+ truncated: 12,
5552
+ steps: 20,
5553
+ turns: 10,
5554
+ retries: 10,
5555
+ attempts: 10
5556
+ });
5557
+ const finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
5558
+ /** Identifier-like strings only (routes, models, efforts, reason kinds, codes). */
5559
+ const word = (value) => typeof value === "string" && /^[\w.:/@[\]-]{1,80}$/u.test(value) ? value : void 0;
5560
+ const iso = (value) => finite(value) === void 0 ? void 0 : new Date(value).toISOString();
5561
+ const last = (list, count) => list.slice(Math.max(0, list.length - count));
5562
+ const compact = (object) => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
5563
+ const chunksOf = (stream) => Array.isArray(stream) ? stream.map((item) => item?.chunk).filter((chunk) => chunk !== null && typeof chunk === "object") : [];
5564
+ /** A provider or harness error message, redacted and shortened. */
5565
+ function safeMessage$1(text, secrets) {
5566
+ if (typeof text !== "string" || text === "") return void 0;
5567
+ const safe = redactSecrets(text, secrets).replace(/sk-[A-Za-z0-9_-]{8,}/gu, "sk-[redacted]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/giu, "Bearer [redacted]").replace(/\s+/gu, " ").trim();
5568
+ return safe.length > MESSAGE_CHARS ? `${safe.slice(0, MESSAGE_CHARS)}…` : safe;
5569
+ }
5570
+ function failureOf(reason, secrets) {
5571
+ const failure = reason?.failure ?? reason?.error;
5572
+ return failure === void 0 ? {} : {
5573
+ code: word(failure.code),
5574
+ message: safeMessage$1(failure.message, secrets)
5575
+ };
5576
+ }
5577
+ /**
5578
+ * Summarize one session's events.
5579
+ * @param events - the session's events in log order.
5580
+ * @param options - values to redact from error messages.
5581
+ * @returns the numbers-only summary body.
5582
+ */
5583
+ function summarizeSessionEvents(events, { secrets = [] } = {}) {
5584
+ const requests = [];
5585
+ const steps = [];
5586
+ const turnEnds = [];
5587
+ const retries = [];
5588
+ const attempts = [];
5589
+ let turns = 0;
5590
+ let compactions = 0;
5591
+ for (const event of events ?? []) {
5592
+ const type = event?.type;
5593
+ const data = event?.data ?? {};
5594
+ if (typeof type !== "string") continue;
5595
+ if (/compact/u.test(type)) compactions += 1;
5596
+ if (type === "turn/start") turns += 1;
5597
+ else if (type === "request/header") {
5598
+ const config = data.header?.config ?? {};
5599
+ requests.push(compact({
5600
+ time: iso(event.time),
5601
+ reason: word(data.reason),
5602
+ provider: word(config.provider),
5603
+ model: word(config.model),
5604
+ reasoningEffort: word(config.reasoningEffort),
5605
+ maxTokens: finite(config.maxTokens),
5606
+ maxTokensFromAdapter: data.header?.adapterDefaults?.maxTokens === true ? true : void 0
5607
+ }));
5608
+ } else if (type === "assistant/message") {
5609
+ const chunks = chunksOf(data.stream);
5610
+ const usage = data.usage ?? chunks.find((chunk) => chunk.type === "usage")?.usage ?? {};
5611
+ const input = finite(usage.inputTokens);
5612
+ const cacheRead = finite(usage.cacheReadTokens);
5613
+ steps.push(compact({
5614
+ turn: finite(data.turn),
5615
+ step: finite(data.step),
5616
+ time: iso(event.time),
5617
+ finish: word(chunks.find((chunk) => chunk.type === "finish")?.reason?.kind) ?? "none",
5618
+ input,
5619
+ cacheRead,
5620
+ cacheWrite: finite(usage.cacheWriteTokens),
5621
+ output: finite(usage.outputTokens),
5622
+ context: input === void 0 && cacheRead === void 0 ? void 0 : (input ?? 0) + (cacheRead ?? 0)
5623
+ }));
5624
+ } else if (type === "assistant/attempt") {
5625
+ const reason = chunksOf(data.stream).find((chunk) => chunk.type === "finish")?.reason;
5626
+ attempts.push(compact({
5627
+ turn: finite(data.turn),
5628
+ step: finite(data.step),
5629
+ time: iso(event.time),
5630
+ finish: word(reason?.kind),
5631
+ ...failureOf(reason, secrets)
5632
+ }));
5633
+ } else if (type === "llm/retry") retries.push(compact({
5634
+ turn: finite(data.turn),
5635
+ step: finite(data.step),
5636
+ time: iso(event.time),
5637
+ provider: word(data.provider),
5638
+ retry: finite(data.retry),
5639
+ maxRetries: finite(data.maxRetries),
5640
+ ...failureOf({ failure: data.failure }, secrets)
5641
+ }));
5642
+ else if (type === "turn/end") turnEnds.push(compact({
5643
+ turn: finite(data.turn),
5644
+ time: iso(event.time),
5645
+ kind: word(data.reason?.kind),
5646
+ ...failureOf(data.reason, secrets)
5647
+ }));
5648
+ }
5649
+ const truncated = steps.filter((step) => step.finish === "max-tokens");
5650
+ return {
5651
+ totals: {
5652
+ turns,
5653
+ steps: steps.length,
5654
+ maxTokenSteps: truncated.length,
5655
+ retries: retries.length,
5656
+ failedAttempts: attempts.length,
5657
+ compactionEvents: compactions
5658
+ },
5659
+ requestSettings: last(requests, DIAGNOSTICS_LIMITS.requests),
5660
+ maxTokenSteps: last(truncated, DIAGNOSTICS_LIMITS.truncated),
5661
+ recentSteps: last(steps, DIAGNOSTICS_LIMITS.steps),
5662
+ turnEnds: last(turnEnds, DIAGNOSTICS_LIMITS.turns),
5663
+ retries: last(retries, DIAGNOSTICS_LIMITS.retries),
5664
+ failedAttempts: last(attempts, DIAGNOSTICS_LIMITS.attempts)
5665
+ };
5666
+ }
5667
+ /**
5668
+ * The diagnostics service behind the settings card.
5669
+ * @param options - DSH's session query service (read lazily), the session
5670
+ * event reader the image originals already use, the key ring and group
5671
+ * reader, the stored secrets to redact, and this package's manifest URL.
5672
+ */
5673
+ function createDiagnostics({ sessionQuery, readEvents, ring, groups, secrets = async () => [], ownPackageJsonUrl }) {
5674
+ const query = () => {
5675
+ const service = sessionQuery?.();
5676
+ if (typeof service?.listSessions !== "function") throw new Error("PackyCode diagnostics need DSH session history, which this runtime does not provide");
5677
+ return service;
5678
+ };
5679
+ const version = async () => {
5680
+ try {
5681
+ return word(JSON.parse(await readFile(fileURLToPath(ownPackageJsonUrl), "utf8")).version);
5682
+ } catch {
5683
+ return;
5684
+ }
5685
+ };
5686
+ /** Stored conversation keys by position: state and groups, never labels or keys. */
5687
+ const keys = async () => {
5688
+ const listed = (await ring.list()).keys ?? [];
5689
+ let reading;
5690
+ try {
5691
+ reading = await groups?.read({ force: false });
5692
+ } catch {
5693
+ reading = void 0;
5694
+ }
5695
+ return listed.map((entry, index) => {
5696
+ const group = reading?.keys?.find((item) => item.id === entry.id);
5697
+ return compact({
5698
+ key: index + 1,
5699
+ active: entry.active === true,
5700
+ disabled: entry.disabled === true ? true : void 0,
5701
+ groups: Array.isArray(group?.groups) ? group.groups.map(word).filter(Boolean) : void 0,
5702
+ reaches: group?.coverage === void 0 ? void 0 : Object.keys(group.coverage).filter((model) => group.coverage[model] === true)
5703
+ });
5704
+ });
5705
+ };
5706
+ return Object.freeze({
5707
+ /** Recent top-level sessions, newest first, for the picker. */
5708
+ async list(signal) {
5709
+ return { sessions: (await query().listSessions(signal)).filter((record) => typeof record?.header?.id === "string" && !(record.header.delegationDepth > 0)).slice(0, SESSION_LIST_LIMIT).map((record) => compact({
5710
+ id: record.header.id,
5711
+ createdAt: iso(record.header.createdAt),
5712
+ live: record.live === true,
5713
+ folder: typeof record.header.cwd === "string" && record.header.cwd !== "" ? basename(record.header.cwd) : void 0
5714
+ })) };
5715
+ },
5716
+ /** The numbers-only summary of one session. */
5717
+ async read(payload, signal) {
5718
+ const sessionId = payload?.sessionId;
5719
+ if (typeof sessionId !== "string" || !SESSION_ID.test(sessionId)) throw new Error("PackyCode diagnostics need a session to read");
5720
+ query();
5721
+ let events;
5722
+ try {
5723
+ events = await readEvents(sessionId, signal);
5724
+ } catch {
5725
+ throw new Error("PackyCode could not read that session");
5726
+ }
5727
+ const record = (await query().listSessions(signal)).find((item) => item?.header?.id === sessionId);
5728
+ return {
5729
+ format: DIAGNOSTICS_FORMAT,
5730
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5731
+ plugin: compact({ version: await version() }),
5732
+ session: compact({
5733
+ id: sessionId,
5734
+ createdAt: iso(record?.header?.createdAt),
5735
+ live: record === void 0 ? void 0 : record.live === true,
5736
+ delegationDepth: finite(record?.header?.delegationDepth)
5737
+ }),
5738
+ keys: await keys(),
5739
+ ...summarizeSessionEvents(events, { secrets: await secrets() })
5740
+ };
5741
+ }
5742
+ });
5743
+ }
5744
+ //#endregion
5442
5745
  //#region src/preferences.js
5443
5746
  /**
5444
5747
  * Where the plugin's preferences are read from and written to, on both
@@ -5631,6 +5934,7 @@ function createImageController({ ring, balance, readPreference, writePreference,
5631
5934
  async mutate(operation, payload = {}) {
5632
5935
  const operations = {
5633
5936
  add: () => ring.add(payload.key, payload.label),
5937
+ rename: () => ring.rename(String(payload.id ?? ""), payload.label),
5634
5938
  remove: () => ring.remove(String(payload.id ?? "")),
5635
5939
  select: () => ring.select(String(payload.id ?? "")),
5636
5940
  enable: () => ring.enable(String(payload.id ?? ""))
@@ -6961,8 +7265,9 @@ function createRouteAdapter({ ring, keys, fallbackSessionId, resolveAttachments,
6961
7265
  * Every reply carries only what this plugin owns: key labels and state, the
6962
7266
  * balance and group projections the host built, host overrides, whether this
6963
7267
  * device has the Claude Code CLI, the plugin version, and the web search
6964
- * preference and model visibility, or bounded session-authorized original PNG
6965
- * chunks. Key material never crosses this boundary.
7268
+ * preference and model visibility, bounded session-authorized original PNG
7269
+ * chunks, or a numbers-only session diagnosis. Key material never crosses this
7270
+ * boundary.
6966
7271
  *
6967
7272
  * @module dsh-packycode-api/rpc
6968
7273
  */
@@ -6988,7 +7293,7 @@ const keyId = (payload) => String(payload?.id ?? "");
6988
7293
  * web search controller (without one, its endpoints refuse).
6989
7294
  * @returns a handler for `connection.rpc.handle(CHANNEL, handler)`.
6990
7295
  */
6991
- function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals }) {
7296
+ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals, diagnostics }) {
6992
7297
  const need = (service, message) => {
6993
7298
  if (service === void 0) throw new Error(message);
6994
7299
  return service;
@@ -7000,6 +7305,7 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
7000
7305
  const models = () => need(modelSelection, "PackyCode model selection is unavailable in this runtime");
7001
7306
  const imageSupport = () => need(images, "PackyCode images are unavailable in this runtime");
7002
7307
  const originals = () => need(imageOriginals, "PackyCode original images are unavailable in this runtime");
7308
+ const diagnosis = () => need(diagnostics, "PackyCode diagnostics are unavailable in this runtime");
7003
7309
  const status = async () => ({
7004
7310
  ...await ring.list(),
7005
7311
  hosts: hosts(),
@@ -7080,9 +7386,15 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
7080
7386
  key: payload?.key,
7081
7387
  label: payload?.label
7082
7388
  }),
7389
+ "images/keys/rename": (payload) => imageSupport().mutate("rename", {
7390
+ id: payload?.id,
7391
+ label: payload?.label
7392
+ }),
7083
7393
  "images/keys/remove": (payload) => imageSupport().mutate("remove", { id: payload?.id }),
7084
7394
  "images/keys/select": (payload) => imageSupport().mutate("select", { id: payload?.id }),
7085
- "images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id })
7395
+ "images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id }),
7396
+ "diagnostics/sessions": (_payload, signal) => diagnosis().list(signal),
7397
+ "diagnostics/session": (payload, signal) => diagnosis().read(payload, signal)
7086
7398
  };
7087
7399
  return async (endpoint, payload, signal) => {
7088
7400
  try {
@@ -8278,6 +8590,23 @@ function apply(ctx, config) {
8278
8590
  search.register(webCtx.web, (callback, label) => scope.effect(callback, label));
8279
8591
  });
8280
8592
  keys.remember().catch(() => void 0);
8593
+ /** One session's events from DSH's session query service, without projections. */
8594
+ const readSessionEvents = async (sessionId, signal) => {
8595
+ const query = ctx.get?.("sessionQuery");
8596
+ if (typeof query?.observeSession === "function") {
8597
+ const observation = await query.observeSession(sessionId, {
8598
+ signal,
8599
+ projectionMode: "none"
8600
+ });
8601
+ try {
8602
+ return [...observation.events];
8603
+ } finally {
8604
+ observation[Symbol.dispose]();
8605
+ }
8606
+ }
8607
+ if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
8608
+ return [];
8609
+ };
8281
8610
  const handler = createPackyCodeRpc({
8282
8611
  ring,
8283
8612
  balance,
@@ -8291,22 +8620,15 @@ function apply(ctx, config) {
8291
8620
  images,
8292
8621
  imageOriginals: createImageOriginalReader({
8293
8622
  imageDirectory: join(dshHome, "packycode-api", "images"),
8294
- getSessionEvents: async (sessionId, signal) => {
8295
- const query = ctx.get?.("sessionQuery");
8296
- if (typeof query?.observeSession === "function") {
8297
- const observation = await query.observeSession(sessionId, {
8298
- signal,
8299
- projectionMode: "none"
8300
- });
8301
- try {
8302
- return [...observation.events];
8303
- } finally {
8304
- observation[Symbol.dispose]();
8305
- }
8306
- }
8307
- if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
8308
- return [];
8309
- }
8623
+ getSessionEvents: readSessionEvents
8624
+ }),
8625
+ diagnostics: createDiagnostics({
8626
+ sessionQuery: () => ctx.get?.("sessionQuery"),
8627
+ readEvents: readSessionEvents,
8628
+ ring,
8629
+ groups,
8630
+ secrets: async () => (await Promise.all([ring.secrets(), imageRing.secrets()])).flat().map((entry) => entry.key),
8631
+ ownPackageJsonUrl: new URL("../package.json", import.meta.url)
8310
8632
  })
8311
8633
  });
8312
8634
  /**
@@ -8342,4 +8664,4 @@ function apply(ctx, config) {
8342
8664
  });
8343
8665
  }
8344
8666
  //#endregion
8345
- export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, resolveSpawnCommand, statusHostOrder, summarizeToolUse, validateImageOptions };
8667
+ export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DIAGNOSTICS_FORMAT, DIAGNOSTICS_LIMITS, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createDiagnostics, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, renderTitlePrompt, resolveSpawnCommand, statusHostOrder, summarizeSessionEvents, summarizeToolUse, unwrapTitle, validateImageOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-packycode-api",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "Use PackyCode API keys in DeepSeek Harness with multi-protocol models, image generation and editing, separate key rings, and balance display",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",