@fishwithcat/dsh-subagent 0.0.0-stage → 0.2.2-alpha.3

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.
Files changed (59) hide show
  1. package/README.i18n.yaml +121 -0
  2. package/README.md +258 -2
  3. package/README.zh.md +259 -0
  4. package/lib/index.js +3065 -0
  5. package/lib/typert.host.d.ts +3 -0
  6. package/lib/typert.host.js +1086 -0
  7. package/lib/typert.remote-client.d.ts +29 -0
  8. package/lib/typert.remote-client.js +122 -0
  9. package/lib/types/activation.d.ts +110 -0
  10. package/lib/types/activation.js +112 -0
  11. package/lib/types/archive-admission.d.ts +23 -0
  12. package/lib/types/archive-admission.js +75 -0
  13. package/lib/types/assistant-output.d.ts +48 -0
  14. package/lib/types/assistant-output.js +73 -0
  15. package/lib/types/catalog.d.ts +87 -0
  16. package/lib/types/catalog.js +113 -0
  17. package/lib/types/child-agent.d.ts +145 -0
  18. package/lib/types/child-agent.js +229 -0
  19. package/lib/types/client.d.ts +8 -0
  20. package/lib/types/client.js +7 -0
  21. package/lib/types/continuation-messages.d.ts +66 -0
  22. package/lib/types/continuation-messages.js +116 -0
  23. package/lib/types/control-types.d.ts +121 -0
  24. package/lib/types/control-types.js +8 -0
  25. package/lib/types/control.d.ts +41 -0
  26. package/lib/types/control.js +75 -0
  27. package/lib/types/depth.d.ts +31 -0
  28. package/lib/types/depth.js +39 -0
  29. package/lib/types/descriptor.d.ts +105 -0
  30. package/lib/types/descriptor.js +192 -0
  31. package/lib/types/error.d.ts +11 -0
  32. package/lib/types/error.js +14 -0
  33. package/lib/types/index.d.ts +282 -0
  34. package/lib/types/index.js +398 -0
  35. package/lib/types/lifecycle.d.ts +54 -0
  36. package/lib/types/lifecycle.js +89 -0
  37. package/lib/types/list-children.d.ts +36 -0
  38. package/lib/types/list-children.js +166 -0
  39. package/lib/types/manager.d.ts +215 -0
  40. package/lib/types/manager.js +1171 -0
  41. package/lib/types/out-of-process.d.ts +92 -0
  42. package/lib/types/out-of-process.js +177 -0
  43. package/lib/types/projection-types.d.ts +93 -0
  44. package/lib/types/projection-types.js +7 -0
  45. package/lib/types/projection.d.ts +94 -0
  46. package/lib/types/projection.js +157 -0
  47. package/lib/types/structured.d.ts +49 -0
  48. package/lib/types/structured.js +130 -0
  49. package/lib/types/types.d.ts +331 -0
  50. package/lib/types/types.js +17 -0
  51. package/locale/de.json +6 -0
  52. package/locale/en.json +5 -0
  53. package/locale/es.json +6 -0
  54. package/locale/fr.json +6 -0
  55. package/locale/ja.json +6 -0
  56. package/locale/pt.json +6 -0
  57. package/locale/ru.json +6 -0
  58. package/locale/zh.json +6 -0
  59. package/package.json +131 -4
