@huanlin/dsh-plugin-yet-another-subagent 0.1.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/lib/client.js +44 -18
- package/lib/index.js +269 -100
- package/lib/types/client/SettingsPage.d.ts +19 -1
- package/lib/types/client/SubagentCard.d.ts +2 -1
- package/lib/types/client/SubagentTreeView.d.ts +2 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/projection.d.ts +118 -7
- package/lib/types/repair.d.ts +35 -18
- package/lib/types/rpc.d.ts +3 -1
- package/package.json +24 -33
- package/lib/invariant.js +0 -21
- package/lib/types/invariant.d.ts +0 -16
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import z from "schemastery";
|
|
2
2
|
import { assertSubagentMaxDepth, settleRun } from "@deepseek-ai/dsh-subagent";
|
|
3
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
4
|
import { constants, zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
6
5
|
import { copyFile, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
@@ -521,17 +520,29 @@ function buildTool(profiles, ctx) {
|
|
|
521
520
|
//#endregion
|
|
522
521
|
//#region src/repair.ts
|
|
523
522
|
/**
|
|
524
|
-
* One-shot session-log repair:
|
|
525
|
-
*
|
|
526
|
-
*
|
|
523
|
+
* One-shot session-log repair: physically REMOVE legacy `ya-subagent/started`
|
|
524
|
+
* event rows so the harness persistence read path (`assertEventsSupported`)
|
|
525
|
+
* loads the log again.
|
|
527
526
|
*
|
|
528
|
-
* Background:
|
|
529
|
-
* `session.append(...)
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
527
|
+
* Background: plugin versions ≤0.1.2 appended `ya-subagent/started` via
|
|
528
|
+
* `session.append(...)`. `KNOWN_SESSION_EVENT_TYPES` is code-generated with no
|
|
529
|
+
* plugin registration surface, and v0.1.2-alpha.1 refuses EVERY log row whose
|
|
530
|
+
* type is outside that set — the old `ignorable` envelope flag no longer
|
|
531
|
+
* exists, so stamping it (the ≤0.1.5 repair) cannot help. The only repair is
|
|
532
|
+
* removal.
|
|
533
|
+
*
|
|
534
|
+
* Rows cannot simply be deleted: the read path enforces contiguous `seq`
|
|
535
|
+
* numbers. This module therefore rewrites the log in place (after a `.bak`
|
|
536
|
+
* backup):
|
|
537
|
+
*
|
|
538
|
+
* - drops every `ya-subagent/started` row;
|
|
539
|
+
* - decrements the `seq` of every later ordinary event row (packed
|
|
540
|
+
* `text-chunks` / `reasoning-chunks` / `tool-call-chunks` storage rows
|
|
541
|
+
* shift their `seq0` instead);
|
|
542
|
+
* - shifts every `sourceEventSeqs` citation by the number of dropped rows
|
|
543
|
+
* ahead of it (dropped rows are never cited: only surface events carry
|
|
544
|
+
* provenance and they cite assistant chunks / surface nodes, which a
|
|
545
|
+
* plugin row never is).
|
|
535
546
|
*
|
|
536
547
|
* Two physical encodings (mirrors `session-persistence-jsonl`):
|
|
537
548
|
* - `.jsonl` — plaintext, one JSON record per line.
|
|
@@ -539,17 +550,28 @@ function buildTool(profiles, ctx) {
|
|
|
539
550
|
* frame holds the session header line, subsequent
|
|
540
551
|
* frames each hold one append batch of event lines.
|
|
541
552
|
* Each frame is independently decodable + checksummed.
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
553
|
+
* The first frame containing a dropped row and every
|
|
554
|
+
* frame after it are recompressed (their rows renumber);
|
|
555
|
+
* untouched earlier frames are copied verbatim.
|
|
545
556
|
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
557
|
+
* Modified rows are re-encoded with `JSON.stringify`, which reproduces the
|
|
558
|
+
* write path's canonical single-line form and preserves the parsed key order;
|
|
559
|
+
* untouched lines stay byte-identical.
|
|
560
|
+
*
|
|
561
|
+
* Idempotent: a log with no target rows is left untouched (no backup, no
|
|
562
|
+
* rewrite). A corrupt (unparsable) line is left untouched — that is the
|
|
563
|
+
* harness's refusal job, not ours.
|
|
548
564
|
*
|
|
549
565
|
* @module @huanlin/dsh-plugin-yet-another-subagent/repair
|
|
550
566
|
*/
|
|
551
567
|
/** The event type this module targets. */
|
|
552
568
|
const TARGET_TYPE = "ya-subagent/started";
|
|
569
|
+
/** Packed chunk-run storage row tags (their span start rides `seq0`). */
|
|
570
|
+
const CHUNK_ROW_TYPES = /* @__PURE__ */ new Set([
|
|
571
|
+
"text-chunks",
|
|
572
|
+
"reasoning-chunks",
|
|
573
|
+
"tool-call-chunks"
|
|
574
|
+
]);
|
|
553
575
|
/** Zstandard magic number (little-endian 0xFD2FB528). */
|
|
554
576
|
const ZSTD_MAGIC = 4247762216;
|
|
555
577
|
/** Compression options matching the harness's `CHECKSUM_OPTIONS`. */
|
|
@@ -634,8 +656,9 @@ async function repairSessions(sessionsRoot) {
|
|
|
634
656
|
* needed and no backup exists yet.
|
|
635
657
|
*/
|
|
636
658
|
async function repairPlaintextFile(path) {
|
|
637
|
-
const
|
|
638
|
-
|
|
659
|
+
const raw = await readFile(path, "utf8");
|
|
660
|
+
const lines = stripLegacyRows(raw, []);
|
|
661
|
+
if (lines === raw) return { kind: "clean" };
|
|
639
662
|
await ensureBackup(path);
|
|
640
663
|
return {
|
|
641
664
|
kind: "repaired",
|
|
@@ -644,21 +667,23 @@ async function repairPlaintextFile(path) {
|
|
|
644
667
|
}
|
|
645
668
|
/**
|
|
646
669
|
* Repair one `.jsonl.zstd` concatenated-frame file. The header frame is
|
|
647
|
-
* decoded
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
670
|
+
* decoded but never carries event rows; once a dropped row is found, that
|
|
671
|
+
* frame and every later frame are renumbered and recompressed (later rows'
|
|
672
|
+
* seqs shift even when their own text is otherwise unchanged). Frames before
|
|
673
|
+
* the first change are copied verbatim.
|
|
651
674
|
*/
|
|
652
675
|
async function repairZstdFile(path) {
|
|
653
676
|
const buffer = await readFile(path);
|
|
654
677
|
const frames = scanZstdFrames(buffer);
|
|
655
678
|
if (frames.length === 0) return { kind: "clean" };
|
|
656
679
|
const rebuilt = [];
|
|
680
|
+
const droppedSeqs = [];
|
|
657
681
|
let changed = false;
|
|
658
682
|
for (const frame of frames) {
|
|
659
683
|
const frameBytes = buffer.subarray(frame.start, frame.end);
|
|
660
|
-
const
|
|
661
|
-
|
|
684
|
+
const plaintext = zstdDecompressSync(frameBytes).toString("utf8");
|
|
685
|
+
const lines = stripLegacyRows(plaintext, droppedSeqs);
|
|
686
|
+
if (lines !== plaintext) {
|
|
662
687
|
changed = true;
|
|
663
688
|
rebuilt.push(zstdCompressSync(Buffer.from(lines, "utf8"), CHECKSUM_OPTIONS));
|
|
664
689
|
} else rebuilt.push(Buffer.from(frameBytes));
|
|
@@ -671,38 +696,93 @@ async function repairZstdFile(path) {
|
|
|
671
696
|
};
|
|
672
697
|
}
|
|
673
698
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
699
|
+
* Strip every `ya-subagent/started` row from JSONL text and renumber the
|
|
700
|
+
* surviving rows so the read path's contiguity check still passes. The
|
|
701
|
+
* dropped-seq accumulator is shared across calls (zstd frames of one file are
|
|
702
|
+
* transformed sequentially) so later frames renumber against earlier drops.
|
|
703
|
+
* Returns the new text, or the input reference when nothing changed.
|
|
678
704
|
*/
|
|
679
|
-
function
|
|
680
|
-
const
|
|
705
|
+
function stripLegacyRows(text, droppedSeqs) {
|
|
706
|
+
const source = text.split("\n");
|
|
707
|
+
const kept = [];
|
|
681
708
|
let changed = false;
|
|
682
|
-
for (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (!line.includes(TARGET_TYPE)) continue;
|
|
686
|
-
let parsed;
|
|
687
|
-
try {
|
|
688
|
-
parsed = JSON.parse(line);
|
|
689
|
-
} catch {
|
|
709
|
+
for (const line of source) {
|
|
710
|
+
if (line === "") {
|
|
711
|
+
kept.push(line);
|
|
690
712
|
continue;
|
|
691
713
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (record["type"] !== TARGET_TYPE) continue;
|
|
695
|
-
if (record["ignorable"] === true) continue;
|
|
696
|
-
const trimmed = line.trimEnd();
|
|
697
|
-
if (trimmed.endsWith("}")) {
|
|
698
|
-
lines[i] = trimmed.slice(0, -1) + ",\"ignorable\":true}";
|
|
714
|
+
const rewritten = rewriteLine(line, droppedSeqs);
|
|
715
|
+
if (rewritten === void 0) {
|
|
699
716
|
changed = true;
|
|
717
|
+
continue;
|
|
700
718
|
}
|
|
719
|
+
if (rewritten !== line) changed = true;
|
|
720
|
+
kept.push(rewritten);
|
|
701
721
|
}
|
|
702
|
-
return
|
|
703
|
-
|
|
704
|
-
|
|
722
|
+
return changed ? kept.join("\n") : text;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Rewrite one JSONL line under the current dropped-seq prefix.
|
|
726
|
+
* @returns the (possibly original) line text, or `undefined` when the line is
|
|
727
|
+
* a dropped target row.
|
|
728
|
+
*/
|
|
729
|
+
function rewriteLine(line, droppedSeqs) {
|
|
730
|
+
let parsed;
|
|
731
|
+
try {
|
|
732
|
+
parsed = JSON.parse(line);
|
|
733
|
+
} catch {
|
|
734
|
+
return line;
|
|
735
|
+
}
|
|
736
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return line;
|
|
737
|
+
const record = parsed;
|
|
738
|
+
if (record["type"] === TARGET_TYPE && typeof record["seq"] === "number") {
|
|
739
|
+
droppedSeqs.push(record["seq"]);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const shiftOf = (seq) => {
|
|
743
|
+
let shift = 0;
|
|
744
|
+
for (const dropped of droppedSeqs) if (dropped < seq) shift += 1;
|
|
745
|
+
return shift;
|
|
705
746
|
};
|
|
747
|
+
let modified = false;
|
|
748
|
+
if (CHUNK_ROW_TYPES.has(String(record["type"])) && typeof record["seq0"] === "number") {
|
|
749
|
+
const seq0 = record["seq0"];
|
|
750
|
+
const shifted = seq0 - shiftOf(seq0);
|
|
751
|
+
if (shifted !== seq0) {
|
|
752
|
+
record["seq0"] = shifted;
|
|
753
|
+
modified = true;
|
|
754
|
+
}
|
|
755
|
+
} else if (typeof record["seq"] === "number") {
|
|
756
|
+
const seq = record["seq"];
|
|
757
|
+
const shifted = seq - shiftOf(seq);
|
|
758
|
+
if (shifted !== seq) {
|
|
759
|
+
record["seq"] = shifted;
|
|
760
|
+
modified = true;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (Array.isArray(record["sourceEventSeqs"])) {
|
|
764
|
+
const shiftedEntries = [];
|
|
765
|
+
let provenanceModified = false;
|
|
766
|
+
for (const entry of record["sourceEventSeqs"]) if (typeof entry === "number") {
|
|
767
|
+
const shifted = entry - shiftOf(entry);
|
|
768
|
+
if (shifted !== entry) provenanceModified = true;
|
|
769
|
+
shiftedEntries.push(shifted);
|
|
770
|
+
} else if (Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "number" && typeof entry[1] === "number") {
|
|
771
|
+
const start = entry[0] - shiftOf(entry[0]);
|
|
772
|
+
const end = entry[1] - shiftOf(entry[1]);
|
|
773
|
+
if (start !== entry[0] || end !== entry[1]) provenanceModified = true;
|
|
774
|
+
shiftedEntries.push([start, end]);
|
|
775
|
+
} else {
|
|
776
|
+
shiftedEntries.length = 0;
|
|
777
|
+
provenanceModified = false;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
if (provenanceModified) {
|
|
781
|
+
record["sourceEventSeqs"] = shiftedEntries;
|
|
782
|
+
modified = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return modified ? JSON.stringify(record) : line;
|
|
706
786
|
}
|
|
707
787
|
/**
|
|
708
788
|
* Locate complete Zstandard frames in a concatenated stream. A structurally
|
|
@@ -809,7 +889,9 @@ function fail(message) {
|
|
|
809
889
|
* Register the ya-subagent RPC channel on the host's connection service.
|
|
810
890
|
* `connection` is in the plugin's inject list, so `ctx.connection` is
|
|
811
891
|
* directly available; the channel route rolls back on fiber disposal
|
|
812
|
-
* (the inner `owner.effect` owns cleanup).
|
|
892
|
+
* (the inner `owner.effect` owns cleanup). Trust and browser authentication
|
|
893
|
+
* moved to the physical `/api` carrier in v0.1.2-alpha.1, so channels no
|
|
894
|
+
* longer carry an `authority` option.
|
|
813
895
|
* @param ctx - host context.
|
|
814
896
|
* @param store - profile store.
|
|
815
897
|
*/
|
|
@@ -852,7 +934,7 @@ function registerRpc(ctx, store) {
|
|
|
852
934
|
}
|
|
853
935
|
default: return fail(`unknown endpoint: ${endpoint}`);
|
|
854
936
|
}
|
|
855
|
-
}
|
|
937
|
+
});
|
|
856
938
|
}
|
|
857
939
|
//#endregion
|
|
858
940
|
//#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
@@ -5031,6 +5113,67 @@ function refine(fn, _params = {}) {
|
|
|
5031
5113
|
function superRefine(fn, params) {
|
|
5032
5114
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
5033
5115
|
}
|
|
5116
|
+
//#endregion
|
|
5117
|
+
//#region src/projection.ts
|
|
5118
|
+
/**
|
|
5119
|
+
* Two session projections (design doc §3.6):
|
|
5120
|
+
*
|
|
5121
|
+
* - `subagentProfile` (parent session): fold `tool/call` (name `subagent`,
|
|
5122
|
+
* profile in `arguments.profile`) + the matching `tool/result.subagentId`,
|
|
5123
|
+
* building a `childId → profileId` map. Used as a cross-check / fallback
|
|
5124
|
+
* for SubagentCard (which usually reads `profileLabel` straight from the
|
|
5125
|
+
* result content).
|
|
5126
|
+
*
|
|
5127
|
+
* - `yaSubagentProgress` (child session): toolcall count, token usage,
|
|
5128
|
+
* and lifecycle state. Pushed over the projection frame so the parent's
|
|
5129
|
+
* SubagentCard can subscribe even though client runtime drops non-current
|
|
5130
|
+
* `session/event` frames (single-stage model).
|
|
5131
|
+
*
|
|
5132
|
+
* Both units are pure synchronous folds; the framework drives them and the
|
|
5133
|
+
* host wire layer ships the validated views.
|
|
5134
|
+
*
|
|
5135
|
+
* Alpha.3 change-feed contract (`@deepseek-ai/dsh-session-projection`): the
|
|
5136
|
+
* drive publishes a client view only when its raw output changes by
|
|
5137
|
+
* `Object.is`, so an object-valued view MUST reuse its reference while the
|
|
5138
|
+
* wire content is unchanged — a fresh object per call republishes on every
|
|
5139
|
+
* internal-only state change (e.g. the excluded `streamingText`
|
|
5140
|
+
* accumulator). Both `view`s below go through {@link memoizeView} for that
|
|
5141
|
+
* reference-stability guarantee.
|
|
5142
|
+
*
|
|
5143
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/projection
|
|
5144
|
+
*/
|
|
5145
|
+
/**
|
|
5146
|
+
* Wrap a pure view builder so equal content reuses the SAME object reference.
|
|
5147
|
+
*
|
|
5148
|
+
* The alpha.3 projection drive keeps the two latest raw `view` results per
|
|
5149
|
+
* session cell and publishes only when they differ by `Object.is`. A view
|
|
5150
|
+
* that allocates a fresh object per call defeats that gate; this wrapper
|
|
5151
|
+
* returns the previous object whenever the newly built one is content-equal
|
|
5152
|
+
* (`equal`) or the state reference is unchanged (repeated reads of the same
|
|
5153
|
+
* cell). The memo is shared across sessions folded by the unit — returning a
|
|
5154
|
+
* content-equal reference from another session's earlier state is still
|
|
5155
|
+
* correct, since each session's gate only compares its own consecutive
|
|
5156
|
+
* results and a stable reference satisfies it identically.
|
|
5157
|
+
*/
|
|
5158
|
+
function memoizeView(build, equal) {
|
|
5159
|
+
let lastState;
|
|
5160
|
+
let lastView;
|
|
5161
|
+
return (state) => {
|
|
5162
|
+
if (lastView !== void 0 && Object.is(state, lastState)) return lastView;
|
|
5163
|
+
const candidate = build(state);
|
|
5164
|
+
if (lastView !== void 0 && equal(candidate, lastView)) return lastView;
|
|
5165
|
+
lastState = state;
|
|
5166
|
+
lastView = candidate;
|
|
5167
|
+
return candidate;
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
/** Content equality for flat `Record<string, string>` views. */
|
|
5171
|
+
function recordEqual(a, b) {
|
|
5172
|
+
const keys = Object.keys(a);
|
|
5173
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
5174
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
5175
|
+
return true;
|
|
5176
|
+
}
|
|
5034
5177
|
/**
|
|
5035
5178
|
* Fold the parent session's `tool/call` + `tool/result` for tool name
|
|
5036
5179
|
* `subagent`. The profile id is carried in `tool/call.arguments.profile`
|
|
@@ -5040,53 +5183,52 @@ function superRefine(fn, params) {
|
|
|
5040
5183
|
*/
|
|
5041
5184
|
const subagentProfileProjection = {
|
|
5042
5185
|
key: "subagentProfile",
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5186
|
+
stateSchema: object({
|
|
5187
|
+
pending: record(string(), string()),
|
|
5188
|
+
mapping: record(string(), string()),
|
|
5189
|
+
callToChild: record(string(), string())
|
|
5046
5190
|
}).strict(),
|
|
5047
|
-
stateVersion:
|
|
5191
|
+
stateVersion: 4,
|
|
5048
5192
|
init: () => ({
|
|
5049
|
-
pending:
|
|
5193
|
+
pending: {},
|
|
5050
5194
|
mapping: {},
|
|
5051
5195
|
callToChild: {}
|
|
5052
5196
|
}),
|
|
5053
5197
|
apply: (state, event) => {
|
|
5054
5198
|
if (event.type === "ya-subagent/started") {
|
|
5055
5199
|
const { callId, childId, profileId } = event.data;
|
|
5056
|
-
const
|
|
5057
|
-
|
|
5058
|
-
[childId]: profileId
|
|
5059
|
-
};
|
|
5060
|
-
const nextCallToChild = {
|
|
5061
|
-
...state.callToChild,
|
|
5062
|
-
[callId]: childId
|
|
5063
|
-
};
|
|
5064
|
-
const nextPending = new Map(state.pending);
|
|
5065
|
-
nextPending.delete(callId);
|
|
5200
|
+
const nextPending = { ...state.pending };
|
|
5201
|
+
delete nextPending[callId];
|
|
5066
5202
|
return {
|
|
5067
5203
|
pending: nextPending,
|
|
5068
|
-
mapping:
|
|
5069
|
-
|
|
5204
|
+
mapping: {
|
|
5205
|
+
...state.mapping,
|
|
5206
|
+
[childId]: profileId
|
|
5207
|
+
},
|
|
5208
|
+
callToChild: {
|
|
5209
|
+
...state.callToChild,
|
|
5210
|
+
[callId]: childId
|
|
5211
|
+
}
|
|
5070
5212
|
};
|
|
5071
5213
|
}
|
|
5072
5214
|
if (event.type === "tool/call" && event.data.name === "subagent") {
|
|
5073
5215
|
const profileId = readProfileId(event.data.arguments);
|
|
5074
5216
|
if (profileId === void 0) return state;
|
|
5075
|
-
const nextPending = new Map(state.pending);
|
|
5076
|
-
nextPending.set(event.data.callId, profileId);
|
|
5077
5217
|
return {
|
|
5078
5218
|
...state,
|
|
5079
|
-
pending:
|
|
5219
|
+
pending: {
|
|
5220
|
+
...state.pending,
|
|
5221
|
+
[event.data.callId]: profileId
|
|
5222
|
+
}
|
|
5080
5223
|
};
|
|
5081
5224
|
}
|
|
5082
5225
|
if (event.type === "tool/result") {
|
|
5083
|
-
const callId = event.data.message.
|
|
5084
|
-
|
|
5085
|
-
const profileId = state.pending.get(callId);
|
|
5226
|
+
const callId = event.data.message.source.callId;
|
|
5227
|
+
const profileId = state.pending[callId];
|
|
5086
5228
|
if (profileId === void 0) return state;
|
|
5087
5229
|
const childId = readChildId(event.data.message);
|
|
5088
|
-
const nextPending =
|
|
5089
|
-
nextPending
|
|
5230
|
+
const nextPending = { ...state.pending };
|
|
5231
|
+
delete nextPending[callId];
|
|
5090
5232
|
if (childId === void 0) return {
|
|
5091
5233
|
...state,
|
|
5092
5234
|
pending: nextPending
|
|
@@ -5105,10 +5247,16 @@ const subagentProfileProjection = {
|
|
|
5105
5247
|
}
|
|
5106
5248
|
return state;
|
|
5107
5249
|
},
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5250
|
+
wire: {
|
|
5251
|
+
viewSchema: object({
|
|
5252
|
+
children: record(string(), string()),
|
|
5253
|
+
calls: record(string(), string())
|
|
5254
|
+
}).strict(),
|
|
5255
|
+
view: memoizeView((state) => ({
|
|
5256
|
+
children: state.mapping,
|
|
5257
|
+
calls: state.callToChild
|
|
5258
|
+
}), (a, b) => recordEqual(a.children, b.children) && recordEqual(a.calls, b.calls))
|
|
5259
|
+
}
|
|
5112
5260
|
};
|
|
5113
5261
|
/** Parse the `profile` field from a `tool/call` arguments JSON string. */
|
|
5114
5262
|
function readProfileId(argumentsRaw) {
|
|
@@ -5213,6 +5361,39 @@ const activitySchema = union([object({
|
|
|
5213
5361
|
name: string(),
|
|
5214
5362
|
args: string().optional()
|
|
5215
5363
|
}).strict()]);
|
|
5364
|
+
const progressViewSchema = object({
|
|
5365
|
+
toolCallCount: number().int().nonnegative(),
|
|
5366
|
+
tokens: object({
|
|
5367
|
+
input: number().int().nonnegative(),
|
|
5368
|
+
output: number().int().nonnegative(),
|
|
5369
|
+
cacheRead: number().int().nonnegative(),
|
|
5370
|
+
cacheWrite: number().int().nonnegative(),
|
|
5371
|
+
reasoning: number().int().nonnegative()
|
|
5372
|
+
}).strict(),
|
|
5373
|
+
state: union([
|
|
5374
|
+
literal("running"),
|
|
5375
|
+
literal("idle"),
|
|
5376
|
+
literal("settled")
|
|
5377
|
+
]),
|
|
5378
|
+
activity: activitySchema.optional()
|
|
5379
|
+
}).strict();
|
|
5380
|
+
/** The fold state is the wire view plus the in-flight streaming accumulator. */
|
|
5381
|
+
const progressStateSchema = progressViewSchema.extend({ streamingText: string() });
|
|
5382
|
+
/** Content equality for the progress view's token totals. */
|
|
5383
|
+
function tokensEqual(a, b) {
|
|
5384
|
+
return a.input === b.input && a.output === b.output && a.cacheRead === b.cacheRead && a.cacheWrite === b.cacheWrite && a.reasoning === b.reasoning;
|
|
5385
|
+
}
|
|
5386
|
+
/** Content equality for the progress view's activity union. */
|
|
5387
|
+
function activityEqual(a, b) {
|
|
5388
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
5389
|
+
if (a.kind === "text" && b.kind === "text") return a.text === b.text;
|
|
5390
|
+
if (a.kind === "tool" && b.kind === "tool") return a.name === b.name && a.args === b.args;
|
|
5391
|
+
return false;
|
|
5392
|
+
}
|
|
5393
|
+
/** Content equality for the whole progress view (per-field, no identity). */
|
|
5394
|
+
function progressViewEqual(a, b) {
|
|
5395
|
+
return a.toolCallCount === b.toolCallCount && a.state === b.state && tokensEqual(a.tokens, b.tokens) && activityEqual(a.activity, b.activity);
|
|
5396
|
+
}
|
|
5216
5397
|
/**
|
|
5217
5398
|
* Fold the child session's own events into a compact progress view. Token
|
|
5218
5399
|
* usage accumulates from `assistant/message.usage` (cache fields are
|
|
@@ -5220,23 +5401,8 @@ const activitySchema = union([object({
|
|
|
5220
5401
|
*/
|
|
5221
5402
|
const yaSubagentProgressProjection = {
|
|
5222
5403
|
key: "yaSubagentProgress",
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
tokens: object({
|
|
5226
|
-
input: number().int().nonnegative(),
|
|
5227
|
-
output: number().int().nonnegative(),
|
|
5228
|
-
cacheRead: number().int().nonnegative(),
|
|
5229
|
-
cacheWrite: number().int().nonnegative(),
|
|
5230
|
-
reasoning: number().int().nonnegative()
|
|
5231
|
-
}).strict(),
|
|
5232
|
-
state: union([
|
|
5233
|
-
literal("running"),
|
|
5234
|
-
literal("idle"),
|
|
5235
|
-
literal("settled")
|
|
5236
|
-
]),
|
|
5237
|
-
activity: activitySchema.optional()
|
|
5238
|
-
}).strict(),
|
|
5239
|
-
stateVersion: 2,
|
|
5404
|
+
stateSchema: progressStateSchema,
|
|
5405
|
+
stateVersion: 3,
|
|
5240
5406
|
init: () => ({
|
|
5241
5407
|
toolCallCount: 0,
|
|
5242
5408
|
tokens: {
|
|
@@ -5319,9 +5485,12 @@ const yaSubagentProgressProjection = {
|
|
|
5319
5485
|
}
|
|
5320
5486
|
return state;
|
|
5321
5487
|
},
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5488
|
+
wire: {
|
|
5489
|
+
viewSchema: progressViewSchema,
|
|
5490
|
+
view: memoizeView((state) => {
|
|
5491
|
+
const { streamingText: _, ...rest } = state;
|
|
5492
|
+
return rest;
|
|
5493
|
+
}, progressViewEqual)
|
|
5325
5494
|
}
|
|
5326
5495
|
};
|
|
5327
5496
|
//#endregion
|
|
@@ -5335,7 +5504,7 @@ const inject = [
|
|
|
5335
5504
|
"connection"
|
|
5336
5505
|
];
|
|
5337
5506
|
/** Settings namespace under which profile state persists (`$DSH_HOME/settings.yaml`). */
|
|
5338
|
-
const SETTINGS_NAMESPACE =
|
|
5507
|
+
const SETTINGS_NAMESPACE = "ya-subagent";
|
|
5339
5508
|
/** Schemastery schema for one profile (config layer). */
|
|
5340
5509
|
const SubagentProfileSchema = z.object({
|
|
5341
5510
|
id: z.string().required().description("Unique profile id (lowercase letters, digits, hyphens; 1-32 chars)."),
|
|
@@ -35,15 +35,33 @@ export interface YaSubagentSettingsInjected {
|
|
|
35
35
|
readonly rpc: ClientConnectionRpc;
|
|
36
36
|
/** Refetch the profile list from the host. */
|
|
37
37
|
readonly fetchProfiles: () => Promise<readonly SubagentProfile[]>;
|
|
38
|
+
/** Refetch the routable model catalog (provider groups); undefined on failure. */
|
|
39
|
+
readonly fetchModelCatalog: () => Promise<ModelCatalogData | undefined>;
|
|
38
40
|
/** Bound locale translator for the ya-subagent namespace. */
|
|
39
41
|
readonly t: (key: string) => string;
|
|
40
42
|
}
|
|
41
43
|
/** Full props: settings.section runtime share + locale seat + inject. */
|
|
42
44
|
type SettingsPageProps = PropsRuntime<'settings.section'> & PropsLocale<'ya-subagent'> & YaSubagentSettingsInjected;
|
|
45
|
+
/** One model inside a provider group (mirrors `ModelCatalogModel`). */
|
|
46
|
+
interface ModelEntry {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly description?: string;
|
|
50
|
+
}
|
|
51
|
+
/** One provider group (mirrors `ModelProviderGroup`). */
|
|
52
|
+
interface ModelGroup {
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly models: readonly ModelEntry[];
|
|
56
|
+
}
|
|
57
|
+
/** Model catalog wire shape (mirror of the host `ModelCatalog` read face). */
|
|
58
|
+
interface ModelCatalogData {
|
|
59
|
+
readonly groups: readonly ModelGroup[];
|
|
60
|
+
}
|
|
43
61
|
/**
|
|
44
62
|
* Render the subagent profiles settings page.
|
|
45
63
|
* @param props - settings.section runtime share + locale + inject.
|
|
46
64
|
* @returns the page element.
|
|
47
65
|
*/
|
|
48
|
-
export declare function SettingsPage({ rpc, fetchProfiles, t }: SettingsPageProps): import("react").JSX.Element;
|
|
66
|
+
export declare function SettingsPage({ rpc, fetchProfiles, fetchModelCatalog, t }: SettingsPageProps): import("react").JSX.Element;
|
|
49
67
|
export {};
|
|
@@ -20,10 +20,11 @@ export interface SubagentCardSessions {
|
|
|
20
20
|
binding(id: string): {
|
|
21
21
|
session: {
|
|
22
22
|
projections: {
|
|
23
|
+
/** Absence of a value is an `undefined` snapshot, never a missing face. */
|
|
23
24
|
faceOf(key: string): {
|
|
24
25
|
getSnapshot(): unknown;
|
|
25
26
|
subscribe(fn: () => void): () => void;
|
|
26
|
-
}
|
|
27
|
+
};
|
|
27
28
|
};
|
|
28
29
|
};
|
|
29
30
|
} | undefined;
|
|
@@ -26,10 +26,11 @@ interface TreeSessions {
|
|
|
26
26
|
binding(id: string): {
|
|
27
27
|
session: {
|
|
28
28
|
projections: {
|
|
29
|
+
/** Absence of a value is an `undefined` snapshot, never a missing face. */
|
|
29
30
|
faceOf(key: string): {
|
|
30
31
|
getSnapshot(): unknown;
|
|
31
32
|
subscribe(fn: () => void): () => void;
|
|
32
|
-
}
|
|
33
|
+
};
|
|
33
34
|
};
|
|
34
35
|
};
|
|
35
36
|
} | undefined;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module @huanlin/dsh-plugin-yet-another-subagent/client
|
|
14
14
|
*/
|
|
15
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
15
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
16
16
|
import { type YaSubagentKey } from './locales.ts';
|
|
17
17
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
18
18
|
interface LocaleNamespaceMap {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module @huanlin/dsh-plugin-yet-another-subagent/client/locales
|
|
5
5
|
*/
|
|
6
6
|
/** All copy keys for the ya-subagent namespace. */
|
|
7
|
-
export type YaSubagentKey = 'nav' | 'page.title' | 'page.empty' | 'page.add' | 'page.add.placeholder.id' | 'page.add.placeholder.label' | 'page.add.submit' | 'page.add.error' | 'page.add.cancel' | 'row.label' | 'row.id' | 'row.model.kind.auto' | 'row.model.kind.manual' | 'row.model.provider' | 'row.model.model' | 'row.model.provider.placeholder' | 'row.model.model.placeholder' | 'row.model.noModels' | 'row.persona' | 'row.persona.kind.inherit' | 'row.persona.kind.custom' | 'row.persona.text' | 'row.toolFilter' | 'row.toolFilter.kind.none' | 'row.toolFilter.kind.allow' | 'row.toolFilter.kind.deny' | 'row.toolFilter.tools' | 'row.toolFilter.tools.search' | 'row.toolFilter.tools.empty' | 'row.toolFilter.tools.selected' | 'row.toolFilter.tools.selectAll' | 'row.toolFilter.tools.clear' | 'row.maxDepth' | 'row.delete' | 'row.delete.confirm' | 'row.save' | 'row.saved' | 'row.error' | 'row.expand' | 'row.collapse' | 'badge.builtin' | 'card.starting' | 'card.waiting' | 'card.idle' | 'card.running' | 'card.completed' | 'card.child-running' | 'card.child-idle' | 'card.toolcalls' | 'card.tokens' | 'card.calling' | 'card.open' | 'card.unavailable' | 'tree.tab' | 'tree.empty' | 'tree.rootHint' | 'tree.toolcalls' | 'tree.tokens' | 'tree.calling' | 'tree.state.running' | 'tree.state.idle' | 'tree.state.settled' | 'repair.button' | 'repair.confirm.title' | 'repair.confirm.body' | 'repair.confirm.warning' | 'repair.confirm.cancel' | 'repair.confirm.proceed' | 'repair.running' | 'repair.result.title' | 'repair.result.scanned' | 'repair.result.repaired' | 'repair.result.skipped' | 'repair.result.errors' | 'repair.result.errorEntry' | 'repair.result.close' | 'repair.error';
|
|
7
|
+
export type YaSubagentKey = 'common.close' | 'nav' | 'page.title' | 'page.empty' | 'page.add' | 'page.add.placeholder.id' | 'page.add.placeholder.label' | 'page.add.submit' | 'page.add.error' | 'page.add.cancel' | 'row.label' | 'row.id' | 'row.model.kind.auto' | 'row.model.kind.manual' | 'row.model.provider' | 'row.model.model' | 'row.model.provider.placeholder' | 'row.model.model.placeholder' | 'row.model.noModels' | 'row.persona' | 'row.persona.kind.inherit' | 'row.persona.kind.custom' | 'row.persona.text' | 'row.toolFilter' | 'row.toolFilter.kind.none' | 'row.toolFilter.kind.allow' | 'row.toolFilter.kind.deny' | 'row.toolFilter.tools' | 'row.toolFilter.tools.search' | 'row.toolFilter.tools.empty' | 'row.toolFilter.tools.selected' | 'row.toolFilter.tools.selectAll' | 'row.toolFilter.tools.clear' | 'row.maxDepth' | 'row.delete' | 'row.delete.confirm' | 'row.save' | 'row.saved' | 'row.error' | 'row.expand' | 'row.collapse' | 'badge.builtin' | 'card.starting' | 'card.waiting' | 'card.idle' | 'card.running' | 'card.completed' | 'card.child-running' | 'card.child-idle' | 'card.toolcalls' | 'card.tokens' | 'card.calling' | 'card.open' | 'card.unavailable' | 'tree.tab' | 'tree.empty' | 'tree.rootHint' | 'tree.toolcalls' | 'tree.tokens' | 'tree.calling' | 'tree.state.running' | 'tree.state.idle' | 'tree.state.settled' | 'repair.button' | 'repair.confirm.title' | 'repair.confirm.body' | 'repair.confirm.warning' | 'repair.confirm.cancel' | 'repair.confirm.proceed' | 'repair.running' | 'repair.result.title' | 'repair.result.scanned' | 'repair.result.repaired' | 'repair.result.skipped' | 'repair.result.errors' | 'repair.result.errorEntry' | 'repair.result.close' | 'repair.error';
|
|
8
8
|
/** Locale namespace id. */
|
|
9
9
|
export declare const NS = "ya-subagent";
|
|
10
10
|
/** English dictionary. */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const name = "yet-another-subagent";
|
|
|
23
23
|
export declare const inject: string[];
|
|
24
24
|
export type { SubagentProfile, YaSubagentConfig } from './types.ts';
|
|
25
25
|
/** Settings namespace under which profile state persists (`$DSH_HOME/settings.yaml`). */
|
|
26
|
-
export declare const SETTINGS_NAMESPACE
|
|
26
|
+
export declare const SETTINGS_NAMESPACE = "ya-subagent";
|
|
27
27
|
export interface Config extends YaSubagentConfig {
|
|
28
28
|
}
|
|
29
29
|
export declare const Config: z<Config>;
|