@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/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.then((run) => {
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: started.childId,
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 run = await ctx.subagents.start("spawn", {
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>;