@morlay/dsh-subagent 0.0.10 → 0.0.11-alpha.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.
@@ -4,15 +4,15 @@
4
4
  * @module @deepseek-ai/dsh-subagent/internal
5
5
  */
6
6
 
7
- import type { Agent } from "@deepseek-ai/dsh-agent";
8
- import type { ContentBlock, MessageId, MessageSource } from "@deepseek-ai/dsh-llm";
9
- import type { SessionId } from "@deepseek-ai/dsh-session";
10
- import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
11
- import type SubagentRuntime from "./index.ts";
12
- import type { SubagentDelivery } from "./control-types.ts";
7
+ import type { Agent } from '@deepseek-ai/dsh-agent'
8
+ import type { ContentBlock, MessageId, MessageSource } from '@deepseek-ai/dsh-llm'
9
+ import type { SessionId } from '@deepseek-ai/dsh-session'
10
+ import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
11
+ import type SubagentRuntime from './index.ts'
12
+ import type { SubagentDelivery } from './control-types.ts'
13
13
 
14
14
  /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
15
- export const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
15
+ export const adjacentAgentSendMessageTool = Symbol.for('dsh.subagent.adjacentAgentSendMessageTool')
16
16
 
17
17
  /**
18
18
  * Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
@@ -20,8 +20,8 @@ export const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAge
20
20
  * @returns the same definition with its internal identity installed.
21
21
  */
22
22
  export function markAdjacentAgentSendMessageTool(definition: ToolDefinition): ToolDefinition {
23
- Object.defineProperty(definition, adjacentAgentSendMessageTool, { value: true });
24
- return definition;
23
+ Object.defineProperty(definition, adjacentAgentSendMessageTool, { value: true })
24
+ return definition
25
25
  }
26
26
 
27
27
  /**
@@ -30,12 +30,8 @@ export function markAdjacentAgentSendMessageTool(definition: ToolDefinition): To
30
30
  * @returns whether the definition carries the internal standard-tool identity.
31
31
  */
32
32
  export function isAdjacentAgentSendMessageTool(definition: ToolDefinition | undefined): boolean {
33
- return (
34
- definition !== undefined &&
35
- (definition as ToolDefinition & { [adjacentAgentSendMessageTool]?: true })[
36
- adjacentAgentSendMessageTool
37
- ] === true
38
- );
33
+ return definition !== undefined
34
+ && (definition as ToolDefinition & { [adjacentAgentSendMessageTool]?: true })[adjacentAgentSendMessageTool] === true
39
35
  }
40
36
 
41
37
  /**
@@ -43,7 +39,7 @@ export function isAdjacentAgentSendMessageTool(definition: ToolDefinition | unde
43
39
  * entry and this unbundled internal subpath.
44
40
  * @internal
45
41
  */
46
- export const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
42
+ export const deliverSubagentPrompt = Symbol.for('dsh.subagent.deliverPrompt')
47
43
 
48
44
  /** Runtime face required by the host-only prompt adapters. */
49
45
  export interface HostPromptDeliverer {
@@ -54,7 +50,7 @@ export interface HostPromptDeliverer {
54
50
  source: MessageSource,
55
51
  signal: AbortSignal,
56
52
  delivery: SubagentDelivery,
57
- ): Promise<MessageId>;
53
+ ): Promise<MessageId>
58
54
  }
59
55
 
60
56
  /**
@@ -81,8 +77,8 @@ export function queueHostSubagentPrompt(
81
77
  content,
82
78
  source,
83
79
  signal,
84
- "queue",
85
- );
80
+ 'queue',
81
+ )
86
82
  }
87
83
 
88
84
  /**
@@ -109,6 +105,6 @@ export function steerHostSubagentPrompt(
109
105
  content,
110
106
  source,
111
107
  signal,
112
- "steer",
113
- );
108
+ 'steer',
109
+ )
114
110
  }
@@ -7,13 +7,13 @@
7
7
  * @module @deepseek-ai/dsh-subagent/lifecycle
8
8
  */
