@multiclawcli/core 0.13.0 → 0.14.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.
@@ -1,7 +1,8 @@
1
1
  import type { EventBus } from "../event/EventBus";
2
2
  import type { AgentDefinition, AgentResult } from "../types/agent";
3
3
  import type { MultiClawConfig } from "../types/config";
4
- import type { AgentSessionManager } from "./AgentSessionManager";
4
+ import type { SessionIntent } from "../types/session";
5
+ import { AgentSessionManager } from "./AgentSessionManager";
5
6
  import type { ContextManager } from "./ContextManager";
6
7
  export declare class AgentRunner {
7
8
  private eventBus;
@@ -10,6 +11,8 @@ export declare class AgentRunner {
10
11
  private config;
11
12
  private sessionManager?;
12
13
  constructor(eventBus: EventBus, contextManager: ContextManager, globalWorkDir: string, config: MultiClawConfig, sessionManager?: AgentSessionManager | undefined);
14
+ private sessionTask;
15
+ previewSession(agent: AgentDefinition): SessionIntent;
13
16
  run(agent: AgentDefinition, signal?: AbortSignal, instruction?: string): Promise<AgentResult>;
14
17
  /**
15
18
  * Publish one chunk of agent output.
@@ -1,4 +1,5 @@
1
1
  import type { AgentOutput, AgentRuntime, AgentTask } from "../types/runtime";
2
+ import type { SessionEvent, SessionIntent, SessionRecord } from "../types/session";
2
3
  /** What one agent did with a mid-turn message. */
3
4
  export interface SupplementReport {
4
5
  agentId: string;
@@ -11,20 +12,24 @@ export interface SupplementReport {
11
12
  }
12
13
  /** Owns reusable agent sessions across independent orchestration turns. */
13
14
  export declare class AgentSessionManager {
15
+ private listeners;
16
+ subscribe(listener: (event: SessionEvent) => void): () => void;
17
+ private publish;
14
18
  private entries;
15
19
  private resetting?;
16
20
  private closing?;
17
21
  private closed;
18
- execute(runtime: AgentRuntime, task: AgentTask): Promise<AgentOutput>;
19
- /**
20
- * Whether this manager still holds a session for `agentId`.
21
- *
22
- * A caller about to continue a past conversation uses this to avoid naming a
23
- * session it already has open: passing the id would change the entry's
24
- * fingerprint, and the manager would close a perfectly good session and
25
- * `session/load` it straight back.
26
- */
27
- has(agentId: string): boolean;
22
+ private compatible;
23
+ inspect(runtime: AgentRuntime, task: AgentTask, context?: {
24
+ session?: SessionRecord;
25
+ strict?: boolean;
26
+ }): SessionIntent;
27
+ execute(runtime: AgentRuntime, task: AgentTask, context?: {
28
+ session?: SessionRecord;
29
+ strict?: boolean;
30
+ executionId?: string;
31
+ attempt?: number;
32
+ }): Promise<AgentOutput>;
28
33
  /**
29
34
  * Deliver a message into every session that is running a turn right now.
30
35
  *
@@ -40,10 +45,13 @@ export declare class AgentSessionManager {
40
45
  * "steering failed".
41
46
  */
42
47
  supplement(text: string): Promise<SupplementReport[]>;
48
+ /** Close selected idle conversations without resetting the leader. */
49
+ resetAgents(agentIds: string[]): Promise<void>;
43
50
  /** Closes all sessions and permits a fresh conversation after cleanup completes. */
44
51
  reset(): Promise<void>;
45
52
  /** Terminal, idempotent cleanup barrier. No new executions may start. */
46
53
  close(): Promise<void>;
47
54
  private drain;
48
55
  private closeEntry;
56
+ private closeSession;
49
57
  }
@@ -1,21 +1,39 @@
1
1
  import { EventBus } from "../event/EventBus";
2
2
  import type { MultiClawConfig, OrchestrationPreview, OrchestratorResult } from "../types/config";
3
- import type { AgentSessionManager } from "./AgentSessionManager";
3
+ import { AgentSessionManager } from "./AgentSessionManager";
4
4
  export declare class Orchestrator {
5
5
  private config;
6
+ private sessions?;
6
7
  readonly eventBus: EventBus;
7
8
  private contextManager;
8
9
  private runner;
9
10
  private workDir;
10
11
  /** Which configured checkpoints have already been triggered this run. */
11
12
  private checkpointTriggered;
12
- constructor(config: MultiClawConfig, sessions?: AgentSessionManager);
13
+ private originalRequirement;
14
+ private work?;
15
+ private routing?;
16
+ private assignments;
17
+ private excluded;
18
+ private rerouting;
19
+ private repairSpent;
20
+ private leaderCosts;
21
+ private ownsSessions;
22
+ private cleanupAttempted;
23
+ private offSessions;
24
+ private sessionRecords;
25
+ private recovery;
26
+ private routeError?;
27
+ constructor(config: MultiClawConfig, sessions?: AgentSessionManager | undefined);
13
28
  preview(options?: {
14
29
  signal?: AbortSignal;
15
30
  }): Promise<OrchestrationPreview>;
31
+ private previewManaged;
16
32
  run(options?: {
17
33
  signal?: AbortSignal;
18
34
  }): Promise<OrchestratorResult>;
35
+ private runManaged;
36
+ private runPass;
19
37
  private reviewPolicyError;
20
38
  private reviewDelivery;
21
39
  private handleCheckpoint;
@@ -53,6 +71,8 @@ export declare class Orchestrator {
53
71
  * job, and that is what `message` records.
54
72
  */
55
73
  private resolveTurn;
74
+ private beginWork;
75
+ private captureWork;
56
76
  /**
57
77
  * The configured team, as copies.
58
78
  *
@@ -64,7 +64,7 @@ export interface AgentDefinition {
64
64
  retries?: number;
65
65
  workDir?: string;
66
66
  }
67
- export type AgentStatus = "pending" | "running" | "retrying" | "success" | "failed"
67
+ export type AgentStatus = "pending" | "running" | "retrying" | "handed_back" | "success" | "failed"
68
68
  /**
69
69
  * The run was stopped by its AbortSignal rather than finishing or erroring.
70
70
  * Distinct from `failed` so a Ctrl-C does not read as a crash, and so the
@@ -72,6 +72,7 @@ export type AgentStatus = "pending" | "running" | "retrying" | "success" | "fail
72
72
  */
73
73
  | "cancelled" | "skipped";
74
74
  export interface AgentResult {
75
+ handoffReason?: string;
75
76
  agentId: string;
76
77
  agentName: string;
77
78
  status: AgentStatus;
@@ -87,8 +88,8 @@ export interface AgentResult {
87
88
  usage?: AgentOutput["usage"];
88
89
  /** USD cost the runtime reported; absent when the provider does not price in USD. */
89
90
  costUsd?: number;
90
- /** Session the runtime ran under, when it reported one. */
91
- sessionId?: string;
91
+ /** Reliable Session facts, including on failed and cancelled executions. */
92
+ session?: import("./session").SessionRecord;
92
93
  }
93
94
  export interface SubTask {
94
95
  id: string;
@@ -1,21 +1,36 @@
1
1
  import type { AgentDefinition, PermissionMode } from "./agent";
2
2
  import type { LeaderMessage } from "./leader";
3
3
  import type { RuntimePermissionHandler, RuntimeQuestionHandler } from "./runtime";
4
- /**
5
- * One finished turn of a session, as dialogue the leader can read back.
6
- *
7
- * A requirement alone is not always interpretable: when the leader asked "which
8
- * database?" and the user answered "Postgres", turn two's requirement is the
9
- * bare word "Postgres". Paired with the turn that produced the question it is
10
- * obvious; on its own it is not.
11
- */
4
+ /** Caller-owned facts for the latest work in this console session. */
5
+ export interface WorkContext {
6
+ id: string;
7
+ originalRequirement: string;
8
+ requirement: string;
9
+ agents: Array<{
10
+ agentId: string;
11
+ scope?: string;
12
+ plan?: string;
13
+ session?: import("./session").SessionRecord;
14
+ dependsOn?: string[];
15
+ taskTitle?: string;
16
+ }>;
17
+ outcome?: TurnOutcome;
18
+ }
19
+ export interface RoutingInfo {
20
+ mode: "run" | "continue";
21
+ workId?: string;
22
+ reroutes: number;
23
+ error?: string;
24
+ }
25
+ /** One session turn, paired with its reply and observed execution facts. */
12
26
  export interface TurnRecord {
13
27
  /** What the user asked for. */
14
28
  requirement: string;
15
29
  /** How the turn ended in words, when it ended that way. */
16
30
  reply?: LeaderMessage;
17
- /** Observed execution and acceptance facts; absent in older caller-provided history. */
31
+ /** Observed execution and acceptance facts when agents ran. */
18
32
  outcome?: TurnOutcome;
33
+ work?: WorkContext;
19
34
  }
20
35
  /** Compact facts for the next leader turn, not a model-generated summary or retry instruction. */
21
36
  export interface TurnOutcome {
@@ -95,6 +110,8 @@ export interface MultiClawConfig {
95
110
  * unset and sees no conversation at all.
96
111
  */
97
112
  history?: TurnRecord[];
113
+ /** Latest work only; dialogue turns do not replace it. */
114
+ latestWork?: WorkContext;
98
115
  /**
99
116
  * Continue known agent sessions, and optionally re-run a fixed set of agents
100
117
  * instead of letting the leader route.
@@ -109,15 +126,14 @@ export interface MultiClawConfig {
109
126
  * whichever agents the route happens to pick. This is what makes a new
110
127
  * instruction land in a conversation the agents already remember.
111
128
  *
112
- * Like `history`, it is per-run state the caller carries: core has no session
113
- * of its own, and a config that never resumes leaves it unset and behaves
114
- * exactly as before.
129
+ * Session records are candidates, checked by core against work identity and
130
+ * immutable creation settings. Loading is confirmed only by runtime success.
115
131
  */
116
132
  resume?: {
117
133
  /** Agent ids to run, in the order they were originally selected. */
118
134
  agents?: string[];
119
- /** agentId → session id, for agents whose session is worth continuing. */
120
- sessions?: Record<string, string>;
135
+ /** agentId → observed Session facts used to check continuation or loading. */
136
+ sessions?: Record<string, import("./session").SessionRecord>;
121
137
  };
122
138
  logDir?: string;
123
139
  continueOnError?: boolean;
@@ -152,6 +168,9 @@ export interface MultiClawConfig {
152
168
  };
153
169
  }
154
170
  export interface OrchestrationPreview {
171
+ sessions?: import("./session").SessionIntent[];
172
+ originalRequirement?: string;
173
+ routing?: RoutingInfo;
155
174
  name: string;
156
175
  requirement?: string;
157
176
  totalAgents: number;
@@ -169,6 +188,11 @@ export interface DeliverableSummary {
169
188
  modifiedAt: string;
170
189
  }
171
190
  export interface OrchestratorResult {
191
+ originalRequirement?: string;
192
+ routing?: RoutingInfo;
193
+ work?: WorkContext;
194
+ /** Current route results; agentResults remains the full execution audit. */
195
+ effectiveAgentResults?: import("./agent").AgentResult[];
172
196
  name: string;
173
197
  /** With review enabled, true only when execution and delivery acceptance both succeed. */
174
198
  success: boolean;
@@ -11,6 +11,27 @@ export interface StageInfo {
11
11
  }>;
12
12
  }
13
13
  export type MultiClawEvent = {
14
+ type: "agent:session";
15
+ timestamp: string;
16
+ payload: import("./session").SessionEvent;
17
+ } | {
18
+ type: "orchestration:route";
19
+ timestamp: string;
20
+ payload: {
21
+ routing: import("./config").RoutingInfo;
22
+ work?: import("./config").WorkContext;
23
+ };
24
+ } | {
25
+ type: "orchestration:routing-error";
26
+ timestamp: string;
27
+ payload: {
28
+ message: string;
29
+ };
30
+ } | {
31
+ type: "agent:handed-back";
32
+ timestamp: string;
33
+ payload: AgentResult;
34
+ } | {
14
35
  type: "orchestration:start";
15
36
  timestamp: string;
16
37
  payload: {
@@ -6,9 +6,14 @@
6
6
  * is the familiar case; the other two exist so that a greeting or a question is
7
7
  * answered instead of spinning up every agent in the config.
8
8
  */
9
- export type LeaderMode = "run" | "reply" | "ask";
9
+ export type LeaderMode = "run" | "continue" | "reply" | "ask";
10
10
  export type LeaderDecision = {
11
- mode: "run";
11
+ mode: "run" | "continue";
12
+ workId?: string;
13
+ tasks?: Array<{
14
+ agentId: string;
15
+ scope: string;
16
+ }>;
12
17
  run: string[];
13
18
  /** Complete task combining the original goal with clarification constraints. */
14
19
  requirement?: string;
@@ -133,7 +133,10 @@ export type RuntimeQuestionResponse = {
133
133
  /** Structured form input only; the signal aborts when the requesting turn ends. */
134
134
  export type RuntimeQuestionHandler = (request: RuntimeQuestionRequest, signal: AbortSignal) => Promise<RuntimeQuestionResponse>;
135
135
  export interface AgentTask {
136
+ /** Host work identity, independent of provider session identity. */
137
+ workId?: string;
136
138
  agentId: string;
139
+ provider?: string;
137
140
  systemPrompt: string;
138
141
  prompt: string;
139
142
  workDir: string;
@@ -228,6 +231,7 @@ export interface RuntimeSession {
228
231
  */
229
232
  readonly capabilities: {
230
233
  continuation: boolean;
234
+ load: boolean;
231
235
  supplement: boolean;
232
236
  };
233
237
  /** Rejects overlapping turns; it does not implicitly queue or inject input. */
@@ -0,0 +1,36 @@
1
+ /** Observed provider conversation facts, independent of an execution's outcome. */
2
+ export interface SessionRecord {
3
+ bindingId: string;
4
+ agentId: string;
5
+ workId?: string;
6
+ sessionId?: string;
7
+ runtime: string;
8
+ compatibilityKey: string;
9
+ state: "opening" | "idle" | "running" | "cancelling" | "closing" | "closed" | "unknown";
10
+ capabilities: {
11
+ continuation: boolean;
12
+ load: boolean;
13
+ supplement: boolean;
14
+ };
15
+ action: "created" | "loaded" | "reused";
16
+ observedAt: string;
17
+ reason?: string;
18
+ failure?: {
19
+ code: "incompatible" | "unsupported" | "opening_failed" | "load_failed" | "turn_failed" | "cleanup_failed";
20
+ message: string;
21
+ };
22
+ }
23
+ export interface SessionEvent {
24
+ action: "opening" | "created" | "load_requested" | "loaded" | "reused" | "state_changed" | "opening_failed" | "load_failed" | "closing" | "closed" | "cleanup_failed";
25
+ executionId: string;
26
+ attempt: number;
27
+ record: SessionRecord;
28
+ requestedSessionId?: string;
29
+ reason?: string;
30
+ }
31
+ /** Read-only intent; loading and cleanup have not happened. */
32
+ export interface SessionIntent {
33
+ agentId: string;
34
+ action: "create" | "reuse" | "load" | "unavailable";
35
+ reason: string;
36
+ }
@@ -0,0 +1,7 @@
1
+ /** A terminal host control message, never inferred from ordinary prose. */
2
+ export declare const HANDOFF_INSTRUCTION: string;
3
+ export declare function parseHandoff(output: string): {
4
+ reason: string;
5
+ } | {
6
+ error: string;
7
+ } | undefined;
@@ -17,8 +17,8 @@
17
17
  export interface ResumePlan {
18
18
  /** Ids that will run, in the order the caller named them. */
19
19
  agents: readonly string[];
20
- /** Agent id → the session id to continue, for the agents that reported one. */
21
- sessions?: Readonly<Record<string, string>>;
20
+ /** Agent ID → observed Session metadata to check before resuming. */
21
+ sessions?: Readonly<Record<string, import("../types/session").SessionRecord>>;
22
22
  /** The team the ids were chosen from; supplies each agent's dependencies. */
23
23
  team: readonly {
24
24
  id: string;
@@ -26,7 +26,7 @@ export interface ResumePlan {
26
26
  }[];
27
27
  }
28
28
  /**
29
- * One clause per agent — `dev starts fresh`, `reviewer continues its session` —
29
+ * One clause per agent — `dev starts fresh`, `reviewer will attempt to resume its session` —
30
30
  * followed by one clause per agent whose dependencies were pruned.
31
31
  *
32
32
  * Deliberately a description, not a plan: what actually runs stays core's
@@ -0,0 +1,7 @@
1
+ import type { AgentRuntime, AgentTask } from "../types/runtime";
2
+ import type { SessionEvent, SessionRecord } from "../types/session";
3
+ /** Only immutable creation options participate; target IDs and work identity are checked separately. */
4
+ export declare function sessionCompatibilityKey(runtime: Pick<AgentRuntime, "name">, task: AgentTask): string;
5
+ export declare function isSessionRecord(value: unknown): value is SessionRecord;
6
+ export declare function isSessionEvent(value: unknown): value is SessionEvent;
7
+ export declare function describeSession(record: SessionRecord): string;
@@ -0,0 +1,3 @@
1
+ import type { WorkContext } from "../types/config";
2
+ /** Validate persisted ownership and execution facts consumed by routing. */
3
+ export declare function isWorkContext(value: unknown): value is WorkContext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiclawcli/core",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",