@qlzt/dsh-subagent 0.0.0-stage → 0.2.1-alpha.2
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/LICENSE +21 -0
- package/README.i18n.yaml +94 -0
- package/README.md +215 -2
- package/README.zh.md +216 -0
- package/lib/index.js +3333 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +991 -0
- package/lib/typert.remote-client.d.ts +25 -0
- package/lib/typert.remote-client.js +114 -0
- package/lib/types/activation.d.ts +114 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/archive-admission.d.ts +16 -0
- package/lib/types/archive-admission.js +151 -0
- package/lib/types/assistant-output.d.ts +48 -0
- package/lib/types/assistant-output.js +73 -0
- package/lib/types/catalog.d.ts +92 -0
- package/lib/types/catalog.js +113 -0
- package/lib/types/child-agent.d.ts +154 -0
- package/lib/types/child-agent.js +211 -0
- package/lib/types/client.d.ts +8 -0
- package/lib/types/client.js +7 -0
- package/lib/types/continuation-messages.d.ts +63 -0
- package/lib/types/continuation-messages.js +114 -0
- package/lib/types/control-types.d.ts +142 -0
- package/lib/types/control-types.js +8 -0
- package/lib/types/control.d.ts +45 -0
- package/lib/types/control.js +79 -0
- package/lib/types/depth.d.ts +31 -0
- package/lib/types/depth.js +39 -0
- package/lib/types/descriptor.d.ts +105 -0
- package/lib/types/descriptor.js +192 -0
- package/lib/types/error.d.ts +11 -0
- package/lib/types/error.js +14 -0
- package/lib/types/index.d.ts +319 -0
- package/lib/types/index.js +455 -0
- package/lib/types/internal.d.ts +58 -0
- package/lib/types/internal.js +58 -0
- package/lib/types/lifecycle.d.ts +83 -0
- package/lib/types/lifecycle.js +115 -0
- package/lib/types/list-children.d.ts +36 -0
- package/lib/types/list-children.js +166 -0
- package/lib/types/manager.d.ts +247 -0
- package/lib/types/manager.js +1294 -0
- package/lib/types/out-of-process.d.ts +92 -0
- package/lib/types/out-of-process.js +178 -0
- package/lib/types/projection-types.d.ts +85 -0
- package/lib/types/projection-types.js +7 -0
- package/lib/types/projection.d.ts +100 -0
- package/lib/types/projection.js +157 -0
- package/lib/types/structured.d.ts +49 -0
- package/lib/types/structured.js +130 -0
- package/lib/types/types.d.ts +353 -0
- package/lib/types/types.js +19 -0
- package/package.json +136 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Generated by @qlzt/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import type {
|
|
3
|
+
RemoteResult,
|
|
4
|
+
TypertRemoteContribution,
|
|
5
|
+
} from '@qlzt/dsh-typert-protocol'
|
|
6
|
+
import type { SessionId } from '@qlzt/dsh-session/types'
|
|
7
|
+
import type { SubagentInterruptReceipt, SubagentPromptReceipt, SubagentPromptRequest } from '@qlzt/dsh-subagent/client'
|
|
8
|
+
|
|
9
|
+
declare module '@qlzt/dsh-typert-protocol' {
|
|
10
|
+
interface TypertRemoteNamespace$7375626167656e7473 {
|
|
11
|
+
interruptByParent: (childSessionId: SessionId, parentSessionId: SessionId, mode: 'continuable') => Promise<RemoteResult<SubagentInterruptReceipt>>
|
|
12
|
+
prompt: (request: SubagentPromptRequest, signal?: AbortSignal) => Promise<RemoteResult<SubagentPromptReceipt>>
|
|
13
|
+
}
|
|
14
|
+
interface TypertRemoteMap {
|
|
15
|
+
'subagents/interruptByParent': (childSessionId: SessionId, parentSessionId: SessionId, mode: 'continuable') => Promise<RemoteResult<SubagentInterruptReceipt>>
|
|
16
|
+
'subagents/prompt': (request: SubagentPromptRequest, signal?: AbortSignal) => Promise<RemoteResult<SubagentPromptReceipt>>
|
|
17
|
+
}
|
|
18
|
+
interface TypertRemoteNamespaceMap {
|
|
19
|
+
'subagents': TypertRemoteNamespace$7375626167656e7473
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export declare const TYPERT_REMOTE: TypertRemoteContribution
|
|
24
|
+
export default TYPERT_REMOTE
|
|
25
|
+
//# sourceMappingURL=typert.remote-client.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* Generated by @qlzt/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
let _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_0$schema$value
|
|
5
|
+
const _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_0$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_0$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
6
|
+
let _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_1$schema$value
|
|
7
|
+
const _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_1$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_1$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
8
|
+
let _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_2$schema$value
|
|
9
|
+
const _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_2$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_2$schema$value ??= z.literal("continuable"))
|
|
10
|
+
let _deepseek_ai_dsh_subagent_subagents_interruptByParent_result$schema$value
|
|
11
|
+
const _deepseek_ai_dsh_subagent_subagents_interruptByParent_result$schema = () => (_deepseek_ai_dsh_subagent_subagents_interruptByParent_result$schema$value ??= z.object({
|
|
12
|
+
'accepted': z.literal(true).readonly(),
|
|
13
|
+
}))
|
|
14
|
+
let _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema$value
|
|
15
|
+
const _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema = () => (_deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema$value ??= z.object({
|
|
16
|
+
'requestId': z.intersection(z.string(), z.unknown()).readonly(),
|
|
17
|
+
'parentSessionId': z.intersection(z.string(), z.unknown()).readonly(),
|
|
18
|
+
'childSessionId': z.intersection(z.string(), z.unknown()).readonly(),
|
|
19
|
+
'mode': z.literal("continuable").readonly(),
|
|
20
|
+
'delivery': z.union([z.literal("queue"), z.literal("steer")]).readonly(),
|
|
21
|
+
'content': z.array(z.union([z.object({
|
|
22
|
+
'type': z.literal("text").readonly(),
|
|
23
|
+
'text': z.string().readonly(),
|
|
24
|
+
}), z.object({
|
|
25
|
+
'type': z.literal("image").readonly(),
|
|
26
|
+
'mediaType': z.union([z.literal("image/png"), z.literal("image/jpeg"), z.literal("image/webp"), z.literal("image/gif")]).readonly(),
|
|
27
|
+
'data': z.string().readonly(),
|
|
28
|
+
'name': z.string().readonly().optional(),
|
|
29
|
+
})])).readonly(),
|
|
30
|
+
'clientTimeZone': z.string().readonly().optional(),
|
|
31
|
+
}))
|
|
32
|
+
let _deepseek_ai_dsh_subagent_subagents_prompt_result$schema$value
|
|
33
|
+
const _deepseek_ai_dsh_subagent_subagents_prompt_result$schema = () => (_deepseek_ai_dsh_subagent_subagents_prompt_result$schema$value ??= z.object({
|
|
34
|
+
'messageId': z.intersection(z.string(), z.unknown()).readonly(),
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
export const TYPERT_REMOTE = {
|
|
38
|
+
package: '@qlzt/dsh-subagent',
|
|
39
|
+
descriptors: [
|
|
40
|
+
{
|
|
41
|
+
id: '@qlzt/dsh-subagent#subagents/interruptByParent',
|
|
42
|
+
service: 'subagents',
|
|
43
|
+
namespace: 'subagents',
|
|
44
|
+
method: 'interruptByParent',
|
|
45
|
+
invocation: { kind: 'direct' },
|
|
46
|
+
parameters: [
|
|
47
|
+
{
|
|
48
|
+
name: 'childSessionId',
|
|
49
|
+
wire: 'childSessionId',
|
|
50
|
+
source: 'json',
|
|
51
|
+
codec: {
|
|
52
|
+
mode: 'strict',
|
|
53
|
+
typeSymbol: '@qlzt/dsh-session/types#SessionId',
|
|
54
|
+
create: _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_0$schema,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'parentSessionId',
|
|
59
|
+
wire: 'parentSessionId',
|
|
60
|
+
source: 'json',
|
|
61
|
+
codec: {
|
|
62
|
+
mode: 'strict',
|
|
63
|
+
typeSymbol: '@qlzt/dsh-session/types#SessionId',
|
|
64
|
+
create: _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_1$schema,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'mode',
|
|
69
|
+
wire: 'mode',
|
|
70
|
+
source: 'json',
|
|
71
|
+
codec: {
|
|
72
|
+
mode: 'strict',
|
|
73
|
+
typeSymbol: '@qlzt/dsh-subagent#subagents/interruptByParent:mode',
|
|
74
|
+
create: _deepseek_ai_dsh_subagent_subagents_interruptByParent_parameter_2$schema,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
result: {
|
|
79
|
+
mode: 'strict',
|
|
80
|
+
typeSymbol: '@qlzt/dsh-subagent/client#SubagentInterruptReceipt',
|
|
81
|
+
create: _deepseek_ai_dsh_subagent_subagents_interruptByParent_result$schema,
|
|
82
|
+
},
|
|
83
|
+
sourceLocation: {"file":"packages/subagent/subagent/src/index.ts","line":513,"column":3},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: '@qlzt/dsh-subagent#subagents/prompt',
|
|
87
|
+
service: 'subagents',
|
|
88
|
+
namespace: 'subagents',
|
|
89
|
+
method: 'prompt',
|
|
90
|
+
invocation: { kind: 'direct' },
|
|
91
|
+
parameters: [
|
|
92
|
+
{
|
|
93
|
+
name: 'request',
|
|
94
|
+
wire: 'request',
|
|
95
|
+
source: 'json',
|
|
96
|
+
codec: {
|
|
97
|
+
mode: 'strict',
|
|
98
|
+
typeSymbol: '@qlzt/dsh-subagent/client#SubagentPromptRequest',
|
|
99
|
+
create: _deepseek_ai_dsh_subagent_subagents_prompt_parameter_0$schema,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
cancellation: { parameter: 'signal' },
|
|
104
|
+
result: {
|
|
105
|
+
mode: 'strict',
|
|
106
|
+
typeSymbol: '@qlzt/dsh-subagent/client#SubagentPromptReceipt',
|
|
107
|
+
create: _deepseek_ai_dsh_subagent_subagents_prompt_result$schema,
|
|
108
|
+
},
|
|
109
|
+
sourceLocation: {"file":"packages/subagent/subagent/src/index.ts","line":446,"column":9},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default TYPERT_REMOTE
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Execution-period records, capacity slots, child serialization, and local output capture.
|
|
2
|
+
* @module @qlzt/dsh-subagent/activation
|
|
3
|
+
*/
|
|
4
|
+
import type { Agent, AgentHandle } from '@qlzt/dsh-agent';
|
|
5
|
+
import type { SessionId, SessionLogOffset } from '@qlzt/dsh-session';
|
|
6
|
+
import type { ActivationObserver } from './lifecycle.ts';
|
|
7
|
+
import type { StructuredAttachment } from './structured.ts';
|
|
8
|
+
import type { SubagentResult, SubagentRun } from './types.ts';
|
|
9
|
+
/** Process-local slots shared through uninterrupted continuable parent links. */
|
|
10
|
+
export declare class ActivationPool {
|
|
11
|
+
private readonly slots;
|
|
12
|
+
/**
|
|
13
|
+
* Reserve before reconstruction; release also tolerates unpublished rollback.
|
|
14
|
+
* @param capacity - the maximum simultaneous activations in this pool.
|
|
15
|
+
* @returns an idempotent slot release.
|
|
16
|
+
*/
|
|
17
|
+
reserve(capacity: number): () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* One residency epoch for a local child or an external execution.
|
|
21
|
+
* The manager owns admission, execution handles, and resource release.
|
|
22
|
+
*/
|
|
23
|
+
interface ActivationBase {
|
|
24
|
+
/** Shared capacity for this Activation and all its managed descendants. */
|
|
25
|
+
readonly pool: ActivationPool;
|
|
26
|
+
/** Return this epoch's slot after its handle has finished disposal. */
|
|
27
|
+
readonly releaseSlot: () => void;
|
|
28
|
+
/** The durable child this Activation is an epoch of. */
|
|
29
|
+
readonly childId: SessionId;
|
|
30
|
+
readonly parent: Agent;
|
|
31
|
+
readonly delivery: 'parent' | 'caller';
|
|
32
|
+
readonly result: PromiseWithResolvers<SubagentResult>;
|
|
33
|
+
/** Published synchronously before teardown; subsequent callers share this transaction. */
|
|
34
|
+
closing: Promise<void> | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Exact live Agent ancestry observed when this Activation materialized.
|
|
37
|
+
* Weak membership preserves host-scope identity across an intermediate
|
|
38
|
+
* ancestor leaving the registry without retaining that ancestor's runtime.
|
|
39
|
+
*/
|
|
40
|
+
readonly ancestry: WeakSet<Agent>;
|
|
41
|
+
/**
|
|
42
|
+
* Session ids of the child Activations this one owns. Because one Session has
|
|
43
|
+
* at most one live Activation, the id identifies the live child without
|
|
44
|
+
* another runtime-incarnation reference. Non-empty blocks settlement.
|
|
45
|
+
*/
|
|
46
|
+
readonly ownedChildren: Set<SessionId>;
|
|
47
|
+
/** The lifecycle observer that emits this epoch's start and terminal edges. */
|
|
48
|
+
readonly observer: ActivationObserver;
|
|
49
|
+
/**
|
|
50
|
+
* Whether any delivery to this child was ever accepted. A materialization
|
|
51
|
+
* rolled back before its first acceptance is a child the caller was told does
|
|
52
|
+
* not exist, so its teardown owes the parent no settlement account.
|
|
53
|
+
*/
|
|
54
|
+
announced: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Resources retained only for the selected execution kind. */
|
|
57
|
+
export type ActivationResource = {
|
|
58
|
+
readonly kind: 'local';
|
|
59
|
+
readonly handle: AgentHandle;
|
|
60
|
+
readonly outputStart: SessionLogOffset;
|
|
61
|
+
readonly structured: StructuredAttachment | undefined;
|
|
62
|
+
/** Latest live failure not superseded by a later committed turn ending. */
|
|
63
|
+
failureAt: SessionLogOffset | undefined;
|
|
64
|
+
/** Renewed when local settlement must recheck input or owned children. */
|
|
65
|
+
poke: PromiseWithResolvers<void>;
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: 'external';
|
|
68
|
+
readonly run: SubagentRun;
|
|
69
|
+
readonly controller: AbortController;
|
|
70
|
+
};
|
|
71
|
+
/** One managed execution period with its provider-specific resources. */
|
|
72
|
+
export type Activation = ActivationBase & ActivationResource;
|
|
73
|
+
/** Local residency with input admission and Session-backed result capture. */
|
|
74
|
+
export type LocalActivation = Extract<Activation, {
|
|
75
|
+
kind: 'local';
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Read the live Agent retained by local execution.
|
|
79
|
+
* @param activation - the managed local or external execution.
|
|
80
|
+
* @returns the local Agent, absent for an external run.
|
|
81
|
+
*/
|
|
82
|
+
export declare function activationAgent(activation: Activation): Agent | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Require local residency before accepting further input.
|
|
85
|
+
* @param activation - execution targeted by message delivery.
|
|
86
|
+
* @returns the local activation.
|
|
87
|
+
* @throws when the external backend cannot accept another message.
|
|
88
|
+
*/
|
|
89
|
+
export declare function requireLocalActivation(activation: Activation): LocalActivation;
|
|
90
|
+
/**
|
|
91
|
+
* Retain local failures whose turn ending could not be committed.
|
|
92
|
+
* @param activation - exact local Agent and epoch receiving the scoped observers.
|
|
93
|
+
* @param wake - notify its settlement watcher when failure changes readiness.
|
|
94
|
+
*/
|
|
95
|
+
export declare function observeLocalFailure(activation: LocalActivation, wake: () => void): void;
|
|
96
|
+
/**
|
|
97
|
+
* Capture local output and retain failures without a later committed turn ending.
|
|
98
|
+
* @param activation - the settled local execution and any structured result.
|
|
99
|
+
* @returns final output, stop reason, and committed structured value.
|
|
100
|
+
*/
|
|
101
|
+
export declare function captureLocalResult(activation: LocalActivation): SubagentResult;
|
|
102
|
+
/** Serialize each durable child's delivery, release, and disposal. */
|
|
103
|
+
export declare class ChildLock {
|
|
104
|
+
private tails;
|
|
105
|
+
/**
|
|
106
|
+
* Run `operation` after every previously queued operation for `childId`.
|
|
107
|
+
* @param childId - the durable child whose operations are linearized.
|
|
108
|
+
* @param operation - the critical section to run in order.
|
|
109
|
+
* @returns the operation's own settlement.
|
|
110
|
+
*/
|
|
111
|
+
run<T>(childId: SessionId, operation: () => Promise<T>): Promise<T>;
|
|
112
|
+
}
|
|
113
|
+
export {};
|
|
114
|
+
//# sourceMappingURL=activation.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Execution-period records, capacity slots, child serialization, and local output capture.
|
|
2
|
+
* @module @qlzt/dsh-subagent/activation
|
|
3
|
+
*/
|
|
4
|
+
import { assertNever } from '@qlzt/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 slots shared through uninterrupted continuable parent links. */
|
|
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,16 @@
|
|
|
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.
|
|
5
|
+
*
|
|
6
|
+
* @module @qlzt/dsh-subagent
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
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. Both listeners live as long as `ctx`'s fiber.
|
|
13
|
+
* @param ctx - context carrying the Agent registry; the Session query service is optional and only supplies labels.
|
|
14
|
+
*/
|
|
15
|
+
export declare function installSubagentArchiveAdmission(ctx: Context): void;
|
|
16
|
+
//# sourceMappingURL=archive-admission.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
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.
|
|
5
|
+
*
|
|
6
|
+
* @module @qlzt/dsh-subagent
|
|
7
|
+
*/
|
|
8
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
9
|
+
if (value !== null && value !== void 0) {
|
|
10
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
11
|
+
var dispose, inner;
|
|
12
|
+
if (async) {
|
|
13
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
14
|
+
dispose = value[Symbol.asyncDispose];
|
|
15
|
+
}
|
|
16
|
+
if (dispose === void 0) {
|
|
17
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
18
|
+
dispose = value[Symbol.dispose];
|
|
19
|
+
if (async) inner = dispose;
|
|
20
|
+
}
|
|
21
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
22
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
23
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
24
|
+
}
|
|
25
|
+
else if (async) {
|
|
26
|
+
env.stack.push({ async: true });
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
30
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
31
|
+
return function (env) {
|
|
32
|
+
function fail(e) {
|
|
33
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
34
|
+
env.hasError = true;
|
|
35
|
+
}
|
|
36
|
+
var r, s = 0;
|
|
37
|
+
function next() {
|
|
38
|
+
while (r = env.stack.pop()) {
|
|
39
|
+
try {
|
|
40
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
41
|
+
if (r.dispose) {
|
|
42
|
+
var result = r.dispose.call(r.value);
|
|
43
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
44
|
+
}
|
|
45
|
+
else s |= 1;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
fail(e);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
52
|
+
if (env.hasError) throw env.error;
|
|
53
|
+
}
|
|
54
|
+
return next();
|
|
55
|
+
};
|
|
56
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
57
|
+
var e = new Error(message);
|
|
58
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
59
|
+
});
|
|
60
|
+
import { foldSubagentDescriptor } from "./descriptor.js";
|
|
61
|
+
/**
|
|
62
|
+
* Answer `workspace/session-activity` with the running subagent descendants of
|
|
63
|
+
* the asked Session, and `workspace/session-stop` by cancelling each of them
|
|
64
|
+
* as their parent would. Both listeners live as long as `ctx`'s fiber.
|
|
65
|
+
* @param ctx - context carrying the Agent registry; the Session query service is optional and only supplies labels.
|
|
66
|
+
*/
|
|
67
|
+
export function installSubagentArchiveAdmission(ctx) {
|
|
68
|
+
ctx.on('workspace/session-activity', async ({ sessionId }, next) => {
|
|
69
|
+
const running = runningDescendants(ctx, sessionId);
|
|
70
|
+
const rest = await next();
|
|
71
|
+
if (running.length === 0)
|
|
72
|
+
return rest;
|
|
73
|
+
const own = { kind: 'subagent', items: await Promise.all(running.map(child => describe(ctx, child))) };
|
|
74
|
+
return [own, ...rest];
|
|
75
|
+
});
|
|
76
|
+
ctx.on('workspace/session-stop', ({ sessionId }) => {
|
|
77
|
+
for (const child of runningDescendants(ctx, sessionId)) {
|
|
78
|
+
try {
|
|
79
|
+
child.cancel({ kind: 'parent' });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// One child refusing its cancel must not keep its siblings running for an archived parent.
|
|
83
|
+
ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Live subagent descendants inside a turn, by durable lineage: a child whose
|
|
90
|
+
* header names its parent and carries the subagent origin this package
|
|
91
|
+
* records, at any depth. A fork shares the lineage field without the origin
|
|
92
|
+
* and is an independent conversation, so it never holds its source. Lineage
|
|
93
|
+
* is read as data, so a damaged header chain that loops is visited once.
|
|
94
|
+
*/
|
|
95
|
+
function runningDescendants(ctx, rootId) {
|
|
96
|
+
const childrenOf = new Map();
|
|
97
|
+
for (const agent of ctx.agents.list()) {
|
|
98
|
+
const { parentSession, origin } = agent.session.header;
|
|
99
|
+
if (parentSession === undefined || origin !== 'subagent')
|
|
100
|
+
continue;
|
|
101
|
+
const siblings = childrenOf.get(parentSession) ?? [];
|
|
102
|
+
siblings.push(agent);
|
|
103
|
+
childrenOf.set(parentSession, siblings);
|
|
104
|
+
}
|
|
105
|
+
const running = [];
|
|
106
|
+
const pending = [rootId];
|
|
107
|
+
const visited = new Set();
|
|
108
|
+
while (pending.length > 0) {
|
|
109
|
+
const parentId = pending.shift();
|
|
110
|
+
if (visited.has(parentId))
|
|
111
|
+
continue;
|
|
112
|
+
visited.add(parentId);
|
|
113
|
+
for (const child of childrenOf.get(parentId) ?? []) {
|
|
114
|
+
if (child.status === 'running')
|
|
115
|
+
running.push(child);
|
|
116
|
+
pending.push(child.id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return running;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The child's activity item: its id, plus the durable creation label its
|
|
123
|
+
* descriptor carries, read through a live Session observation. Without the
|
|
124
|
+
* Session query service, or with a descriptor that is absent or unreadable,
|
|
125
|
+
* the item names the child by id alone.
|
|
126
|
+
*/
|
|
127
|
+
async function describe(ctx, child) {
|
|
128
|
+
const query = ctx.get('sessionQuery');
|
|
129
|
+
if (query === undefined)
|
|
130
|
+
return { id: child.id };
|
|
131
|
+
try {
|
|
132
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
133
|
+
try {
|
|
134
|
+
const observation = __addDisposableResource(env_1, await query.observeSession(child.id, { projectionMode: 'none' }), false);
|
|
135
|
+
const label = foldSubagentDescriptor(observation.events.slice(observation.inheritedEventCount))?.label;
|
|
136
|
+
return label === undefined ? { id: child.id } : { id: child.id, label };
|
|
137
|
+
}
|
|
138
|
+
catch (e_1) {
|
|
139
|
+
env_1.error = e_1;
|
|
140
|
+
env_1.hasError = true;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
__disposeResources(env_1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// A damaged descriptor is data damage in the child; the report still names the child.
|
|
148
|
+
return { id: child.id };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# 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 @qlzt/dsh-subagent/assistant-output
|
|
11
|
+
*/
|
|
12
|
+
import { type ContentBlock } from '@qlzt/dsh-llm';
|
|
13
|
+
import type { SessionEvent } from '@qlzt/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 @qlzt/dsh-subagent/assistant-output
|
|
11
|
+
*/
|
|
12
|
+
import { joinAssistantStreamText } from '@qlzt/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
|