9
9
 
10
- import { randomUUID } from "node:crypto";
11
- import type { Context } from "@deepseek-ai/cordis";
12
- import type { Agent } from "@deepseek-ai/dsh-agent";
13
- import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
14
- import type { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
15
- import { SubagentRunId } from "./types.ts";
16
- import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from "./types.ts";
10
+ import { randomUUID } from 'node:crypto'
11
+ import type { Context } from '@deepseek-ai/cordis'
12
+ import type { Agent } from '@deepseek-ai/dsh-agent'
13
+ import { foldConsumedWork } from '@deepseek-ai/dsh-agent'
14
+ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
15
+ import { SubagentRunId } from './types.ts'
16
+ import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts'
17
17
 
18
18
  /**
19
19
  * Lifecycle observer for one activation's residency epoch. Package-private: the
@@ -25,7 +25,7 @@ export interface ActivationObserver {
25
25
  * Publish the start edge once the epoch is resident.
26
26
  * @param child - the resident local Agent, or undefined for an external execution.
27
27
  */
28
- start(child?: Agent): void;
28
+ start(child?: Agent): void
29
29
  /**
30
30
  * Publish the terminal edge exactly once, pairing this epoch's {@link start},
31
31
  * after the disposal outcome is known. Called only for a resident epoch: a
@@ -33,7 +33,7 @@ export interface ActivationObserver {
33
33
  * report a lifecycle the child never had.
34
34
  * @param result - the captured result, with teardown failures applied.
35
35
  */
36
- settle(result: SubagentResult): void;
36
+ settle(result: SubagentResult): void
37
37
  }
38
38
 
39
39
  /**
@@ -46,10 +46,10 @@ export interface ActivationObserver {
46
46
  * a narrowed stand-in would silently change scope filtering.
47
47
  */
48
48
  export type LifecycleEmitter = {
49
- (name: "subagent/start", info: SubagentRunInfo, parent: Agent): void;
50
- (name: "subagent/end", info: SubagentRunEndInfo, parent: Agent): void;
51
- (name: "subagent/provider-removed", info: string): void;
52
- };
49
+ (name: 'subagent/start', info: SubagentRunInfo, parent: Agent): void
50
+ (name: 'subagent/end', info: SubagentRunEndInfo, parent: Agent): void
51
+ (name: 'subagent/provider-removed', info: string): void
52
+ }
53
53
 
54
54
  /**
55
55
  * Build the contained lifecycle emitter this seam publishes every edge through.
@@ -65,23 +65,24 @@ export function createLifecycleEmitter(
65
65
  carrier: (parent: Agent) => object,
66
66
  ): LifecycleEmitter {
67
67
  return (
68
- name: "subagent/start" | "subagent/end" | "subagent/provider-removed",
68
+ name: 'subagent/start' | 'subagent/end' | 'subagent/provider-removed',
69
69
  info: SubagentRunInfo | SubagentRunEndInfo | string,
70
70
  parent?: Agent,
71
71
  ): void => {
72
- const dispatchArgs: unknown[] =
73
- parent === undefined ? [name, info] : [carrier(parent), name, info];
74
- for (const callback of ctx.events.dispatch("emit", dispatchArgs)) {
72
+ const dispatchArgs: unknown[] = parent === undefined
73
+ ? [name, info]
74
+ : [carrier(parent), name, info]
75
+ for (const callback of ctx.events.dispatch('emit', dispatchArgs)) {
75
76
  try {
76
- const returned: unknown = callback(info);
77
+ const returned: unknown = callback(info)
77
78
  void Promise.resolve(returned).catch((error: unknown) => {
78
- ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`);
79
- });
79
+ ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`)
80
+ })
80
81
  } catch (error: unknown) {
81
- ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`);
82
+ ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`)
82
83
  }
83
84
  }
84
- };
85
+ }
85
86
  }
86
87
 
87
88
  /**
@@ -98,24 +99,20 @@ export function createActivationObserver(
98
99
  childId: SessionId,
99
100
  parent: Agent,
100
101
  ): ActivationObserver {
101
- const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: false };
102
+ const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: false }
102
103
  return {
103
104
  start: (child?: Agent): void => {
104
- identity.local = child !== undefined;
105
- emit("subagent/start", identity, parent);
105
+ identity.local = child !== undefined
106
+ emit('subagent/start', identity, parent)
106
107
  },
107
108
  settle: ({ stopReason, output }: SubagentResult): void => {
108
- emit(
109
- "subagent/end",
110
- {
111
- ...identity,
112
- stopReason,
113
- ...(output.length === 0 ? {} : { lastAssistantMessage: output }),
114
- },
115
- parent,
116
- );
109
+ emit('subagent/end', {
110
+ ...identity,
111
+ stopReason,
112
+ ...output.length === 0 ? {} : { lastAssistantMessage: output },
113
+ }, parent)
117
114
  },
118
- };
115
+ }
119
116
  }
120
117
 
121
118
  /**
@@ -134,39 +131,39 @@ export function createActivationObserver(
134
131
  * @returns its terminal stop reason; `completed` only for an epoch that both
135
132
  * closed cleanly and had nothing left to run.
136
133
  */
