@hydraharness/harness-subagent 0.1.1-rc.10

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/lib/index.js +2750 -0
  4. package/lib/invariant.js +76 -0
  5. package/lib/types/activation-setup-registry.d.ts +57 -0
  6. package/lib/types/activation-setup-registry.js +148 -0
  7. package/lib/types/assistant-output.d.ts +48 -0
  8. package/lib/types/assistant-output.js +72 -0
  9. package/lib/types/budget.d.ts +34 -0
  10. package/lib/types/budget.js +54 -0
  11. package/lib/types/child-agent.d.ts +139 -0
  12. package/lib/types/child-agent.js +178 -0
  13. package/lib/types/client.d.ts +7 -0
  14. package/lib/types/client.js +7 -0
  15. package/lib/types/continuation.d.ts +457 -0
  16. package/lib/types/continuation.js +1157 -0
  17. package/lib/types/depth.d.ts +31 -0
  18. package/lib/types/depth.js +39 -0
  19. package/lib/types/descriptor-seed.d.ts +21 -0
  20. package/lib/types/descriptor-seed.js +24 -0
  21. package/lib/types/descriptor.d.ts +139 -0
  22. package/lib/types/descriptor.js +189 -0
  23. package/lib/types/error.d.ts +11 -0
  24. package/lib/types/error.js +14 -0
  25. package/lib/types/index.d.ts +296 -0
  26. package/lib/types/index.js +374 -0
  27. package/lib/types/invariant.d.ts +13 -0
  28. package/lib/types/invariant.js +91 -0
  29. package/lib/types/lifecycle.d.ts +113 -0
  30. package/lib/types/lifecycle.js +177 -0
  31. package/lib/types/list-children.d.ts +112 -0
  32. package/lib/types/list-children.js +316 -0
  33. package/lib/types/out-of-process.d.ts +117 -0
  34. package/lib/types/out-of-process.js +211 -0
  35. package/lib/types/projection-types.d.ts +60 -0
  36. package/lib/types/projection-types.js +7 -0
  37. package/lib/types/projection.d.ts +95 -0
  38. package/lib/types/projection.js +150 -0
  39. package/lib/types/run-settlement.d.ts +17 -0
  40. package/lib/types/run-settlement.js +66 -0
  41. package/lib/types/types.d.ts +315 -0
  42. package/lib/types/types.js +19 -0
  43. package/package.json +112 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pure client-safe subagent projection vocabulary.
