@multiclawcli/core 0.12.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
  *
@@ -33,8 +33,6 @@ export interface ProviderSpec {
33
33
  * then falls back to a prompt prefix.
34
34
  */
35
35
  meta?: (task: AgentTask) => Record<string, unknown> | undefined;
36
- /** Whether this provider honours a tool allowlist at all. */
37
- tools: boolean;
38
36
  /** How model selection is communicated to the agent. */
39
37
  modelSelection: ModelSelectionMode;
40
38
  /** Models offered by this provider. */
@@ -1,5 +1,11 @@
1
1
  import type { AgentOutput } from "./runtime";
2
- export type ToolName = "Read" | "Write" | "Edit" | "MultiEdit" | "Bash" | "Glob" | "Grep" | "WebFetch";
2
+ /**
3
+ * How a runtime answers a provider's tool-permission request.
4
+ *
5
+ * `auto` approves every request, `ask` hands it to a human (and refuses when
6
+ * nobody is listening), `deny` refuses every request.
7
+ */
8
+ export type PermissionMode = "auto" | "ask" | "deny";
3
9
  export interface AgentDefinition {
4
10
  id: string;
5
11
  name: string;
@@ -50,13 +56,15 @@ export interface AgentDefinition {
50
56
  taskTitle?: string;
51
57
  agentScope?: string;
52
58
  agentPlan?: string;
53
- tools?: ToolName[];
59
+ permissions?: {
60
+ mode: PermissionMode;
61
+ };
54
62
  dependsOn?: string[];
55
63
  timeout?: number;
56
64
  retries?: number;
57
65
  workDir?: string;
58
66
  }
59
- export type AgentStatus = "pending" | "running" | "retrying" | "success" | "failed"
67
+ export type AgentStatus = "pending" | "running" | "retrying" | "handed_back" | "success" | "failed"
60
68
  /**
61
69
  * The run was stopped by its AbortSignal rather than finishing or erroring.
62
70
  * Distinct from `failed` so a Ctrl-C does not read as a crash, and so the
@@ -64,6 +72,7 @@ export type AgentStatus = "pending" | "running" | "retrying" | "success" | "fail
64
72
  */
65
73
  | "cancelled" | "skipped";
66
74
  export interface AgentResult {
75
+ handoffReason?: string;
67
76
  agentId: string;
68
77
  agentName: string;
69
78
  status: AgentStatus;
@@ -79,8 +88,8 @@ export interface AgentResult {
79
88
  usage?: AgentOutput["usage"];
80
89
  /** USD cost the runtime reported; absent when the provider does not price in USD. */
81
90
  costUsd?: number;
82
- /** Session the runtime ran under, when it reported one. */
83
- sessionId?: string;
91
+ /** Reliable Session facts, including on failed and cancelled executions. */
92
+ session?: import("./session").SessionRecord;
84
93
  }
85
94
  export interface SubTask {
86
95
  id: string;
@@ -1,21 +1,36 @@
1
- import type { AgentDefinition, ToolName } from "./agent";
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;
@@ -146,10 +162,15 @@ export interface MultiClawConfig {
146
162
  provider?: string;
147
163
  model?: string;
148
164
  command?: string;
149
- tools?: ToolName[];
165
+ permissions?: {
166
+ mode: PermissionMode;
167
+ };
150
168
  };
151
169
  }
152
170
  export interface OrchestrationPreview {
171
+ sessions?: import("./session").SessionIntent[];
172
+ originalRequirement?: string;
173
+ routing?: RoutingInfo;
153
174
  name: string;
154
175
  requirement?: string;
155
176
  totalAgents: number;
@@ -167,6 +188,11 @@ export interface DeliverableSummary {
167
188
  modifiedAt: string;
168
189
  }
169
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[];
170
196
  name: string;
171
197
  /** With review enabled, true only when execution and delivery acceptance both succeed. */
172
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;
@@ -82,7 +82,6 @@ export interface RuntimeCapabilities {
82
82
  sessions: boolean;
83
83
  resume: boolean;
84
84
  permissions: boolean;
85
- tools: boolean;
86
85
  models: boolean;
87
86
  images: boolean;
88
87
  usage: boolean;
@@ -109,14 +108,14 @@ export interface RuntimePermissionRequest extends RuntimeEventBase {
109
108
  toolName?: string;
110
109
  kind?: string;
111
110
  input?: unknown;
112
- /** Only choices permitted by the configured tools policy are offered. */
111
+ /** The provider's offered choices, in the order it presented them. */
113
112
  options: Array<{
114
113
  optionId: string;
115
114
  name: string;
116
115
  kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";
117
116
  }>;
118
117
  }
119
- /** Return an offered option id, or undefined to cancel/deny. Never grant outside the tools policy. */
118
+ /** Return an offered option id, or undefined to cancel/deny. */
120
119
  export type RuntimePermissionHandler = (request: RuntimePermissionRequest, signal: AbortSignal) => Promise<string | undefined>;
121
120
  export interface RuntimeQuestionRequest extends RuntimeEventBase {
122
121
  requestId: string;
@@ -134,15 +133,17 @@ export type RuntimeQuestionResponse = {
134
133
  /** Structured form input only; the signal aborts when the requesting turn ends. */
135
134
  export type RuntimeQuestionHandler = (request: RuntimeQuestionRequest, signal: AbortSignal) => Promise<RuntimeQuestionResponse>;
136
135
  export interface AgentTask {
136
+ /** Host work identity, independent of provider session identity. */
137
+ workId?: string;
137
138
  agentId: string;
139
+ provider?: string;
138
140
  systemPrompt: string;
139
141
  prompt: string;
140
- tools: string[];
141
142
  workDir: string;
142
143
  timeout: number;
143
144
  signal?: AbortSignal;
144
145
  onEvent?: (event: RuntimeEvent) => void;
145
- /** Automatic tools policy by default; ask without a handler denies. */
146
+ /** Approval mode for tool calls; the runtime defaults to `"auto"` when unset. */
146
147
  permissionMode?: "auto" | "ask" | "deny";
147
148
  onPermission?: RuntimePermissionHandler;
148
149
  onQuestion?: RuntimeQuestionHandler;
@@ -230,6 +231,7 @@ export interface RuntimeSession {
230
231
  */
231
232
  readonly capabilities: {
232
233
  continuation: boolean;
234
+ load: boolean;
233
235
  supplement: boolean;
234
236
  };
235
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.12.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,10 +0,0 @@
1
- import type { AgentDefinition } from "../types/agent";
2
- import type { MultiClawConfig } from "../types/config";
3
- /** Record factory defaults without altering the tools array passed to the runtime. */
4
- export declare function markPresetTools(agent: AgentDefinition, overrides?: Partial<AgentDefinition>): AgentDefinition;
5
- /** Spreads retain provenance; equal replacement arrays and in-place edits are explicit. */
6
- export declare function hasExplicitTools(agent: AgentDefinition): boolean;
7
- /** Validate the entire configured team before routing, execution, or workspace cleanup. */
8
- export declare function validateToolsPolicy(config: MultiClawConfig, options?: {
9
- signal?: AbortSignal;
10
- }): Promise<void>;