137
- export function epochStopReason(events: readonly SessionEvent[]): SubagentResult["stopReason"] {
138
- const { end, droppedUnrun } = foldConsumedWork(events);
134
+ export function epochStopReason(events: readonly SessionEvent[]): SubagentResult['stopReason'] {
135
+ const { end, droppedUnrun } = foldConsumedWork(events)
139
136
  switch (end?.data.reason.kind) {
140
- case "max-tokens":
141
- return "max-tokens";
142
- case "aborted":
143
- case "interrupted":
144
- return "aborted";
145
- case "error":
146
- return "error";
137
+ case 'max-tokens':
138
+ return 'max-tokens'
139
+ case 'aborted':
140
+ case 'interrupted':
141
+ return 'aborted'
142
+ case 'error':
143
+ return 'error'
147
144
  // A pre-step rejection — a hook deny, a policy plugin — discarded input
148
145
  // this epoch had claimed: the work was declined, not done.
149
- case "blocked":
150
- return "refusal";
146
+ case 'blocked':
147
+ return 'refusal'
151
148
  // A clean ending and no accounting turn at all share one rule: the epoch
152
149
  // finished what it was given unless a cancelled queue says otherwise.
153
150
  case undefined:
154
- case "completed":
155
- return droppedUnrun ? "aborted" : "completed";
151
+ case 'completed':
152
+ return droppedUnrun ? 'aborted' : 'completed'
156
153
  /* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
157
154
  * this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
158
155
  * so a backend-added variant cannot be listed; treating an unnameable reason as
159
156
  * success would report failed work as completed. */
160
157
  default:
161
- return "error";
158
+ return 'error'
162
159
  }
163
160
  }
164
161
 
165
162
  /** Render any listener-thrown value without letting coercion escape containment. */
166
163
  function renderThrown(value: unknown): string {
167
164
  try {
168
- return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
165
+ return value instanceof Error ? `${value.name}: ${value.message}` : String(value)
169
166
  } catch {
170
- return "<unrenderable thrown value>";
167
+ return '<unrenderable thrown value>'
171
168
  }
172
169
  }
@@ -4,21 +4,21 @@
4
4
  * @module @deepseek-ai/dsh-subagent
5
5
  */
6
6
 
7
- import type { Context } from "@deepseek-ai/cordis";
8
- import type { SessionId } from "@deepseek-ai/dsh-session";
9
- import type { SubagentListEntry } from "./control-types.ts";
10
- import { SubagentError } from "./error.ts";
11
- import type { SubagentCatalogEntry } from "./projection-types.ts";
7
+ import type { Context } from '@deepseek-ai/cordis'
8
+ import type { SessionId } from '@deepseek-ai/dsh-session'
9
+ import type { SubagentListEntry } from './control-types.ts'
10
+ import { SubagentError } from './error.ts'
11
+ import type { SubagentCatalogEntry } from './projection-types.ts'
12
12
 
13
- export type { SubagentListEntry } from "./control-types.ts";
13
+ export type { SubagentListEntry } from './control-types.ts'
14
14
 
15
15
  /** One catalog descendant with its direct parent and edge distance from the requested root. */
16
16
  export type SubagentDescendantListEntry = SubagentListEntry & {
17
17
  /** Parent whose catalog contains this child. */
18
- readonly parentId: SessionId;
18
+ readonly parentId: SessionId
19
19
  /** Edge distance from the requested root; direct children are `1`. */
20
- readonly depth: number;
21
- };
20
+ readonly depth: number
21
+ }
22
22
 
