@huanlin/dsh-plugin-yet-another-subagent 0.1.2 → 0.1.4
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 +136 -130
- package/lib/client.js +145 -3
- package/lib/index.js +289 -27
- package/lib/types/client/SettingsPage.d.ts +49 -0
- package/lib/types/client/SubagentCard.d.ts +56 -0
- package/lib/types/client/SubagentTreeView.d.ts +63 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +13 -0
- package/lib/types/index.d.ts +42 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/profile-store.d.ts +59 -0
- package/lib/types/projection.d.ts +103 -0
- package/lib/types/repair.d.ts +49 -0
- package/lib/types/rpc.d.ts +59 -0
- package/lib/types/tool-factory.d.ts +45 -0
- package/lib/types/types.d.ts +116 -0
- package/package.json +159 -158
package/lib/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import z from "schemastery";
|
|
|
2
2
|
import { assertSubagentMaxDepth, settleRun } from "@deepseek-ai/dsh-subagent";
|
|
3
3
|
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
4
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
|
+
import { constants, zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
6
|
+
import { copyFile, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
5
8
|
//#region src/types.ts
|
|
6
9
|
/**
|
|
7
10
|
* Coerce a possibly-stale profile shape (from an older `settings.yaml` or a
|
|
@@ -473,13 +476,7 @@ function buildTool(profiles, ctx) {
|
|
|
473
476
|
...request,
|
|
474
477
|
signal: controller.signal
|
|
475
478
|
});
|
|
476
|
-
start.
|
|
477
|
-
parent.session.append("ya-subagent/started", {
|
|
478
|
-
callId: exec.callId,
|
|
479
|
-
childId: String(run.id),
|
|
480
|
-
profileId: profile.id
|
|
481
|
-
});
|
|
482
|
-
}).catch(() => {});
|
|
479
|
+
start.catch(() => {});
|
|
483
480
|
return {
|
|
484
481
|
cancel: (reason) => {
|
|
485
482
|
controller.abort(reason ?? "background subagent task killed");
|
|
@@ -497,33 +494,21 @@ function buildTool(profiles, ctx) {
|
|
|
497
494
|
const spawn = ctx.subagents.getProvider("spawn");
|
|
498
495
|
if (spawn === void 0) throw new Error("subagent spawn provider not available; load @deepseek-ai/dsh-subagent-spawn-in-process");
|
|
499
496
|
if (spawn.prepareContinuable === void 0) throw new Error("subagent spawn provider does not support continuable children");
|
|
500
|
-
const started = await ctx.subagents.startContinuable({
|
|
501
|
-
provider: "spawn",
|
|
502
|
-
label: args.description,
|
|
503
|
-
request,
|
|
504
|
-
signal: exec.signal
|
|
505
|
-
});
|
|
506
|
-
parent.session.append("ya-subagent/started", {
|
|
507
|
-
callId: exec.callId,
|
|
508
|
-
childId: started.childId,
|
|
509
|
-
profileId: profile.id
|
|
510
|
-
});
|
|
511
497
|
return {
|
|
512
498
|
kind: "continuable",
|
|
513
|
-
subagentId:
|
|
499
|
+
subagentId: (await ctx.subagents.startContinuable({
|
|
500
|
+
provider: "spawn",
|
|
501
|
+
label: args.description,
|
|
502
|
+
request,
|
|
503
|
+
signal: exec.signal
|
|
504
|
+
})).childId,
|
|
514
505
|
profileLabel: profile.label
|
|
515
506
|
};
|
|
516
507
|
}
|
|
517
|
-
const
|
|
508
|
+
const result = await settleForegroundRun(await ctx.subagents.start("spawn", {
|
|
518
509
|
...request,
|
|
519
510
|
signal: exec.signal
|
|
520
|
-
});
|
|
521
|
-
parent.session.append("ya-subagent/started", {
|
|
522
|
-
callId: exec.callId,
|
|
523
|
-
childId: String(run.id),
|
|
524
|
-
profileId: profile.id
|
|
525
|
-
});
|
|
526
|
-
const result = await settleForegroundRun(run);
|
|
511
|
+
}));
|
|
527
512
|
return {
|
|
528
513
|
kind: "foreground",
|
|
529
514
|
runId: result.runId,
|
|
@@ -534,6 +519,273 @@ function buildTool(profiles, ctx) {
|
|
|
534
519
|
});
|
|
535
520
|
}
|
|
536
521
|
//#endregion
|
|
522
|
+
//#region src/repair.ts
|
|
523
|
+
/**
|
|
524
|
+
* One-shot session-log repair: stamp `"ignorable": true` onto legacy
|
|
525
|
+
* `ya-subagent/started` events so the harness persistence read path
|
|
526
|
+
* (`assertEventsSupported`) will skip them instead of refusing the whole log.
|
|
527
|
+
*
|
|
528
|
+
* Background: older plugin versions wrote `ya-subagent/started` via
|
|
529
|
+
* `session.append(...)`, but `session.append` cannot set the `ignorable`
|
|
530
|
+
* envelope flag, and `KNOWN_SESSION_EVENT_TYPES` is code-generated with no
|
|
531
|
+
* plugin registration surface. The read path therefore refuses any log
|
|
532
|
+
* containing the type unless each occurrence carries `ignorable: true`.
|
|
533
|
+
* This module rewrites on-disk artifacts in place (after a `.bak` backup) to
|
|
534
|
+
* add that flag to every `ya-subagent/started` row missing it.
|
|
535
|
+
*
|
|
536
|
+
* Two physical encodings (mirrors `session-persistence-jsonl`):
|
|
537
|
+
* - `.jsonl` — plaintext, one JSON record per line.
|
|
538
|
+
* - `.jsonl.zstd` — concatenated independent Zstandard frames: the first
|
|
539
|
+
* frame holds the session header line, subsequent
|
|
540
|
+
* frames each hold one append batch of event lines.
|
|
541
|
+
* Each frame is independently decodable + checksummed.
|
|
542
|
+
* Only frames whose decoded plaintext contains a target
|
|
543
|
+
* row are recompressed; untouched frames are copied
|
|
544
|
+
* verbatim so byte-identity is preserved where possible.
|
|
545
|
+
*
|
|
546
|
+
* Idempotent: rows already carrying `ignorable: true` are skipped; files with
|
|
547
|
+
* no target rows are left untouched (no backup, no rewrite).
|
|
548
|
+
*
|
|
549
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/repair
|
|
550
|
+
*/
|
|
551
|
+
/** The event type this module targets. */
|
|
552
|
+
const TARGET_TYPE = "ya-subagent/started";
|
|
553
|
+
/** Zstandard magic number (little-endian 0xFD2FB528). */
|
|
554
|
+
const ZSTD_MAGIC = 4247762216;
|
|
555
|
+
/** Compression options matching the harness's `CHECKSUM_OPTIONS`. */
|
|
556
|
+
const CHECKSUM_OPTIONS = { params: { [constants.ZSTD_c_checksumFlag]: 1 } };
|
|
557
|
+
/**
|
|
558
|
+
* Recursively repair every session log under `sessionsRoot`.
|
|
559
|
+
*
|
|
560
|
+
* @param sessionsRoot - absolute path to `$DSH_HOME/sessions`.
|
|
561
|
+
* @returns aggregate stats. Never throws — per-file failures land in `errors`.
|
|
562
|
+
*/
|
|
563
|
+
async function repairSessions(sessionsRoot) {
|
|
564
|
+
const errors = [];
|
|
565
|
+
let scanned = 0;
|
|
566
|
+
let repaired = 0;
|
|
567
|
+
let skipped = 0;
|
|
568
|
+
const visit = async (dir) => {
|
|
569
|
+
let entries;
|
|
570
|
+
try {
|
|
571
|
+
entries = await readdir(dir);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
errors.push({
|
|
574
|
+
path: dir,
|
|
575
|
+
message: errorMessage(err)
|
|
576
|
+
});
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
await Promise.all(entries.map(async (name) => {
|
|
580
|
+
const path = join(dir, name);
|
|
581
|
+
let isDir;
|
|
582
|
+
let isFile;
|
|
583
|
+
try {
|
|
584
|
+
const info = await stat(path);
|
|
585
|
+
isDir = info.isDirectory();
|
|
586
|
+
isFile = info.isFile();
|
|
587
|
+
} catch (err) {
|
|
588
|
+
errors.push({
|
|
589
|
+
path,
|
|
590
|
+
message: `stat failed: ${errorMessage(err)}`
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (isDir) {
|
|
595
|
+
await visit(path);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (!isFile) return;
|
|
599
|
+
const isJsonl = name.endsWith(".jsonl");
|
|
600
|
+
const isZstd = name.endsWith(".jsonl.zstd");
|
|
601
|
+
if (!isJsonl && !isZstd) return;
|
|
602
|
+
scanned += 1;
|
|
603
|
+
try {
|
|
604
|
+
const outcome = isJsonl ? await repairPlaintextFile(path) : await repairZstdFile(path);
|
|
605
|
+
if (outcome.kind === "repaired") {
|
|
606
|
+
await writeFile(path, outcome.bytes);
|
|
607
|
+
repaired += 1;
|
|
608
|
+
} else if (outcome.kind === "clean") skipped += 1;
|
|
609
|
+
} catch (err) {
|
|
610
|
+
errors.push({
|
|
611
|
+
path,
|
|
612
|
+
message: errorMessage(err)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}));
|
|
616
|
+
};
|
|
617
|
+
try {
|
|
618
|
+
await visit(sessionsRoot);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
errors.push({
|
|
621
|
+
path: sessionsRoot,
|
|
622
|
+
message: errorMessage(err)
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
scanned,
|
|
627
|
+
repaired,
|
|
628
|
+
skipped,
|
|
629
|
+
errors
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Repair one `.jsonl` plaintext file. Backs up to `.bak` first if a repair is
|
|
634
|
+
* needed and no backup exists yet.
|
|
635
|
+
*/
|
|
636
|
+
async function repairPlaintextFile(path) {
|
|
637
|
+
const { lines, changed } = patchPlaintextLines(await readFile(path, "utf8"));
|
|
638
|
+
if (!changed) return { kind: "clean" };
|
|
639
|
+
await ensureBackup(path);
|
|
640
|
+
return {
|
|
641
|
+
kind: "repaired",
|
|
642
|
+
bytes: Buffer.from(lines, "utf8")
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Repair one `.jsonl.zstd` concatenated-frame file. The header frame is
|
|
647
|
+
* decoded to check for a target row (current harness writes the header as its
|
|
648
|
+
* own frame, so a target there is theoretically possible but unlikely); event
|
|
649
|
+
* frames are decoded and patched individually. Only frames with a patch are
|
|
650
|
+
* recompressed; untouched frames are copied verbatim.
|
|
651
|
+
*/
|
|
652
|
+
async function repairZstdFile(path) {
|
|
653
|
+
const buffer = await readFile(path);
|
|
654
|
+
const frames = scanZstdFrames(buffer);
|
|
655
|
+
if (frames.length === 0) return { kind: "clean" };
|
|
656
|
+
const rebuilt = [];
|
|
657
|
+
let changed = false;
|
|
658
|
+
for (const frame of frames) {
|
|
659
|
+
const frameBytes = buffer.subarray(frame.start, frame.end);
|
|
660
|
+
const { lines, changed: frameChanged } = patchPlaintextLines(zstdDecompressSync(frameBytes).toString("utf8"));
|
|
661
|
+
if (frameChanged) {
|
|
662
|
+
changed = true;
|
|
663
|
+
rebuilt.push(zstdCompressSync(Buffer.from(lines, "utf8"), CHECKSUM_OPTIONS));
|
|
664
|
+
} else rebuilt.push(Buffer.from(frameBytes));
|
|
665
|
+
}
|
|
666
|
+
if (!changed) return { kind: "clean" };
|
|
667
|
+
await ensureBackup(path);
|
|
668
|
+
return {
|
|
669
|
+
kind: "repaired",
|
|
670
|
+
bytes: Buffer.concat(rebuilt)
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Patch every `ya-subagent/started` line missing `ignorable` by inserting
|
|
675
|
+
* `"ignorable":true` into the JSON object. Returns the new text and whether
|
|
676
|
+
* any line changed. Lines that fail to parse as JSON are left untouched
|
|
677
|
+
* (a corrupt line is the harness's refusal job, not ours).
|
|
678
|
+
*/
|
|
679
|
+
function patchPlaintextLines(text) {
|
|
680
|
+
const lines = text.split("\n");
|
|
681
|
+
let changed = false;
|
|
682
|
+
for (let i = 0; i < lines.length; i++) {
|
|
683
|
+
const line = lines[i];
|
|
684
|
+
if (!line) continue;
|
|
685
|
+
if (!line.includes(TARGET_TYPE)) continue;
|
|
686
|
+
let parsed;
|
|
687
|
+
try {
|
|
688
|
+
parsed = JSON.parse(line);
|
|
689
|
+
} catch {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (typeof parsed !== "object" || parsed === null) continue;
|
|
693
|
+
const record = parsed;
|
|
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}";
|
|
699
|
+
changed = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
lines: lines.join("\n"),
|
|
704
|
+
changed
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Locate complete Zstandard frames in a concatenated stream. A structurally
|
|
709
|
+
* incomplete final frame (torn tail from a concurrent writer) is skipped —
|
|
710
|
+
* repairing it would risk data loss, and the harness treats it as a torn tail
|
|
711
|
+
* too. Mirrors `scanZstdFrames` in `session-persistence-jsonl/src/zstd.ts`.
|
|
712
|
+
*/
|
|
713
|
+
function scanZstdFrames(buffer) {
|
|
714
|
+
const frames = [];
|
|
715
|
+
let offset = 0;
|
|
716
|
+
while (offset < buffer.length) {
|
|
717
|
+
const start = offset;
|
|
718
|
+
if (buffer.length - offset < 4) break;
|
|
719
|
+
if (buffer.readUInt32LE(offset) !== ZSTD_MAGIC) break;
|
|
720
|
+
offset += 4;
|
|
721
|
+
if (offset === buffer.length) break;
|
|
722
|
+
const descriptor = buffer.readUInt8(offset);
|
|
723
|
+
offset += 1;
|
|
724
|
+
if ((descriptor & 24) !== 0) break;
|
|
725
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
726
|
+
const singleSegment = (descriptor & 32) !== 0;
|
|
727
|
+
const checksum = (descriptor & 4) !== 0;
|
|
728
|
+
const dictionaryFlag = descriptor & 3;
|
|
729
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
730
|
+
const contentSizeBytes = contentSizeFlag === 0 ? singleSegment ? 1 : 0 : 1 << contentSizeFlag;
|
|
731
|
+
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
732
|
+
if (buffer.length - offset < remainingHeaderBytes) break;
|
|
733
|
+
offset += remainingHeaderBytes;
|
|
734
|
+
let lastBlock = false;
|
|
735
|
+
while (!lastBlock) {
|
|
736
|
+
if (buffer.length - offset < 3) {
|
|
737
|
+
offset = start;
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
const blockHeader = buffer.readUIntLE(offset, 3);
|
|
741
|
+
offset += 3;
|
|
742
|
+
lastBlock = (blockHeader & 1) !== 0;
|
|
743
|
+
const blockType = blockHeader >>> 1 & 3;
|
|
744
|
+
const blockSize = blockHeader >>> 3;
|
|
745
|
+
if (blockType === 3) {
|
|
746
|
+
offset = start;
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize;
|
|
750
|
+
if (buffer.length - offset < payloadBytes) {
|
|
751
|
+
offset = start;
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
offset += payloadBytes;
|
|
755
|
+
}
|
|
756
|
+
if (offset === start) break;
|
|
757
|
+
if (checksum) {
|
|
758
|
+
if (buffer.length - offset < 4) {
|
|
759
|
+
offset = start;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
offset += 4;
|
|
763
|
+
}
|
|
764
|
+
frames.push({
|
|
765
|
+
start,
|
|
766
|
+
end: offset
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
return frames;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Copy `path` to `path.bak` if no backup exists yet. A concurrent repair run
|
|
773
|
+
* leaves the original backup in place; a pre-existing `.bak` from another tool
|
|
774
|
+
* is also preserved.
|
|
775
|
+
*/
|
|
776
|
+
async function ensureBackup(path) {
|
|
777
|
+
const backup = `${path}.bak`;
|
|
778
|
+
try {
|
|
779
|
+
await stat(backup);
|
|
780
|
+
return;
|
|
781
|
+
} catch {}
|
|
782
|
+
await copyFile(path, backup);
|
|
783
|
+
}
|
|
784
|
+
/** Extract a human-readable message from an unknown error. */
|
|
785
|
+
function errorMessage(err) {
|
|
786
|
+
return err instanceof Error ? err.message : String(err);
|
|
787
|
+
}
|
|
788
|
+
//#endregion
|
|
537
789
|
//#region src/rpc.ts
|
|
538
790
|
/** Build an RPC ok branch. */
|
|
539
791
|
function ok(value) {
|
|
@@ -588,6 +840,16 @@ function registerRpc(ctx, store) {
|
|
|
588
840
|
name: s.name,
|
|
589
841
|
description: s.description
|
|
590
842
|
})) });
|
|
843
|
+
case "sessions.repair": {
|
|
844
|
+
const dshHomePath = ctx.get("dshHomePath");
|
|
845
|
+
const sessionsRoot = dshHomePath !== void 0 ? dshHomePath("sessions") : void 0;
|
|
846
|
+
if (sessionsRoot === void 0) return fail("dshHomePath provider unavailable; cannot resolve sessions root");
|
|
847
|
+
try {
|
|
848
|
+
return ok(await repairSessions(sessionsRoot));
|
|
849
|
+
} catch (err) {
|
|
850
|
+
return fail(`session repair failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
591
853
|
default: return fail(`unknown endpoint: ${endpoint}`);
|
|
592
854
|
}
|
|
593
855
|
}, { authority: "trusted-host" });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SettingsPage — the `ya-subagent` settings section: profile list CRUD.
|
|
3
|
+
*
|
|
4
|
+
* Visual language: matches ModelsSection / GeneralSection — outlined rowCard
|
|
5
|
+
* per profile (border-l2, r12, p12/14), filled editor surface
|
|
6
|
+
* (bg-module-platform, r12, p14/16), capsule controls (h36 r18 primary,
|
|
7
|
+
* h28 r14 secondary), 32px fields with border-l2 / bg-layer-1, 12/18 caption
|
|
8
|
+
* labels. Every color resolves through --dsw-alias-* tokens.
|
|
9
|
+
*
|
|
10
|
+
* Each profile card is collapsible (chevron in the row head); the editor
|
|
11
|
+
* surface is hidden when collapsed. Builtin profiles (cordis.yml seed) carry
|
|
12
|
+
* a `builtin`/`内置` badge next to the title. The "+ Add subagent" button at
|
|
13
|
+
* the bottom reveals an inline draft card with all fields editable (including
|
|
14
|
+
* id) and Create / Cancel actions.
|
|
15
|
+
*
|
|
16
|
+
* The persona field is a radio (inherit deployment persona vs custom text);
|
|
17
|
+
* the textarea is shown only when custom. The tool filter is a select
|
|
18
|
+
* (none / allow / deny); a multi-select dropdown is shown only when allow or
|
|
19
|
+
* deny is picked, populated from `tools.list` (the host's current
|
|
20
|
+
* `ctx.tools.schemas()`).
|
|
21
|
+
*
|
|
22
|
+
* Pulls the profile list once on mount via `connection.rpc.call('/ya-subagent',
|
|
23
|
+
* 'profiles.list')`, dispatches add/update/remove through the
|
|
24
|
+
* same RPC. The toolview slot is keyed by `subagent` and registered once at
|
|
25
|
+
* plugin load, so profile mutations do not need to re-register slots.
|
|
26
|
+
*
|
|
27
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SettingsPage
|
|
28
|
+
*/
|
|
29
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
30
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
31
|
+
import type { ClientConnectionRpc } from '@deepseek-ai/dsh-client-connection/client';
|
|
32
|
+
import type { SubagentProfile } from '../types.ts';
|
|
33
|
+
/** Inject face: RPC handle + locale translate. */
|
|
34
|
+
export interface YaSubagentSettingsInjected {
|
|
35
|
+
readonly rpc: ClientConnectionRpc;
|
|
36
|
+
/** Refetch the profile list from the host. */
|
|
37
|
+
readonly fetchProfiles: () => Promise<readonly SubagentProfile[]>;
|
|
38
|
+
/** Bound locale translator for the ya-subagent namespace. */
|
|
39
|
+
readonly t: (key: string) => string;
|
|
40
|
+
}
|
|
41
|
+
/** Full props: settings.section runtime share + locale seat + inject. */
|
|
42
|
+
type SettingsPageProps = PropsRuntime<'settings.section'> & PropsLocale<'ya-subagent'> & YaSubagentSettingsInjected;
|
|
43
|
+
/**
|
|
44
|
+
* Render the subagent profiles settings page.
|
|
45
|
+
* @param props - settings.section runtime share + locale + inject.
|
|
46
|
+
* @returns the page element.
|
|
47
|
+
*/
|
|
48
|
+
export declare function SettingsPage({ rpc, fetchProfiles, t }: SettingsPageProps): import("react").JSX.Element;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubagentCard — the model-facing toolcall card for the `subagent` tool.
|
|
3
|
+
*
|
|
4
|
+
* Three display branches:
|
|
5
|
+
* 1. **Running** (block is `RunningToolCall`): the tool call is in flight.
|
|
6
|
+
* Show "running" with a spinner dot; no child session to subscribe to.
|
|
7
|
+
* 2. **Continuable settled** (result text matches `started <label>
|
|
8
|
+
* subagent <id>`): subscribe to the child's `yaSubagentProgress`
|
|
9
|
+
* projection for live toolcall/token counts; clickable to open.
|
|
10
|
+
* 3. **Foreground settled** (result text is the child's output): the
|
|
11
|
+
* one-shot child has completed; show "completed" with an output
|
|
12
|
+
* preview. No child session survives.
|
|
13
|
+
*
|
|
14
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentCard
|
|
15
|
+
*/
|
|
16
|
+
import type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client';
|
|
17
|
+
import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
18
|
+
/** Sessions service shape consumed by this card (narrow face of ISessions). */
|
|
19
|
+
export interface SubagentCardSessions {
|
|
20
|
+
binding(id: string): {
|
|
21
|
+
session: {
|
|
22
|
+
projections: {
|
|
23
|
+
faceOf(key: string): {
|
|
24
|
+
getSnapshot(): unknown;
|
|
25
|
+
subscribe(fn: () => void): () => void;
|
|
26
|
+
} | undefined;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
} | undefined;
|
|
30
|
+
openSubagent(address: {
|
|
31
|
+
parentSessionId: string;
|
|
32
|
+
childSessionId: string;
|
|
33
|
+
mode: 'continuable' | 'one-shot';
|
|
34
|
+
}): void;
|
|
35
|
+
subagentAddress(id: string): {
|
|
36
|
+
parentSessionId: string;
|
|
37
|
+
childSessionId: string;
|
|
38
|
+
mode: 'continuable' | 'one-shot';
|
|
39
|
+
} | undefined;
|
|
40
|
+
refreshSubagents(parentSessionId: string): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/** Inject face: the sessions service handle + profile label lookup. */
|
|
43
|
+
export type SubagentCardInjected = {
|
|
44
|
+
sessions: SubagentCardSessions;
|
|
45
|
+
/** Resolve a profile id to its display label; undefined if unknown. */
|
|
46
|
+
profileLabelOf: (id: string) => string | undefined;
|
|
47
|
+
};
|
|
48
|
+
/** Full props: toolview runtime share + this package's locale seat + inject. */
|
|
49
|
+
type SubagentCardProps = ToolCallViewProps & PropsLocale<'ya-subagent'> & InjectFace<SubagentCardInjected>;
|
|
50
|
+
/**
|
|
51
|
+
* Render one `subagent` tool call as a compact live card.
|
|
52
|
+
* @param props - keyed toolview payload + locale seat + sessions inject.
|
|
53
|
+
* @returns the dedicated subagent card.
|
|
54
|
+
*/
|
|
55
|
+
export declare function SubagentCard({ block, callId, toolName, sessionId, sessions, profileLabelOf, t }: SubagentCardProps): import("react").JSX.Element;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubagentTreeView — a `conversation.view` entry showing the root session's
|
|
3
|
+
* full subagent tree (all depths) with live progress.
|
|
4
|
+
*
|
|
5
|
+
* Uses `sessions.subagentsByParent` (the catalog) as the primary tree
|
|
6
|
+
* structure source — this works for ALL depths without needing per-session
|
|
7
|
+
* bindings. `setSubagentCatalogOpen` keeps catalogs auto-refreshing.
|
|
8
|
+
* Projections (`yaSubagentProgress`) are used additionally when a session
|
|
9
|
+
* binding is available (current session + opened children) for richer data.
|
|
10
|
+
*
|
|
11
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentTreeView
|
|
12
|
+
*/
|
|
13
|
+
import type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
14
|
+
import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
15
|
+
/** Catalog entry shape (narrow face of SubagentListEntry). */
|
|
16
|
+
interface CatalogEntry {
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly mode?: string;
|
|
20
|
+
readonly activity?: string;
|
|
21
|
+
readonly hasChildren?: boolean;
|
|
22
|
+
readonly label?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Sessions service shape consumed by this view. */
|
|
25
|
+
interface TreeSessions {
|
|
26
|
+
binding(id: string): {
|
|
27
|
+
session: {
|
|
28
|
+
projections: {
|
|
29
|
+
faceOf(key: string): {
|
|
30
|
+
getSnapshot(): unknown;
|
|
31
|
+
subscribe(fn: () => void): () => void;
|
|
32
|
+
} | undefined;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
} | undefined;
|
|
36
|
+
openSubagent(address: {
|
|
37
|
+
parentSessionId: string;
|
|
38
|
+
childSessionId: string;
|
|
39
|
+
mode: 'continuable' | 'one-shot';
|
|
40
|
+
}): void;
|
|
41
|
+
subagentAddress(id: string): {
|
|
42
|
+
parentSessionId: string;
|
|
43
|
+
childSessionId: string;
|
|
44
|
+
mode: 'continuable' | 'one-shot';
|
|
45
|
+
} | undefined;
|
|
46
|
+
refreshSubagents(parentSessionId: string): Promise<void>;
|
|
47
|
+
setSubagentCatalogOpen(parentSessionId: string, open: boolean): void;
|
|
48
|
+
subagentsByParent: Readonly<Record<string, {
|
|
49
|
+
entries: readonly CatalogEntry[];
|
|
50
|
+
parentAvailable: boolean;
|
|
51
|
+
}>>;
|
|
52
|
+
}
|
|
53
|
+
export type SubagentTreeViewInjected = {
|
|
54
|
+
sessions: TreeSessions;
|
|
55
|
+
profileLabelOf: (id: string) => string | undefined;
|
|
56
|
+
};
|
|
57
|
+
type SubagentTreeViewProps = ConvViewProps & PropsLocale<'ya-subagent'> & InjectFace<SubagentTreeViewInjected>;
|
|
58
|
+
/**
|
|
59
|
+
* Render the subagent tree view. Always shows the ROOT session's full tree;
|
|
60
|
+
* highlights the current session if it is a subagent.
|
|
61
|
+
*/
|
|
62
|
+
export declare function SubagentTreeView({ sessionId, sessions, profileLabelOf, t }: SubagentTreeViewProps): import("react").JSX.Element;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yet-another-subagent — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Single bundle, dual entry: this is the client half (exports `./client`).
|
|
5
|
+
* Host half ships via `.` (see `src/index.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Two registrations:
|
|
8
|
+
* 1. `settings.section` slot — the profile editor page (SettingsPage).
|
|
9
|
+
* 2. `tool.call.toolview` keyed slot, key `subagent` — the live toolcall
|
|
10
|
+
* card (SubagentCard). A single key covers all profiles because the
|
|
11
|
+
* tool name is always `subagent`; the profile is a call parameter.
|
|
12
|
+
*
|
|
13
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client
|
|
14
|
+
*/
|
|
15
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
16
|
+
import { type YaSubagentKey } from './locales.ts';
|
|
17
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
18
|
+
interface LocaleNamespaceMap {
|
|
19
|
+
/** The subagent settings page + tool card copy. */
|
|
20
|
+
'ya-subagent': YaSubagentKey;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Required services: settings/tool slots, locale, sessions, connection. */
|
|
24
|
+
export declare const inject: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Client plugin body: register settings page + single `subagent` toolview slot.
|
|
27
|
+
* @param ctx - client root context.
|
|
28
|
+
*/
|
|
29
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale dictionaries for yet-another-subagent.
|
|
3
|
+
*
|
|
4
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/locales
|
|
5
|
+
*/
|
|
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';
|
|
8
|
+
/** Locale namespace id. */
|
|
9
|
+
export declare const NS = "ya-subagent";
|
|
10
|
+
/** English dictionary. */
|
|
11
|
+
export declare const en: Record<YaSubagentKey, string>;
|
|
12
|
+
/** Chinese dictionary. */
|
|
13
|
+
export declare const zh: Record<YaSubagentKey, string>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yet-another-subagent — host plugin entry.
|
|
3
|
+
*
|
|
4
|
+
* Single bundle, dual entry: this is the host half (exports `.`). The browser
|
|
5
|
+
* half ships via `./client` (see `src/client/index.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Architecture (design doc §1):
|
|
8
|
+
* - A single `subagent` tool is exposed to the model. The desired profile
|
|
9
|
+
* is selected via the `profile` parameter (enum of profile ids). Profile
|
|
10
|
+
* add/remove updates the enum without changing the tool name set.
|
|
11
|
+
* - The tool reuses the official `spawn` provider via `ctx.subagents.startContinuable`.
|
|
12
|
+
* - Profiles live in an in-memory `ProfileStore` mutated through RPC.
|
|
13
|
+
* - Two projections (`subagentProfile` on parent, `yaSubagentProgress` on
|
|
14
|
+
* child) bridge the single-stage client runtime so SubagentCard can
|
|
15
|
+
* subscribe to live child progress.
|
|
16
|
+
*
|
|
17
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from 'cordis';
|
|
20
|
+
import z from 'schemastery';
|
|
21
|
+
import type { YaSubagentConfig } from './types.ts';
|
|
22
|
+
export declare const name = "yet-another-subagent";
|
|
23
|
+
export declare const inject: string[];
|
|
24
|
+
export type { SubagentProfile, YaSubagentConfig } from './types.ts';
|
|
25
|
+
/** Settings namespace under which profile state persists (`$DSH_HOME/settings.yaml`). */
|
|
26
|
+
export declare const SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
27
|
+
export interface Config extends YaSubagentConfig {
|
|
28
|
+
}
|
|
29
|
+
export declare const Config: z<Config>;
|
|
30
|
+
/**
|
|
31
|
+
* Plugin body: register profile tools, RPC, and projections.
|
|
32
|
+
*
|
|
33
|
+
* Persistence: when a settings service is mounted, the profile list lives
|
|
34
|
+
* under the `ya-subagent` namespace in `$DSH_HOME/settings.yaml`. The
|
|
35
|
+
* cordis.yml `profiles` field is the composition `base` (first-boot seed);
|
|
36
|
+
* runtime mutations persist through `scope.replace()`. Headless assemblies
|
|
37
|
+
* without a settings provider fall back to in-memory state (cordis.yml seed
|
|
38
|
+
* only, no persistence).
|
|
39
|
+
* @param ctx - host context carrying `tools`, `subagents`, `sessionProjections`.
|
|
40
|
+
* @param config - resolved config (seed profiles + generalFixed).
|
|
41
|
+
*/
|
|
42
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@huanlin/dsh-plugin-yet-another-subagent`.
|
|
3
|
+
*
|
|
4
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/invariant
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from 'cordis';
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
export declare const name = "yet-another-subagent-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
export declare const inject: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Register this package's invariant companion.
|
|
13
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
14
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
15
|
+
*/
|
|
16
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|