3
+ *
4
+ * @module @hydraharness/harness-subagent/projection-types
5
+ */
6
+ /** Durable active-turn timing for one descriptor-backed child session. */
7
+ export interface SubagentTimingProjection {
8
+ /** Milliseconds accumulated across completed turns after the child's own descriptor. */
9
+ settledMs: number;
10
+ /** Same-cut bounds of the currently open turn, when one has not reached `turn/end`. */
11
+ active?: {
12
+ /** Start of the open turn. */
13
+ since: number;
14
+ /** Latest event time folded into this projection cut. */
15
+ through: number;
16
+ };
17
+ }
18
+ /**
19
+ * Durable identity of one descriptor-backed subagent session: lifecycle mode
20
+ * plus creation label, folded last-wins from `subagent/descriptor` events.
21
+ * Label strength follows the descriptor schema: a continuable child always
22
+ * carries one, a one-shot child may omit it.
23
+ */
24
+ export type SubagentIdentityProjection = {
25
+ /** A terminal one-shot child. */
26
+ mode: 'one-shot';
27
+ /** Optional durable creation label from the child's descriptor. */
28
+ label?: string;
29
+ /**
30
+ * Seq of the `subagent/descriptor` event this identity was folded from.
31
+ * `seq >= header.seedLength` proves the identity comes from the child's
32
+ * OWN log suffix — where a descriptor is immutable once appended — and
33
+ * not from a fork seed's replayed ancestor descriptor.
34
+ */
35
+ seq: number;
36
+ } | {
37
+ /** A resumable conversation. */
38
+ mode: 'continuable';
39
+ /** Durable creation label from the child's descriptor. */
40
+ label: string;
41
+ /** Seq of the folded descriptor event; see the one-shot arm for the own-suffix proof. */
42
+ seq: number;
43
+ };
44
+ declare module '@hydraharness/harness-session-projection/types' {
45
+ interface SessionProjectionMap {
46
+ /** Active-turn duration for a descriptor-backed subagent session. */
47
+ subagentTiming: SubagentTimingProjection;
48
+ /**
49
+ * Identity of a descriptor-backed subagent session. `null` ⟺ no valid
50
+ * descriptor (missing, malformed, or unrecognized-version — deliberately
51
+ * undistinguished). The sentinel is deliberately serializable: a
52
+ * value pushed over JSON transports must survive `JSON.stringify`
53
+ * losslessly, where an `undefined` field would be dropped and a stale
54
+ * identity would survive on the receiving side. The entry itself stays
55
+ * non-optional.
56
+ */
57
+ subagent: SubagentIdentityProjection | null;
58
+ }
59
+ }
60
+ //# sourceMappingURL=projection-types.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Pure client-safe subagent projection vocabulary.
3
+ *
4
+ * @module @hydraharness/harness-subagent/projection-types
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=projection-types.js.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pure session projections for subagent identity (mode/label) and active-turn
3
+ * duration.
4
+ *
5
+ * @module @hydraharness/harness-subagent/projection
6
+ */
7
+ import { z } from 'zod';
8
+ import type { SessionEvent } from '@hydraharness/harness-session';
9
+ import type { SubagentIdentityProjection, SubagentTimingProjection } from './projection-types.ts';
10
+ /** Fold state for a subagent's latest timing snapshot. */
11
+ export interface TimingState {
12
+ /** Milliseconds accumulated across completed post-descriptor turns. */
13
+ settledMs: number;
14
+ /** Current open interval kept paired inside the fold. */
15
+ active?: {
16
+ since: number;
17
+ through: number;
18
+ } | undefined;
19
+ /** Latest pre-descriptor turn start, promoted when the child's own descriptor arrives. */
20
+ pendingTurnStart?: number | undefined;
21
+ /** Whether the fold has crossed a descriptor in this logical log. */
22
+ descriptorSeen: boolean;
23
+ }
24
+ declare module '@hydraharness/harness-session-projection/types' {
25
+ interface SessionProjectionStateMap {
26
+ subagentTiming: TimingState;
27
+ subagent: IdentityState;
28
+ }
29
+ }
30
+ /**
31
+ * Fold turn boundaries around the child's own durable descriptor.
32
+ *
33
+ * A fork seed may contain an ancestor descriptor and completed turns. Every
34
+ * descriptor therefore resets the accumulated state; the healthy catalog
35
+ * admits only a child with exactly one descriptor in its own suffix, making
36
+ * the final reset the child's authoritative timing origin.
37
+ */
38
+ export declare const subagentTimingProjectionDefinition: {
39
+ key: "subagentTiming";
40
+ stateSchema: z.ZodType<TimingState, unknown, z.core.$ZodTypeInternals<TimingState, unknown>>;
41
+ init: () => {
42
+ descriptorSeen: false;
43
+ settledMs: number;
44
+ };
45
+ apply: (state: NoInfer<TimingState>, event: SessionEvent) => {
46
+ /** Milliseconds accumulated across completed post-descriptor turns. */
47
+ settledMs: number;
48
+ /** Current open interval kept paired inside the fold. */
49
+ active?: {
50
+ since: number;
51
+ through: number;
52
+ } | undefined;
53
+ /** Whether the fold has crossed a descriptor in this logical log. */
54
+ descriptorSeen: boolean;
55
+ };
56
+ wire: {
57
+ viewSchema: z.ZodType<SubagentTimingProjection, unknown, z.core.$ZodTypeInternals<SubagentTimingProjection, unknown>>;
58
+ view: (state: NoInfer<TimingState>) => {
59
+ active?: {
60
+ since: number;
61
+ through: number;
62
+ };
63
+ settledMs: number;
64
+ };
65
+ };
66
+ stateVersion: number;
67
+ };
68
+ interface IdentityState {
69
+ /** Identity from the last valid descriptor; absent before one, and after an invalid one. */
70
+ identity?: SubagentIdentityProjection | undefined;
71
+ }
72
+ /**
73
+ * Fold the durable mode/label identity from `subagent/descriptor` events,
74
+ * last-wins: a fork seed may replay an ancestor's descriptor, and the child's
75
+ * own descriptor must override it — the same reset discipline as
76
+ * {@link subagentTimingProjectionDefinition}. A malformed or unknown-version
77
+ * payload resets to the `null` sentinel instead of throwing, so a fork of a
78
+ * healthy ancestor never inherits an identity its own descriptor failed to
79
+ * establish — and the reset survives every JSON push frame, so a consumer
80
+ * holding the earlier identity replaces it instead of keeping it stale;
81
+ * `null` ⟺ no valid descriptor, with the causes deliberately undistinguished.
82
+ */
83
+ export declare const subagentIdentityProjectionDefinition: {
84
+ key: "subagent";
85
+ stateSchema: z.ZodType<IdentityState, unknown, z.core.$ZodTypeInternals<IdentityState, unknown>>;
86
+ init: () => {};
87
+ apply: (state: NoInfer<IdentityState>, event: SessionEvent) => IdentityState;
88
+ wire: {
89
+ viewSchema: z.ZodNullable<z.ZodType<SubagentIdentityProjection, unknown, z.core.$ZodTypeInternals<SubagentIdentityProjection, unknown>>>;
90
+ view: (state: NoInfer<IdentityState>) => SubagentIdentityProjection | null;
91
+ };
92
+ stateVersion: number;
93
+ };
94
+ export {};
95
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Pure session projections for subagent identity (mode/label) and active-turn
3
+ * duration.
4
+ *
5
+ * @module @hydraharness/harness-subagent/projection
6
+ */
7
+ import { z } from 'zod';
8
+ import { foldSubagentDescriptor } from "./descriptor.js";
9
+ const activeIntervalSchema = z.object({
10
+ since: z.number().int().nonnegative(),
11
+ through: z.number().int().nonnegative(),
12
+ }).strict();
13
+ const projectionSchema = z.object({
14
+ settledMs: z.number().int().nonnegative(),
15
+ active: activeIntervalSchema.optional(),
16
+ }).strict().transform(({ settledMs, active }) => ({
17
+ settledMs,
18
+ ...active === undefined ? {} : { active },
19
+ }));
20
+ const timingStateSchema = z.object({
21
+ settledMs: z.number().int().nonnegative(),
22
+ active: activeIntervalSchema.optional(),
23
+ pendingTurnStart: z.number().int().nonnegative().optional(),
24
+ descriptorSeen: z.boolean(),
25
+ }).strict();
26
+ /**
27
+ * Fold turn boundaries around the child's own durable descriptor.
28
+ *
29
+ * A fork seed may contain an ancestor descriptor and completed turns. Every
30
+ * descriptor therefore resets the accumulated state; the healthy catalog
31
+ * admits only a child with exactly one descriptor in its own suffix, making
32
+ * the final reset the child's authoritative timing origin.
33
+ */
34
+ export const subagentTimingProjectionDefinition = {
35
+ key: 'subagentTiming',
36
+ stateSchema: timingStateSchema,
37
+ init: () => ({ descriptorSeen: false, settledMs: 0 }),
38
+ apply: (state, event) => {
39
+ if (event.type === 'turn/start') {
40
+ return state.descriptorSeen
41
+ ? { ...state, active: { since: event.time, through: event.time } }
42
+ : { ...state, pendingTurnStart: event.time };
43
+ }
44
+ if (event.type === 'subagent/descriptor') {
45
+ const activeSince = state.active?.since ?? state.pendingTurnStart;
46
+ return {
47
+ descriptorSeen: true,
48
+ settledMs: 0,
49
+ ...(activeSince === undefined
50
+ ? {}
51
+ : { active: { since: activeSince, through: event.time } }),
52
+ };
53
+ }
54
+ if (event.type === 'turn/end') {
55
+ if (!state.descriptorSeen) {
56
+ if (state.pendingTurnStart === undefined)
57
+ return state;
58
+ const { pendingTurnStart: _closed, ...next } = state;
59
+ return next;
60
+ }
61
+ if (state.active === undefined)
62
+ return state;
63
+ const { active, ...rest } = state;
64
+ return {
65
+ ...rest,
66
+ settledMs: state.settledMs + Math.max(0, event.time - active.since),
67
+ };
68
+ }
69
+ if (state.active === undefined)
70
+ return state;
71
+ return { ...state, active: { ...state.active, through: event.time } };
72
+ },
73
+ wire: {
74
+ viewSchema: projectionSchema,
75
+ view: state => ({
76
+ settledMs: state.settledMs,
77
+ ...(state.active === undefined ? {} : { active: state.active }),
78
+ }),
79
+ },
80
+ stateVersion: 2,
81
+ };
82
+ // The cast bridges only the optional-label arm: Zod's optional output
83
+ // includes explicit `undefined`, which exactOptionalPropertyTypes excludes
84
+ // from the public interface. The no-value state itself is the serializable
85
+ // `null` arm — never `undefined` — so every registry read and push frame
86
+ // survives JSON.stringify losslessly.
87
+ const identityValueSchema = z.discriminatedUnion('mode', [
88
+ z.object({
89
+ mode: z.literal('one-shot'),
90
+ label: z.string().optional(),
91
+ seq: z.number().int().nonnegative(),
92
+ }).strict(),
93
+ z.object({
94
+ mode: z.literal('continuable'),
95
+ label: z.string(),
96
+ seq: z.number().int().nonnegative(),
97
+ }).strict(),
98
+ ]);
99
+ const identitySchema = identityValueSchema.nullable();
100
+ const identityStateSchema = z.object({
101
+ identity: identityValueSchema.optional(),
102
+ }).strict();
103
+ /** Interpret one `subagent/descriptor` event's identity; no value when the payload cannot be trusted. */
104
+ function descriptorIdentity(event) {
105
+ let descriptor;
106
+ try {
107
+ descriptor = foldSubagentDescriptor([event]);
108
+ }
109
+ catch {
110
+ // Only a malformed current-version payload throws in descriptor parsing;
111
+ // a projection fold must never throw, so damage folds to no value.
112
+ descriptor = undefined;
113
+ }
114
+ if (descriptor === undefined)
115
+ return undefined;
116
+ return descriptor.mode === 'one-shot'
117
+ ? {
118
+ mode: 'one-shot',
119
+ ...descriptor.label !== undefined ? { label: descriptor.label } : {},
120
+ seq: event.seq,
121
+ }
122
+ : { mode: 'continuable', label: descriptor.label, seq: event.seq };
123
+ }
124
+ /**
125
+ * Fold the durable mode/label identity from `subagent/descriptor` events,
126
+ * last-wins: a fork seed may replay an ancestor's descriptor, and the child's
127
+ * own descriptor must override it — the same reset discipline as
128
+ * {@link subagentTimingProjectionDefinition}. A malformed or unknown-version
129
+ * payload resets to the `null` sentinel instead of throwing, so a fork of a
130
+ * healthy ancestor never inherits an identity its own descriptor failed to
131
+ * establish — and the reset survives every JSON push frame, so a consumer
132
+ * holding the earlier identity replaces it instead of keeping it stale;
133
+ * `null` ⟺ no valid descriptor, with the causes deliberately undistinguished.
134
+ */
135
+ export const subagentIdentityProjectionDefinition = {
136
+ key: 'subagent',
137
+ stateSchema: identityStateSchema,
138
+ init: () => ({}),
139
+ apply: (state, event) => {
140
+ if (event.type !== 'subagent/descriptor')
141
+ return state;
142
+ const identity = descriptorIdentity(event);
143
+ return identity === undefined ? {} : { identity };
144
+ },
145
+ wire: { viewSchema: identitySchema, view: state => state.identity ?? null },
146
+ // Bumped when the identity gained its `seq` field: an older checkpoint row
147
+ // would replay into a value the schema rejects, so it must refold instead.
148
+ stateVersion: 2,
149
+ };
150
+ //# sourceMappingURL=projection.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Settlement of one ONE-SHOT subagent run into a background-Task outcome. Only
3
+ * the one-shot background path uses Jobs; continuable children have no Task,
4
+ * no per-message result, and no Task cancellation.
5
+ *
6
+ * @module @hydraharness/harness-subagent/run-settlement
7
+ */
8
+ import type { JobOutcome } from '@hydraharness/harness-jobs';
9
+ import type { SubagentRun } from './types.ts';
10
+ /**
11
+ * Await the child result, dispose the run, then return its task outcome. Result
12
+ * and disposal failures become `failed`; when both fail, both details survive.
13
+ * @param run - live run to settle and release.
14
+ * @returns outcome after child resources are released.
15
+ */
16
+ export declare function settleRun(run: SubagentRun): Promise<JobOutcome>;
17
+ //# sourceMappingURL=run-settlement.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Settlement of one ONE-SHOT subagent run into a background-Task outcome. Only
3
+ * the one-shot background path uses Jobs; continuable children have no Task,
4
+ * no per-message result, and no Task cancellation.
5
+ *
6
+ * @module @hydraharness/harness-subagent/run-settlement
7
+ */
8
+ /** Flatten a child's final output blocks to the task's final text. */
9
+ function finalText(blocks) {
10
+ return blocks
11
+ .filter((block) => block.type === 'text')
12
+ .map(block => block.text)
13
+ .join('');
14
+ }
15
+ /** Render a failed stop reason with optional provider-authored detail. */
16
+ function failureDetail(result) {
17
+ const stopReason = result.stopReason;
18
+ return result.diagnostic === undefined
19
+ ? stopReason
20
+ : `${stopReason}; diagnostic: ${result.diagnostic}`;
21
+ }
22
+ /**
23
+ * Map a child result to the task outcome: completed carries final text,
24
+ * aborted is killed, and every other reason is failed without partial output.
25
+ * @param result - child terminal result.
26
+ * @returns outcome for the `ctx.jobs` registration.
27
+ */
28
+ function runOutcome(result) {
29
+ switch (result.stopReason) {
30
+ case 'completed':
31
+ return { status: 'completed', output: finalText(result.output) };
32
+ case 'aborted':
33
+ return { status: 'killed' };
34
+ case 'error':
35
+ case 'max-tokens':
36
+ case 'refusal':
37
+ return { status: 'failed', detail: failureDetail(result) };
38
+ // Merge-extensible reasons remain failures with provider-authored detail.
39
+ default:
40
+ return { status: 'failed', detail: failureDetail(result) };
41
+ }
42
+ }
43
+ /**
44
+ * Await the child result, dispose the run, then return its task outcome. Result
45
+ * and disposal failures become `failed`; when both fail, both details survive.
46
+ * @param run - live run to settle and release.
47
+ * @returns outcome after child resources are released.
48
+ */
49
+ export async function settleRun(run) {
50
+ let outcome;
51
+ try {
52
+ outcome = runOutcome(await run.result);
53
+ }
54
+ catch (error) {
55
+ outcome = { status: 'failed', detail: String(error) };
56
+ }
57
+ try {
58
+ await run.dispose();
59
+ }
60
+ catch (error) {
61
+ const prefix = outcome.detail === undefined ? '' : `${outcome.detail}; `;
62
+ return { status: 'failed', detail: `${prefix}dispose failed: ${String(error)}` };
63
+ }
64
+ return outcome;
65
+ }
66
+ //# sourceMappingURL=run-settlement.js.map