23
23
  /**
24
24
  * Read one parent's durable catalog through a live-preferred Session observation.
@@ -33,24 +33,24 @@ export async function listChildren(
33
33
  parentSessionId: SessionId,
34
34
  signal?: AbortSignal,
35
35
  ): Promise<SubagentCatalogEntry[]> {
36
- const query = ctx.get("sessionQuery");
36
+ const query = ctx.get('sessionQuery')
37
37
  if (query === undefined) {
38
38
  throw new SubagentError(
39
- "listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)",
40
- "SUBAGENT_CONTROL_QUERY_UNAVAILABLE",
41
- );
39
+ 'listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)',
40
+ 'SUBAGENT_CONTROL_QUERY_UNAVAILABLE',
41
+ )
42
42
  }
43
43
  using parent = await query.observeSession(parentSessionId, {
44
- ...(signal === undefined ? {} : { signal }),
45
- });
46
- const entries = parent.projections?.values.subagentCatalog;
44
+ ...signal === undefined ? {} : { signal },
45
+ })
46
+ const entries = parent.projections?.values.subagentCatalog
47
47
  if (entries === undefined) {
48
48
  throw new SubagentError(
49
- "listing subagents requires the registered subagentCatalog projection",
50
- "SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE",
51
- );
49
+ 'listing subagents requires the registered subagentCatalog projection',
50
+ 'SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE',
51
+ )
52
52
  }
53
- return entries;
53
+ return entries
54
54
  }
55
55
 
56
56
  /**
@@ -66,76 +66,68 @@ export async function listDescendants(
66
66
  rootSessionId: SessionId,
67
67
  signal?: AbortSignal,
68
68
  ): Promise<SubagentDescendantListEntry[]> {
69
- const sessions = ctx.get("sessions");
69
+ const sessions = ctx.get('sessions')
70
70
  if (sessions === undefined) {
71
71
  throw new SubagentError(
72
- "listing subagents requires the session store (load @deepseek-ai/dsh-session)",
73
- "SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE",
74
- );
72
+ 'listing subagents requires the session store (load @deepseek-ai/dsh-session)',
73
+ 'SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE',
74
+ )
75
75
  }
76
76
  const readChildren = async (id: SessionId): Promise<SubagentCatalogEntry[]> => {
77
- assertListingNotCancelled(signal);
78
- let children: SubagentCatalogEntry[];
77
+ assertListingNotCancelled(signal)
78
+ let children: SubagentCatalogEntry[]
79
79
  try {
80
- children = await listChildren(ctx, id, signal);
80
+ children = await listChildren(ctx, id, signal)
81
81
  } catch (error) {
82
- assertListingNotCancelled(signal);
83
- throw error;
82
+ assertListingNotCancelled(signal)
83
+ throw error
84
84
  }
85
- assertListingNotCancelled(signal);
86
- return children;
87
- };
85
+ assertListingNotCancelled(signal)
86
+ return children
87
+ }
88
88
  const stack = (await readChildren(rootSessionId))
89
- .map((entry) => ({ entry, parentId: rootSessionId, depth: 1 }))
90
- .reverse();
91
- const visited = new Set<SessionId>([rootSessionId]);
92
- const result: SubagentDescendantListEntry[] = [];
89
+ .map(entry => ({ entry, parentId: rootSessionId, depth: 1 }))
90
+ .reverse()
91
+ const visited = new Set<SessionId>([rootSessionId])
92
+ const result: SubagentDescendantListEntry[] = []
93
93
  for (let position = stack.pop(); position !== undefined; position = stack.pop()) {
94
- const { entry, parentId, depth } = position;
95
- if (visited.has(entry.id)) continue;
96
- visited.add(entry.id);
97
- let children: SubagentCatalogEntry[];
94
+ const { entry, parentId, depth } = position
95
+ if (visited.has(entry.id)) continue
96
+ visited.add(entry.id)
97
+ let children: SubagentCatalogEntry[]
98
98
  try {
99
- children = entry.mode === "external" ? [] : await readChildren(entry.id);
99
+ children = entry.mode === 'external' ? [] : await readChildren(entry.id)
100
100
  } catch (error) {
101
- if (error instanceof SubagentError) throw error;
102
- const code = error instanceof Error && "code" in error ? error.code : undefined;
101
+ if (error instanceof SubagentError) throw error
102
+ const code = error instanceof Error && 'code' in error ? error.code : undefined
103
103
  result.push({
104
- kind: "diagnostic",
105
- id: entry.id,
106
- parentId,
107
- depth,
108
- reason:
109
- code === "SESSION_QUERY_CORRUPT_SESSION" || code === "SESSION_QUERY_SOURCE_CONFLICT"
110
- ? "corrupt"
111
- : "unavailable",
112
- });
113
- continue;
104
+ kind: 'diagnostic', id: entry.id, parentId, depth,
105
+ reason: code === 'SESSION_QUERY_CORRUPT_SESSION' || code === 'SESSION_QUERY_SOURCE_CONFLICT'
106
+ ? 'corrupt' : 'unavailable',
107
+ })
108
+ continue
114
109
  }
115
- if (entry.mode === "unknown") {
116
- result.push({ kind: "diagnostic", id: entry.id, parentId, depth, reason: "unsupported" });
110
+ if (entry.mode === 'unknown') {
111
+ result.push({ kind: 'diagnostic', id: entry.id, parentId, depth, reason: 'unsupported' })
117
112
  } else {
118
- const { createdAt: _createdAt, ...identity } = entry;
113
+ const { createdAt: _createdAt, ...identity } = entry
119
114
  result.push({
120
- ...identity,
121
- kind: "child",
122
- parentId,
123
- depth,
124
- activity: sessions.get(entry.id) === undefined ? "inactive" : "running",
115
+ ...identity, kind: 'child', parentId, depth,
116
+ activity: sessions.get(entry.id) === undefined ? 'inactive' : 'running',
125
117
  hasChildren: children.length > 0,
126
- });
118
+ })
127
119
  }
128
120
  // Catalog event order defines siblings; the stack visits the first one next.
129
121
  for (const child of [...children].reverse()) {
130
- stack.push({ entry: child, parentId: entry.id, depth: depth + 1 });
122
+ stack.push({ entry: child, parentId: entry.id, depth: depth + 1 })
131
123
  }
132
124
  }
133
- return result;
125
+ return result
134
126
  }
135
127
 
136
128
  /** Stop the complete traversal when its caller cancels. */
137
129
  function assertListingNotCancelled(signal: AbortSignal | undefined): void {
138
130
  if (signal?.aborted) {
139
- throw new SubagentError("subagent listing was cancelled", "CANCELLED");
131
+ throw new SubagentError('subagent listing was cancelled', 'CANCELLED')
140
132
  }
141
133
  }