@@ -0,0 +1,29 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
2
+ import type {
3
+ RemoteResult,
4
+ TypertRemoteInfo,
5
+ TypertRemoteMethod,
6
+ } from '@deepseek-ai/dsh-typert-protocol'
7
+ import type { SessionId } from '@deepseek-ai/dsh-session/types'
8
+ import type { SubagentInterruptReceipt, SubagentPromptReceipt, SubagentPromptRequest } from '@deepseek-ai/dsh-subagent/client'
9
+
10
+ declare module '@deepseek-ai/dsh-typert-protocol' {
11
+ interface TypertRemoteNamespace$7375626167656e7473 {
12
+ interruptByUser: (childSessionId: SessionId) => Promise<RemoteResult<SubagentInterruptReceipt>>
13
+ prompt: (request: SubagentPromptRequest, signal?: AbortSignal) => Promise<RemoteResult<SubagentPromptReceipt>>
14
+ }
15
+ interface TypertRemoteMap {
16
+ 'subagents/interruptByUser': (childSessionId: SessionId) => Promise<RemoteResult<SubagentInterruptReceipt>>
17
+ 'subagents/prompt': (request: SubagentPromptRequest, signal?: AbortSignal) => Promise<RemoteResult<SubagentPromptReceipt>>
18
+ }
19
+ interface TypertRemoteNamespaceMap {
20
+ 'subagents': TypertRemoteNamespace$7375626167656e7473
21
+ }
22
+ }
23
+
24
+ export declare const TYPERT_REMOTE: TypertRemoteInfo<{
25
+ readonly 'subagents/interruptByUser': TypertRemoteMethod<{ readonly 'childSessionId': SessionId }, SubagentInterruptReceipt>
26
+ readonly 'subagents/prompt': TypertRemoteMethod<{ readonly 'request': SubagentPromptRequest }, SubagentPromptReceipt>
27
+ }>
28
+ export default TYPERT_REMOTE
29
+ //# sourceMappingURL=typert.remote-client.d.ts.map
@@ -0,0 +1,122 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
2
+ import { z } from 'zod'
3
+
4
+ import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'
5
+
6
+ let _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0$schema$value
7
+ const _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0$schema$value ??= z.string())
8
+ let _deepseek_ai_dsh_subagent_subagents_interruptByUser_result$schema$value
9
+ const _deepseek_ai_dsh_subagent_subagents_interruptByUser_result$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByUser_result$schema$value ??= z.object({
10
+ 'accepted': z.literal(true).readonly(),
11
+ }))
12
+ let _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema$value
13
+ const _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema = () => (_deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema$value ??= z.object({
14
+ 'requestId': z.string().readonly().describe('Identity persisted on the accepted message, minted before the call.'),
15
+ 'childSessionId': z.string().readonly(),
16
+ 'delivery': z.union([z.literal("queue"), z.literal("steer")]).readonly().describe('Whether this message queues a later turn or targets the nearest step.'),
17
+ 'content': z.array(z.union([z.object({
18
+ 'type': z.literal("text").readonly(),
19
+ 'text': z.string().readonly(),
20
+ }), z.object({
21
+ 'type': z.literal("image").readonly(),
22
+ 'mediaType': z.union([z.literal("image/png"), z.literal("image/jpeg"), z.literal("image/webp"), z.literal("image/gif")]).readonly(),
23
+ 'data': z.string().readonly(),
24
+ 'name': z.string().readonly().optional(),
25
+ })])).readonly().describe('Browser prompt parts delivered as the child\'s user message. The Host admits and persists image parts before delivery, so the wire never carries a durable attachment reference the caller could fabricate.'),
26
+ 'clientTimeZone': z.string().readonly().optional().describe('Optional browser zone sampled for this exact human prompt.'),
27
+ }))
28
+ let _deepseek_ai_dsh_subagent_subagents_prompt_result$schema$value
29
+ const _deepseek_ai_dsh_subagent_subagents_prompt_result$schema = () => (_deepseek_ai_dsh_subagent_subagents_prompt_result$schema$value ??= z.object({
30
+ 'messageId': z.string().readonly(),
31
+ }))
32
+ let _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0_strict$schema$value
33
+ const _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0_strict$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0_strict$schema$value ??= z.string())
34
+ let _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0_strict$schema$value
35
+ const _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0_strict$schema = () => (_deepseek_ai_dsh_subagent_subagents_prompt_parameter_0_strict$schema$value ??= z.strictObject({
36
+ 'requestId': z.string().readonly().describe('Identity persisted on the accepted message, minted before the call.'),
37
+ 'childSessionId': z.string().readonly(),
38
+ 'delivery': z.union([z.literal("queue"), z.literal("steer")]).readonly().describe('Whether this message queues a later turn or targets the nearest step.'),
39
+ 'content': z.array(z.union([z.strictObject({
40
+ 'type': z.literal("text").readonly(),
41
+ 'text': z.string().readonly(),
42
+ }), z.strictObject({
43
+ 'type': z.literal("image").readonly(),
44
+ 'mediaType': z.union([z.literal("image/png"), z.literal("image/jpeg"), z.literal("image/webp"), z.literal("image/gif")]).readonly(),
45
+ 'data': z.string().readonly(),
46
+ 'name': z.string().readonly().optional(),
47
+ })])).readonly().describe('Browser prompt parts delivered as the child\'s user message. The Host admits and persists image parts before delivery, so the wire never carries a durable attachment reference the caller could fabricate.'),
48
+ 'clientTimeZone': z.string().readonly().optional().describe('Optional browser zone sampled for this exact human prompt.'),
49
+ }))
50
+
51
+ export const TYPERT_REMOTE = createTypertRemoteInfo({
52
+ package: '@fishwithcat/dsh-subagent',
53
+ methods: {
54
+ 'subagents/interruptByUser': {
55
+ service: 'subagents',
56
+ description: 'Remote face of {@link interrupt}, authorized on the target Session by the Gateway. No catalog, history, persistence, or parent Agent lookup runs. Absent, idle, and already-completed targets are accepted no-ops there.',
57
+ invocation: { kind: 'direct' },
58
+ parameters: [
59
+ {
60
+ name: 'childSessionId',
61
+ description: 'durable child session id to interrupt.',
62
+ source: 'json',
63
+ codec: {
64
+ mode: 'strict',
65
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
66
+ create: _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0$schema,
67
+ jsonSchema: {"type":"string"},
68
+ },
69
+ strictCodec: {
70
+ mode: 'strict',
71
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
72
+ create: _deepseek_ai_dsh_subagent_subagents_interruptByUser_parameter_0_strict$schema,
73
+ jsonSchema: {"type":"string"},
74
+ },
75
+ },
76
+ ],
77
+ result: {
78
+ mode: 'strict',
79
+ typeSymbol: '@deepseek-ai/dsh-subagent/client#SubagentInterruptReceipt',
80
+ create: _deepseek_ai_dsh_subagent_subagents_interruptByUser_result$schema,
81
+ description: 'acknowledgement that the cancel signal was admitted, not that the target is quiescent.',
82
+ jsonSchema: {"type":"object","properties":{"accepted":{"type":"boolean","const":true}},"required":["accepted"],"additionalProperties":true},
83
+ },
84
+ sourceLocation: {"file":"packages/subagent/subagent/src/index.ts","line":467,"column":3},
85
+ },
86
+ 'subagents/prompt': {
87
+ service: 'subagents',
88
+ description: 'Deliver one browser-authored message to a continuable child through its live subagent tree root, retaining the caller-minted request identity and validated browser zone on the accepted message. Success identifies the message the child\'s inbox accepted; later execution is independent of this call. Queue delivery targets a later turn; steer delivery targets the nearest step and retains the Agent loop\'s best-effort fallback semantics. Image parts are admitted and persisted through the attachment store before delivery, and the child\'s model must accept image input. Cold resume at capacity rejects with `subagent/delivery-unavailable`. Access is checked on the target Session before attachment admission or restoration.',
89
+ invocation: { kind: 'direct' },
90
+ parameters: [
91
+ {
92
+ name: 'request',
93
+ description: 'target id, delivery, minted identity, content, and optional browser zone.',
94
+ source: 'json',
95
+ codec: {
96
+ mode: 'strict',
97
+ typeSymbol: '@deepseek-ai/dsh-subagent/client#SubagentPromptRequest',
98
+ create: _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema,
99
+ jsonSchema: {"type":"object","properties":{"requestId":{"type":"string","description":"Identity persisted on the accepted message, minted before the call."},"childSessionId":{"type":"string"},"delivery":{"type":"string","enum":["queue","steer"],"description":"Whether this message queues a later turn or targets the nearest step."},"content":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text"},"text":{"type":"string"}},"required":["type","text"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"image"},"mediaType":{"type":"string","enum":["image/png","image/jpeg","image/webp","image/gif"]},"data":{"type":"string"},"name":{"type":"string"}},"required":["type","mediaType","data"],"additionalProperties":true}]},"description":"Browser prompt parts delivered as the child's user message. The Host admits and persists image parts before delivery, so the wire never carries a durable attachment reference the caller could fabricate."},"clientTimeZone":{"type":"string","description":"Optional browser zone sampled for this exact human prompt."}},"required":["requestId","childSessionId","delivery","content"],"additionalProperties":true},
100
+ },
101
+ strictCodec: {
102
+ mode: 'strict',
103
+ typeSymbol: '@deepseek-ai/dsh-subagent/client#SubagentPromptRequest',
104
+ create: _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0_strict$schema,
105
+ jsonSchema: {"type":"object","properties":{"requestId":{"type":"string","description":"Identity persisted on the accepted message, minted before the call."},"childSessionId":{"type":"string"},"delivery":{"type":"string","enum":["queue","steer"],"description":"Whether this message queues a later turn or targets the nearest step."},"content":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text"},"text":{"type":"string"}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image"},"mediaType":{"type":"string","enum":["image/png","image/jpeg","image/webp","image/gif"]},"data":{"type":"string"},"name":{"type":"string"}},"required":["type","mediaType","data"],"additionalProperties":false}]},"description":"Browser prompt parts delivered as the child's user message. The Host admits and persists image parts before delivery, so the wire never carries a durable attachment reference the caller could fabricate."},"clientTimeZone":{"type":"string","description":"Optional browser zone sampled for this exact human prompt."}},"required":["requestId","childSessionId","delivery","content"],"additionalProperties":false},
106
+ },
107
+ },
108
+ ],
109
+ cancellation: { parameter: 'signal' },
110
+ result: {
111
+ mode: 'strict',
112
+ typeSymbol: '@deepseek-ai/dsh-subagent/client#SubagentPromptReceipt',
113
+ create: _deepseek_ai_dsh_subagent_subagents_prompt_result$schema,
114
+ description: 'the accepted message\'s inbox identity.',
115
+ jsonSchema: {"type":"object","properties":{"messageId":{"type":"string"}},"required":["messageId"],"additionalProperties":true},
116
+ },
117
+ sourceLocation: {"file":"packages/subagent/subagent/src/index.ts","line":414,"column":9},
118
+ },
119
+ },
120
+ }, z)
121
+
122
+ export default TYPERT_REMOTE
@@ -0,0 +1,110 @@
1
+ /** Execution-period records, capacity slots, child serialization, and local output capture.
2
+ * @module @deepseek-ai/dsh-subagent/activation
3
+ */
4
+ import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent';
5
+ import type { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
6
+ import type { StructuredAttachment } from './structured.ts';
7
+ import type { SubagentResult, SubagentRun, SubagentRunId } from './types.ts';
8
+ /** Process-local capacity shared by every member of a subagent tree. */
9
+ export declare class ActivationPool {
10
+ private readonly slots;
11
+ /**
12
+ * Reserve before reconstruction; release also tolerates unpublished rollback.
13
+ * @param capacity - the maximum simultaneous activations in this pool.
14
+ * @returns an idempotent slot release.
15
+ */
16
+ reserve(capacity: number): () => void;
17
+ }
18
+ /**
19
+ * One residency epoch for a local child or an external execution.
20
+ * The manager owns admission, execution handles, and resource release.
21
+ */
22
+ interface ActivationBase {
23
+ /** Return this epoch's slot after its handle has finished disposal. */
24
+ readonly releaseSlot: () => void;
25
+ /** The durable child this Activation is an epoch of. */
26
+ readonly childId: SessionId;
27
+ /** Unique identity shared by this residency epoch's start/end event pair. */
28
+ readonly runId: SubagentRunId;
29
+ /** Provider recorded at creation, retained across cold resumes. */
30
+ readonly provider: string;
31
+ /** Exact live subagent tree root owning this instance and its shared capacity. */
32
+ readonly root: Agent;
33
+ /** Verified creation ancestors, from subagent tree root to direct creator. */
34
+ readonly ancestors: readonly SessionId[];
35
+ readonly delivery: 'parent' | 'caller';
36
+ readonly result: PromiseWithResolvers<SubagentResult>;
37
+ /** Published synchronously before teardown; subsequent callers share this transaction. */
38
+ closing: Promise<void> | undefined;
39
+ /**
40
+ * Whether local input was accepted or external startup completed, including
41
+ * any required catalog publication. Until then, teardown owes the parent no
42
+ * settlement notice, even if this epoch's start event was already published.
43
+ */
44
+ accepted: boolean;
45
+ }
46
+ /** Resources retained only for the selected execution kind. */
47
+ export type ActivationResource = {
48
+ readonly kind: 'local';
49
+ readonly handle: AgentHandle;
50
+ readonly outputStart: SessionLogOffset;
51
+ readonly structured: StructuredAttachment | undefined;
52
+ /** Latest live failure not superseded by a later committed turn ending. */
53
+ failureAt: SessionLogOffset | undefined;
54
+ /** Renewed when local settlement must recheck input. */
55
+ poke: PromiseWithResolvers<void>;
56
+ /**
57
+ * Whether this child's settlement notice omits its closing text: true while
58
+ * the child's last logged tool call was a message to its direct parent and
59
+ * no input was claimed since.
60
+ */
61
+ omitClosingText: boolean;
62
+ } | {
63
+ readonly kind: 'external';
64
+ readonly run: SubagentRun;
65
+ readonly controller: AbortController;
66
+ };
67
+ /** One managed execution period with its provider-specific resources. */
68
+ export type Activation = ActivationBase & ActivationResource;
69
+ /** Local residency with input admission and Session-backed result capture. */
70
+ export type LocalActivation = Extract<Activation, {
71
+ kind: 'local';
72
+ }>;
73
+ /**
74
+ * Read the live Agent retained by local execution.
75
+ * @param activation - the managed local or external execution.
76
+ * @returns the local Agent, absent for an external run.
77
+ */
78
+ export declare function activationAgent(activation: Activation): Agent | undefined;
79
+ /**
80
+ * Require local residency before accepting further input.
81
+ * @param activation - execution targeted by message delivery.
82
+ * @returns the local activation.
83
+ * @throws when the external backend cannot accept another message.
84
+ */
85
+ export declare function requireLocalActivation(activation: Activation): LocalActivation;
86
+ /**
87
+ * Retain local failures whose turn ending could not be committed.
88
+ * @param activation - exact local Agent and epoch receiving the scoped observers.
89
+ * @param wake - notify its settlement watcher when failure changes readiness.
90
+ */
91
+ export declare function observeLocalFailure(activation: LocalActivation, wake: () => void): void;
92
+ /**
93
+ * Capture local output and retain failures without a later committed turn ending.
94
+ * @param activation - the settled local execution and any structured result.
95
+ * @returns final output, stop reason, and committed structured value.
96
+ */
97
+ export declare function captureLocalResult(activation: LocalActivation): SubagentResult;
98
+ /** Serialize each durable child's delivery, release, and disposal. */
99
+ export declare class ChildLock {
100
+ private tails;
101
+ /**
102
+ * Run `operation` after every previously queued operation for `childId`.
103
+ * @param childId - the durable child whose operations are linearized.
104
+ * @param operation - the critical section to run in order.
105
+ * @returns the operation's own settlement.
106
+ */
107
+ run<T>(childId: SessionId, operation: () => Promise<T>): Promise<T>;
108
+ }
109
+ export {};
110
+ //# sourceMappingURL=activation.d.ts.map
@@ -0,0 +1,112 @@
1
+ /** Execution-period records, capacity slots, child serialization, and local output capture.
2
+ * @module @deepseek-ai/dsh-subagent/activation
3
+ */
4
+ import { assertNever } from '@deepseek-ai/dsh-util-values';
5
+ import { finalAssistantOutput } from "./assistant-output.js";
6
+ import { epochStopReason } from "./lifecycle.js";
7
+ import { SubagentError } from "./error.js";
8
+ /** Process-local capacity shared by every member of a subagent tree. */
9
+ export class ActivationPool {
10
+ slots = new Set();
11
+ /**
12
+ * Reserve before reconstruction; release also tolerates unpublished rollback.
13
+ * @param capacity - the maximum simultaneous activations in this pool.
14
+ * @returns an idempotent slot release.
15
+ */
16
+ reserve(capacity) {
17
+ if (this.slots.size >= capacity) {
18
+ throw new SubagentError(`subagent limit reached (active child limit: ${capacity}); wait for an existing child to finish `
19
+ + 'or complete this work with the current agents', 'ACTIVATION_LIMIT_REACHED');
20
+ }
21
+ const slot = Symbol();
22
+ this.slots.add(slot);
23
+ return () => { this.slots.delete(slot); };
24
+ }
25
+ }
26
+ /**
27
+ * Read the live Agent retained by local execution.
28
+ * @param activation - the managed local or external execution.
29
+ * @returns the local Agent, absent for an external run.
30
+ */
31
+ export function activationAgent(activation) {
32
+ return activation.kind === 'local' ? activation.handle.agent : undefined;
33
+ }
34
+ /**
35
+ * Require local residency before accepting further input.
36
+ * @param activation - execution targeted by message delivery.
37
+ * @returns the local activation.
38
+ * @throws when the external backend cannot accept another message.
39
+ */
40
+ export function requireLocalActivation(activation) {
41
+ switch (activation.kind) {
42
+ case 'local': return activation;
43
+ case 'external':
44
+ throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, 'NOT_CONTINUABLE');
45
+ /* v8 ignore next 2 -- Both execution kinds are handled above. */
46
+ default: return assertNever(activation);
47
+ }
48
+ }
49
+ /**
50
+ * Retain local failures whose turn ending could not be committed.
51
+ * @param activation - exact local Agent and epoch receiving the scoped observers.
52
+ * @param wake - notify its settlement watcher when failure changes readiness.
53
+ */
54
+ export function observeLocalFailure(activation, wake) {
55
+ const child = activation.handle.agent;
56
+ child.ctx.on('agent/error', ({ agent }) => {
57
+ if (agent !== child)
58
+ return;
59
+ activation.failureAt = child.session.seq;
60
+ wake();
61
+ });
62
+ child.ctx.on('session/event', (session, event) => {
63
+ if (session === child.session && event.type === 'turn/end'
64
+ && activation.failureAt !== undefined && event.seq >= activation.failureAt) {
65
+ activation.failureAt = undefined;
66
+ }
67
+ });
68
+ }
69
+ /**
70
+ * Capture local output and retain failures without a later committed turn ending.
71
+ * @param activation - the settled local execution and any structured result.
72
+ * @returns final output, stop reason, and committed structured value.
73
+ */
74
+ export function captureLocalResult(activation) {
75
+ // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
76
+ const events = activation.handle.agent.session.snapshotEvents(activation.outputStart);
77
+ const output = finalAssistantOutput(events) ?? [];
78
+ const stopReason = activation.failureAt === undefined ? epochStopReason(events) : 'error';
79
+ const structured = activation.structured;
80
+ if (structured !== undefined) {
81
+ const captured = structured.captured();
82
+ if (captured !== undefined)
83
+ return { output, stopReason, structured: captured.value };
84
+ if (stopReason === 'completed')
85
+ return { output, stopReason: 'error' };
86
+ }
87
+ return { output, stopReason };
88
+ }
89
+ /** Serialize each durable child's delivery, release, and disposal. */
90
+ export class ChildLock {
91
+ tails = new Map();
92
+ /**
93
+ * Run `operation` after every previously queued operation for `childId`.
94
+ * @param childId - the durable child whose operations are linearized.
95
+ * @param operation - the critical section to run in order.
96
+ * @returns the operation's own settlement.
97
+ */
98
+ run(childId, operation) {
99
+ const previous = this.tails.get(childId) ?? Promise.resolve();
100
+ const result = previous.then(operation, operation);
101
+ // Absorb rejections in the chaining tail so one failed critical section
102
+ // cannot reject an unrelated later caller.
103
+ const tail = result.then(() => undefined, () => undefined);
104
+ this.tails.set(childId, tail);
105
+ void tail.then(() => {
106
+ if (this.tails.get(childId) === tail)
107
+ this.tails.delete(childId);
108
+ });
109
+ return result;
110
+ }
111
+ }
112
+ //# sourceMappingURL=activation.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The `subagent` family of the Workspace registry's archive admission: which
3
+ * subagent descendants of a Session are still inside a turn, and how they
4
+ * stop when the Session is archived with its work. Archived ancestors also
5
+ * block later model steps, including when their Sessions have been released.
6
+ *
7
+ * @module @deepseek-ai/dsh-subagent
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import type { Agent } from '@deepseek-ai/dsh-agent';
11
+ import type { Session, SessionId } from '@deepseek-ai/dsh-session';
12
+ import type { SubagentIdentityProjection } from './projection-types.ts';
13
+ /**
14
+ * Answer `workspace/session-activity` with the running subagent descendants of
15
+ * the asked Session, and `workspace/session-stop` by cancelling each of them
16
+ * as their parent would. Reject subsequent steps beneath archived ancestors.
17
+ * All listeners live as long as `ctx`'s fiber.
18
+ * @param ctx - context carrying the Agent registry.
19
+ * @param residentAncestors - verified creation ancestors for an exact managed Agent, absent before publication or for unmanaged Agents.
20
+ * @param identityOf - ready service-owned descriptor identity reader; absent or invalid descriptors yield no identity.
21
+ */
22
+ export declare function installSubagentArchiveAdmission(ctx: Context, residentAncestors: (agent: Agent) => readonly SessionId[] | undefined, identityOf: (session: Session) => SubagentIdentityProjection | undefined): void;
23
+ //# sourceMappingURL=archive-admission.d.ts.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The `subagent` family of the Workspace registry's archive admission: which
3
+ * subagent descendants of a Session are still inside a turn, and how they
4
+ * stop when the Session is archived with its work. Archived ancestors also
5
+ * block later model steps, including when their Sessions have been released.
6
+ *
7
+ * @module @deepseek-ai/dsh-subagent
8
+ */
9
+ /**
10
+ * Answer `workspace/session-activity` with the running subagent descendants of
11
+ * the asked Session, and `workspace/session-stop` by cancelling each of them
12
+ * as their parent would. Reject subsequent steps beneath archived ancestors.
13
+ * All listeners live as long as `ctx`'s fiber.
14
+ * @param ctx - context carrying the Agent registry.
15
+ * @param residentAncestors - verified creation ancestors for an exact managed Agent, absent before publication or for unmanaged Agents.
16
+ * @param identityOf - ready service-owned descriptor identity reader; absent or invalid descriptors yield no identity.
17
+ */
18
+ export function installSubagentArchiveAdmission(ctx, residentAncestors, identityOf) {
19
+ const ancestorsOf = (agent) => residentAncestors(agent) ?? headerAncestors(ctx, agent);
20
+ const runningDescendants = (sessionId) => ctx.agents.list()
21
+ .filter(agent => agent.status === 'running' && ancestorsOf(agent).includes(sessionId));
22
+ ctx.on('workspace/session-activity', async ({ sessionId }, next) => {
23
+ const running = runningDescendants(sessionId);
24
+ const rest = await next();
25
+ if (running.length === 0)
26
+ return rest;
27
+ const own = { kind: 'subagent', items: running.map(child => describe(child, identityOf(child.session))) };
28
+ return [own, ...rest];
29
+ });
30
+ ctx.on('workspace/session-stop', ({ sessionId }) => {
31
+ for (const child of runningDescendants(sessionId)) {
32
+ try {
33
+ child.cancel({ kind: 'parent' });
34
+ }
35
+ catch (error) {
36
+ // One child refusing its cancel must not keep its siblings running for an archived parent.
37
+ ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`);
38
+ }
39
+ }
40
+ });
41
+ ctx.on('agent/pre-step', ({ agent }, next) => {
42
+ const archived = ctx.get('workspaceRegistry')?.archivedSessionIds;
43
+ return archived !== undefined && ancestorsOf(agent).some(id => archived.includes(id))
44
+ ? Promise.resolve({ kind: 'reject' })
45
+ : next();
46
+ });
47
+ }
48
+ /**
49
+ * Header-only ancestry for Agents without a published Activation. Include an
50
+ * offline direct creator; ordinary forks stop the chain and cycles terminate.
51
+ */
52
+ function headerAncestors(ctx, agent) {
53
+ const ancestors = [];
54
+ const visited = new Set([agent.id]);
55
+ let header = agent.session.header;
56
+ while (header.origin === 'subagent' && header.parentSession !== undefined && !visited.has(header.parentSession)) {
57
+ const parentId = header.parentSession;
58
+ ancestors.push(parentId);
59
+ visited.add(parentId);
60
+ const parent = ctx.agents.get(parentId)?.session;
61
+ if (parent === undefined)
62
+ break;
63
+ header = parent.header;
64
+ }
65
+ return ancestors;
66
+ }
67
+ /**
68
+ * Report the child's current projected label only when its descriptor belongs
69
+ * to the child's own log; inherited, absent, or invalid identities yield the id alone.
70
+ */
71
+ function describe(child, identity) {
72
+ const label = identity !== undefined && child.session.isOwnSeq(identity.seq) ? identity.label : undefined;
73
+ return label === undefined ? { id: child.id } : { id: child.id, label };
74
+ }
75
+ //# sourceMappingURL=archive-admission.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Canonical selection of a child's final assistant output. Backend run results
3
+ * and `subagent/end.lastAssistantMessage` apply the same rule: select the last
4
+ * non-empty assistant message. An empty-content message records usage only
5
+ * when the loop appends it after a max-tokens step with no executable blocks,
6
+ * so it does not replace earlier output. If no non-empty message exists,
7
+ * select the accumulated assistant text. Selection is independent of the
8
+ * run's stop reason.
9
+ *
10
+ * @module @deepseek-ai/dsh-subagent/assistant-output
11
+ */
12
+ import { type ContentBlock } from '@deepseek-ai/dsh-llm';
13
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
14
+ /**
15
+ * Incremental fold of the selection rule, for backends that observe a child's
16
+ * output as it streams: session-event backends {@link push} each event, and
17
+ * transports without session events (ACP content chunks) {@link pushText} raw
18
+ * text into the same streamed fallback.
19
+ */
20
+ export declare class AssistantOutputFold {
21
+ private message;
22
+ private partial;
23
+ /**
24
+ * Fold one session event: a non-empty assistant message becomes the
25
+ * candidate final answer, while its embedded stream and any log-only attempt
26
+ * extend the streamed fallback; every other event contributes nothing.
27
+ * @param event - the next observed session event.
28
+ */
29
+ push(event: SessionEvent): void;
30
+ /**
31
+ * Extend the streamed fallback with text observed outside session events.
32
+ * @param text - the next streamed text piece (an empty piece is a no-op).
33
+ */
34
+ pushText(text: string): void;
35
+ /**
36
+ * Select the final output folded so far.
37
+ * @returns the last non-empty assistant message, else the accumulated
38
+ * streamed text, or `undefined` when the child produced neither.
39
+ */
40
+ collect(): readonly ContentBlock[] | undefined;
41
+ }
42
+ /**
43
+ * Apply the selection rule to one complete child-owned event suffix.
44
+ * @param events - the child-owned events (after any seed or epoch boundary).
45
+ * @returns the selected output, or `undefined` when the child produced none.
46
+ */
47
+ export declare function finalAssistantOutput(events: readonly SessionEvent[]): readonly ContentBlock[] | undefined;
48
+ //# sourceMappingURL=assistant-output.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Canonical selection of a child's final assistant output. Backend run results
3
+ * and `subagent/end.lastAssistantMessage` apply the same rule: select the last
4
+ * non-empty assistant message. An empty-content message records usage only
5
+ * when the loop appends it after a max-tokens step with no executable blocks,
6
+ * so it does not replace earlier output. If no non-empty message exists,
7
+ * select the accumulated assistant text. Selection is independent of the
8
+ * run's stop reason.
9
+ *
10
+ * @module @deepseek-ai/dsh-subagent/assistant-output
11
+ */
12
+ import { joinAssistantStreamText } from '@deepseek-ai/dsh-llm';
13
+ /**
14
+ * Incremental fold of the selection rule, for backends that observe a child's
15
+ * output as it streams: session-event backends {@link push} each event, and
16
+ * transports without session events (ACP content chunks) {@link pushText} raw
17
+ * text into the same streamed fallback.
18
+ */
19
+ export class AssistantOutputFold {
20
+ message;
21
+ partial = [];
22
+ /**
23
+ * Fold one session event: a non-empty assistant message becomes the
24
+ * candidate final answer, while its embedded stream and any log-only attempt
25
+ * extend the streamed fallback; every other event contributes nothing.
26
+ * @param event - the next observed session event.
27
+ */
28
+ push(event) {
29
+ if (event.type === 'assistant/message') {
30
+ const content = event.data.message.content;
31
+ if (content.length > 0)
32
+ this.message = content;
33
+ }
34
+ if (event.type === 'assistant/message' || event.type === 'assistant/attempt') {
35
+ this.pushText(joinAssistantStreamText(event.data.stream));
36
+ }
37
+ }
38
+ /**
39
+ * Extend the streamed fallback with text observed outside session events.
40
+ * @param text - the next streamed text piece (an empty piece is a no-op).
41
+ */
42
+ pushText(text) {
43
+ if (text.length > 0)
44
+ this.partial.push(text);
45
+ }
46
+ /**
47
+ * Select the final output folded so far.
48
+ * @returns the last non-empty assistant message, else the accumulated
49
+ * streamed text, or `undefined` when the child produced neither.
50
+ */
51
+ collect() {
52
+ if (this.message !== undefined)
53
+ return this.message;
54
+ const text = this.partial.join('');
55
+ return text.length > 0 ? [{ type: 'text', text }] : undefined;
56
+ }
57
+ }
58
+ /**
59
+ * Apply the selection rule to one complete child-owned event suffix.
60
+ * @param events - the child-owned events (after any seed or epoch boundary).
61
+ * @returns the selected output, or `undefined` when the child produced none.
62
+ */
63
+ export function finalAssistantOutput(events) {
64
+ // TODO: this folds the complete suffix once per run/epoch settlement. If a
65
+ // long continuable epoch ever profiles hot here, scan backward with early
66
+ // exit for the last non-empty message and fold text deltas only on the
67
+ // no-message fallback.
68
+ const fold = new AssistantOutputFold();
69
+ for (const event of events)
70
+ fold.push(event);
71
+ return fold.collect();
72
+ }
73
+ //# sourceMappingURL=assistant-output.js.map