@timurproko/a1 0.1.1-dev.1 → 0.1.1-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,6 +44,19 @@ npm start
44
44
 
45
45
  A1 control state uses `%APPDATA%\\A1` and `%LOCALAPPDATA%\\A1` on Windows, and the `a1` directory under XDG config/data/runtime roots on Unix. Override it only with declared `A1_*` variables such as `A1_CONFIG_DIR`, `A1_DATA_DIR`, `A1_RUNTIME_DIR`, `A1_DATABASE_PATH`, and `A1_ENDPOINT`. Pi profile roots remain `~/.a1/agent`, `~/.pi/agent`, and `~/.a1/sandbox`. This is a no-migration identity hard cut; see [`docs/architecture/toolchain.md`](docs/architecture/toolchain.md#identity-hard-cut-and-cleanup) before removing obsolete control state.
46
46
 
47
+ ### Branch lifecycle
48
+
49
+ Create every topic branch and every `milestone/<name>` work branch from `develop`; never implement directly on `develop` or `master`. A source branch is closed only after this sequence completes:
50
+
51
+ 1. Validate the source branch with its required gates.
52
+ 2. Merge it into `develop` and push the merged `develop` commit.
53
+ 3. Switch to `develop`.
54
+ 4. Preview the exact cleanup set with `npm run branches:prune -- --branch <source>`.
55
+ 5. Safely delete the reviewed local source with `npm run branches:prune -- --apply --branch <source>`.
56
+ 6. When the non-protected remote source exists, delete it in the same cleanup with `npm run branches:prune -- --apply --branch <source> --remote origin`.
57
+
58
+ The command defaults to a dry run against `develop`. It always retains `develop`, `master`, the checked-out branch, explicitly protected branches, and branches whose tips are not ancestors of the integration target. Apply mode uses only Git safe deletion (`git branch -d`), never force deletion. Use `--base <branch>` for another explicit integration target, `--protect <branch>` for additional protection, and `--json` for machine-readable review.
59
+
47
60
  Run the non-desktop gates with:
48
61
 
49
62
  ```sh
package/bin/a1-ui.js CHANGED
@@ -4,11 +4,11 @@ const { runSelectedInteractiveRuntime } = await import("../dist/src/features/lau
4
4
 
5
5
  runSelectedInteractiveRuntime(process.env.A1_LAUNCH_PROFILE ?? "a1", {
6
6
  ownedUi: async () => {
7
- const [{ runOwnedUi }, { composeOwnedUiApplication }] = await Promise.all([
7
+ const [{ runOwnedUi }, { composeStructuredWorkspaceApplication }] = await Promise.all([
8
8
  import("../dist/src/features/owned-ui/index.js"),
9
9
  import("../dist/src/composition/index.js"),
10
10
  ]);
11
- const application = await composeOwnedUiApplication({ cwd: process.cwd() });
11
+ const application = await composeStructuredWorkspaceApplication({ cwd: process.cwd() });
12
12
  return await runOwnedUi({ application });
13
13
  },
14
14
  transparent: async profileId => {
@@ -1,6 +1,9 @@
1
1
  import { type AgentEnginePort } from "../foundation/agent-engine-contracts/index.js";
2
2
  import { type PiEngineAdapter } from "../foundation/pi-engine-adapter/index.js";
3
3
  import { type OwnedUiApplicationPort, type PresentationComponentPort, type PresentationRuntimePort, type PresentationTerminalPort } from "../foundation/presentation-contracts/index.js";
4
+ import { StructuredWorkspaceTabs, type StructuredWorkspaceLimits } from "../features/workspace/index.js";
5
+ import { StructuredWorkspaceApplication } from "./structured-workspace-application.js";
6
+ export * from "./structured-workspace-application.js";
4
7
  export interface ProcessCompositionOptions {
5
8
  readonly cwd?: string;
6
9
  readonly sessionId?: string;
@@ -14,6 +17,21 @@ export interface OwnedUiCompositionOptions {
14
17
  readonly createPiAdapter?: () => Promise<PiEngineAdapter>;
15
18
  }
16
19
  export declare function composeOwnedUiApplication(options?: OwnedUiCompositionOptions): Promise<OwnedUiApplicationPort>;
20
+ export interface StructuredWorkspaceCompositionOptions {
21
+ readonly cwd?: string;
22
+ readonly workspaceId?: string;
23
+ readonly limits?: Partial<StructuredWorkspaceLimits>;
24
+ readonly createEngine?: (agentId: string) => Promise<AgentEnginePort>;
25
+ readonly createPiAdapter?: (agentId: string) => Promise<PiEngineAdapter>;
26
+ }
27
+ export declare function composeStructuredWorkspace(options?: StructuredWorkspaceCompositionOptions): StructuredWorkspaceTabs;
28
+ export interface StructuredWorkspaceApplicationCompositionOptions extends StructuredWorkspaceCompositionOptions {
29
+ readonly terminal?: PresentationTerminalPort;
30
+ readonly mode?: "regular" | "fullscreen";
31
+ readonly initialAgentId?: string;
32
+ readonly initialAgentName?: string;
33
+ }
34
+ export declare function composeStructuredWorkspaceApplication(options?: StructuredWorkspaceApplicationCompositionOptions): Promise<StructuredWorkspaceApplication>;
17
35
  export interface ProcessComposition {
18
36
  readonly engine: AgentEnginePort;
19
37
  createPresentation(root: PresentationComponentPort, terminal: PresentationTerminalPort): PresentationRuntimePort;
@@ -3,6 +3,9 @@ import { createPiEngineAdapter } from "../foundation/pi-engine-adapter/index.js"
3
3
  import { createPiPresentationRuntime, createPiTerminalBridge } from "../foundation/pi-tui-runtime-adapter/index.js";
4
4
  import { assertPresentationComponent, assertPresentationRuntime, } from "../foundation/presentation-contracts/index.js";
5
5
  import { OwnedUiSessionShell } from "../foundation/pi-owned-ui-integration/index.js";
6
+ import { StructuredWorkspaceTabs, } from "../features/workspace/index.js";
7
+ import { StructuredWorkspaceApplication } from "./structured-workspace-application.js";
8
+ export * from "./structured-workspace-application.js";
6
9
  const CAPABILITIES = {
7
10
  contractVersion: AGENT_ENGINE_CONTRACT_VERSION,
8
11
  commands: ["prompt", "steer", "follow-up", "abort", "retry", "compact", "bash", "replace-session"],
@@ -25,6 +28,39 @@ export async function composeOwnedUiApplication(options = {}) {
25
28
  dispose: () => shell.dispose(),
26
29
  };
27
30
  }
31
+ export function composeStructuredWorkspace(options = {}) {
32
+ const cwd = options.cwd ?? process.cwd();
33
+ const createEngine = options.createEngine ?? (async (agentId) => {
34
+ const sessionId = `${agentId}.session`;
35
+ const adapter = options.createPiAdapter
36
+ ? await options.createPiAdapter(agentId)
37
+ : await createPiEngineAdapter({ cwd, sessionId });
38
+ return new PiAgentEngineBridge(adapter, cwd);
39
+ });
40
+ return new StructuredWorkspaceTabs({
41
+ cwd,
42
+ createEngine,
43
+ ...(options.workspaceId === undefined ? {} : { workspaceId: options.workspaceId }),
44
+ ...(options.limits === undefined ? {} : { limits: options.limits }),
45
+ });
46
+ }
47
+ export async function composeStructuredWorkspaceApplication(options = {}) {
48
+ const workspace = composeStructuredWorkspace(options);
49
+ const created = await workspace.createAgent({
50
+ id: options.initialAgentId ?? "agent-1",
51
+ displayName: options.initialAgentName ?? "Agent 1",
52
+ });
53
+ if (created.kind === "rejected") {
54
+ await workspace.dispose();
55
+ throw new Error(`could not create the initial structured workspace agent: ${created.diagnostic}`);
56
+ }
57
+ return new StructuredWorkspaceApplication({
58
+ workspace,
59
+ cwd: options.cwd ?? process.cwd(),
60
+ ...(options.terminal === undefined ? {} : { terminal: options.terminal }),
61
+ ...(options.mode === undefined ? {} : { mode: options.mode }),
62
+ });
63
+ }
28
64
  export async function composeProcess(options = {}) {
29
65
  let engine = options.engine;
30
66
  if (!engine) {
@@ -0,0 +1,38 @@
1
+ import { OwnedUiSessionShellRoot } from "../foundation/pi-owned-ui-integration/index.js";
2
+ import { PiTuiRuntimeAdapter, type PiTuiComponentPort } from "../foundation/pi-tui-runtime-adapter/index.js";
3
+ import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../foundation/presentation-contracts/index.js";
4
+ import { type StructuredWorkspaceTabs } from "../features/workspace/index.js";
5
+ export interface StructuredWorkspaceApplicationOptions {
6
+ readonly workspace: StructuredWorkspaceTabs;
7
+ readonly cwd: string;
8
+ readonly terminal?: PresentationTerminalPort;
9
+ readonly mode?: "regular" | "fullscreen";
10
+ }
11
+ export declare class StructuredWorkspaceApplication implements OwnedUiApplicationPort {
12
+ #private;
13
+ readonly workspace: StructuredWorkspaceTabs;
14
+ readonly root: OwnedUiSessionShellRoot;
15
+ readonly runtime: PiTuiRuntimeAdapter;
16
+ readonly component: StructuredWorkspaceRootComponent;
17
+ constructor(options: StructuredWorkspaceApplicationOptions);
18
+ get disposed(): boolean;
19
+ start(): void;
20
+ flush(): Promise<void>;
21
+ waitUntilStopped(): Promise<void>;
22
+ submit(raw: string): Promise<void>;
23
+ switchRelative(direction: -1 | 1): Promise<void>;
24
+ dispose(): Promise<void>;
25
+ }
26
+ export declare class StructuredWorkspaceRootComponent implements PiTuiComponentPort {
27
+ readonly workspace: StructuredWorkspaceTabs;
28
+ readonly sessionRoot: OwnedUiSessionShellRoot;
29
+ readonly onSwitch: (direction: -1 | 1) => void;
30
+ readonly onEditorChange: (text: string) => void;
31
+ readonly notice: () => string | null;
32
+ constructor(workspace: StructuredWorkspaceTabs, sessionRoot: OwnedUiSessionShellRoot, onSwitch: (direction: -1 | 1) => void, onEditorChange: (text: string) => void, notice: () => string | null);
33
+ render(width: number): readonly string[];
34
+ handleInput(data: string): void;
35
+ invalidate(): void;
36
+ setFocused(focused: boolean): void;
37
+ dispose(): void;
38
+ }
@@ -0,0 +1,261 @@
1
+ import { OWNED_UI_CONTRACT_VERSION, } from "../foundation/owned-ui-contracts/index.js";
2
+ import { OwnedUiSessionShellRoot } from "../foundation/pi-owned-ui-integration/index.js";
3
+ import { createPiTerminalBridge, PiTuiRuntimeAdapter, } from "../foundation/pi-tui-runtime-adapter/index.js";
4
+ import {} from "../features/workspace/index.js";
5
+ export class StructuredWorkspaceApplication {
6
+ workspace;
7
+ root;
8
+ runtime;
9
+ component;
10
+ #unsubscribe;
11
+ #resolveStopped;
12
+ #stopped;
13
+ #notice = null;
14
+ #disposed = false;
15
+ #started = false;
16
+ #agentSequence;
17
+ constructor(options) {
18
+ this.workspace = options.workspace;
19
+ const initial = selectedPanel(options.workspace.view());
20
+ if (!initial)
21
+ throw new TypeError("structured workspace application requires an initial agent tab");
22
+ this.#agentSequence = options.workspace.view().panels.length;
23
+ this.#stopped = new Promise(resolve => { this.#resolveStopped = resolve; });
24
+ this.root = new OwnedUiSessionShellRoot(toOwnedView(options.workspace.view(), initial, null, 80, 24), options.cwd, {
25
+ getColumns: () => this.runtime?.viewport().columns ?? options.terminal?.columns ?? 80,
26
+ getRows: () => this.runtime?.viewport().rows ?? options.terminal?.rows ?? 24,
27
+ requestRender: () => this.runtime?.requestRender(),
28
+ onSubmit: text => { void this.submit(text); },
29
+ onInterrupt: () => { this.#notice = "Use /agent stop to stop the selected agent."; this.runtime?.requestRender(); },
30
+ onClear: () => { this.root.editor.setText(""); this.runtime?.requestRender(); },
31
+ onExit: () => { void this.dispose(); },
32
+ onModelSelect: () => { this.#notice = "Model selection remains scoped to each structured agent session."; this.runtime?.requestRender(); },
33
+ onThinkingCycle: () => { this.#notice = "Thinking settings remain scoped to each structured agent session."; this.runtime?.requestRender(); },
34
+ });
35
+ this.component = new StructuredWorkspaceRootComponent(this.workspace, this.root, direction => { void this.switchRelative(direction); }, text => this.#saveEditor(text), () => this.#notice);
36
+ this.runtime = new PiTuiRuntimeAdapter({
37
+ root: this.component,
38
+ mode: options.mode ?? "regular",
39
+ ...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
40
+ hardwareCursor: true,
41
+ mouse: false,
42
+ });
43
+ this.#unsubscribe = this.workspace.subscribe(view => this.#sync(view));
44
+ }
45
+ get disposed() { return this.#disposed; }
46
+ start() {
47
+ if (this.#started || this.#disposed)
48
+ return;
49
+ this.#started = true;
50
+ this.runtime.start();
51
+ this.#sync(this.workspace.view());
52
+ }
53
+ async flush() {
54
+ await this.workspace.flush();
55
+ this.#sync(this.workspace.view());
56
+ }
57
+ waitUntilStopped() { return this.#stopped; }
58
+ async submit(raw) {
59
+ const input = raw.trim();
60
+ if (!input)
61
+ return;
62
+ if (input === "/agent" || input.startsWith("/agent ")) {
63
+ await this.#agentCommand(input.slice("/agent".length).trim());
64
+ return;
65
+ }
66
+ const selected = selectedPanel(this.workspace.view());
67
+ if (!selected)
68
+ return;
69
+ this.workspace.setEditorText(selected.agentId, input);
70
+ const result = await this.workspace.sendPrompt(selected.agentId, input);
71
+ this.#notice = result.kind === "rejected" ? result.diagnostic : null;
72
+ this.#sync(this.workspace.view());
73
+ }
74
+ async switchRelative(direction) {
75
+ const view = this.workspace.view();
76
+ if (view.panels.length < 2)
77
+ return;
78
+ this.#saveEditor(this.root.editor.getText());
79
+ const current = Math.max(0, view.panels.findIndex(panel => panel.selected));
80
+ const next = (current + direction + view.panels.length) % view.panels.length;
81
+ const target = view.panels[next];
82
+ if (target)
83
+ await this.workspace.selectAgent(target.agentId);
84
+ }
85
+ async dispose() {
86
+ if (this.#disposed)
87
+ return;
88
+ this.#disposed = true;
89
+ this.#unsubscribe();
90
+ const failures = [];
91
+ await this.runtime.stop().catch(error => failures.push(error));
92
+ await this.workspace.dispose().catch(error => failures.push(error));
93
+ this.#resolveStopped?.();
94
+ if (failures.length > 0)
95
+ throw new AggregateError(failures, "structured workspace application disposal failed");
96
+ }
97
+ async #agentCommand(argument) {
98
+ const [command = "list", ...rest] = argument.split(/\s+/).filter(Boolean);
99
+ const view = this.workspace.view();
100
+ const selected = selectedPanel(view);
101
+ if (command === "new") {
102
+ this.#agentSequence += 1;
103
+ const id = `agent-${this.#agentSequence}`;
104
+ const displayName = rest.join(" ") || `Agent ${this.#agentSequence}`;
105
+ const created = await this.workspace.createAgent({ id, displayName });
106
+ if (created.kind === "applied")
107
+ await this.workspace.selectAgent(id);
108
+ this.#notice = created.kind === "rejected" ? created.diagnostic : `Created ${created.value.agentId}.`;
109
+ }
110
+ else if (command === "next" || command === "previous" || command === "prev") {
111
+ await this.switchRelative(command === "next" ? 1 : -1);
112
+ this.#notice = null;
113
+ }
114
+ else if (command === "select") {
115
+ const target = rest[0];
116
+ const selectedResult = target ? await this.workspace.selectAgent(target) : null;
117
+ this.#notice = selectedResult === null ? "Usage: /agent select <id>" : selectedResult.kind === "rejected" ? selectedResult.diagnostic : null;
118
+ }
119
+ else if (command === "stop") {
120
+ const result = selected ? await this.workspace.stopAgent(selected.agentId) : null;
121
+ this.#notice = result === null ? "No agent selected." : result.kind === "rejected" ? result.diagnostic : `Stopped ${selected.agentId}.`;
122
+ }
123
+ else if (command === "restart") {
124
+ const result = selected ? await this.workspace.restartAgent(selected.agentId) : null;
125
+ this.#notice = result === null ? "No agent selected." : result.kind === "rejected" ? result.diagnostic : `Restarted ${selected.agentId}.`;
126
+ }
127
+ else if (command === "remove") {
128
+ if (!selected)
129
+ this.#notice = "No agent selected.";
130
+ else {
131
+ if (selected.lifecycle !== "stopped" && selected.lifecycle !== "failed")
132
+ await this.workspace.stopAgent(selected.agentId);
133
+ const result = await this.workspace.removeAgent(selected.agentId);
134
+ this.#notice = result.kind === "rejected" ? result.diagnostic : `Removed ${selected.agentId}.`;
135
+ }
136
+ }
137
+ else if (command === "list") {
138
+ this.#notice = view.tabs.length === 0 ? "No managed agents." : view.tabs.map(tab => `${tab.selected ? "*" : " "}${tab.agentId}:${tab.label}`).join(" ");
139
+ }
140
+ else {
141
+ this.#notice = "Usage: /agent [list|new [name]|next|prev|select <id>|stop|restart|remove]";
142
+ }
143
+ this.#sync(this.workspace.view());
144
+ }
145
+ #saveEditor(text) {
146
+ const selected = selectedPanel(this.workspace.view());
147
+ if (selected)
148
+ this.workspace.setEditorText(selected.agentId, text);
149
+ }
150
+ #sync(view) {
151
+ const selected = selectedPanel(view);
152
+ if (!selected)
153
+ return;
154
+ const viewport = this.runtime?.viewport() ?? { columns: 80, rows: 24 };
155
+ this.root.update(toOwnedView(view, selected, this.#notice, viewport.columns, viewport.rows));
156
+ if (this.root.editor.getText() !== selected.editorText)
157
+ this.root.editor.setText(selected.editorText);
158
+ this.component.invalidate();
159
+ this.runtime?.requestRender();
160
+ }
161
+ }
162
+ export class StructuredWorkspaceRootComponent {
163
+ workspace;
164
+ sessionRoot;
165
+ onSwitch;
166
+ onEditorChange;
167
+ notice;
168
+ constructor(workspace, sessionRoot, onSwitch, onEditorChange, notice) {
169
+ this.workspace = workspace;
170
+ this.sessionRoot = sessionRoot;
171
+ this.onSwitch = onSwitch;
172
+ this.onEditorChange = onEditorChange;
173
+ this.notice = notice;
174
+ }
175
+ render(width) {
176
+ const view = this.workspace.view();
177
+ const tabLine = truncate(view.tabs.map(tab => `${tab.selected ? "[" : " "}${tab.label}${tab.selected ? "]" : " "}`).join(" "), width);
178
+ const notice = this.notice();
179
+ return [tabLine, ...(notice ? [truncate(notice, width)] : []), ...this.sessionRoot.render(width)];
180
+ }
181
+ handleInput(data) {
182
+ if (data === "\x1b[6;5~" || data === "\x1b[1;3C") {
183
+ this.onEditorChange(this.sessionRoot.editor.getText());
184
+ this.onSwitch(1);
185
+ return;
186
+ }
187
+ if (data === "\x1b[5;5~" || data === "\x1b[1;3D") {
188
+ this.onEditorChange(this.sessionRoot.editor.getText());
189
+ this.onSwitch(-1);
190
+ return;
191
+ }
192
+ this.sessionRoot.editor.handleInput?.(data);
193
+ }
194
+ invalidate() { this.sessionRoot.invalidate(); }
195
+ setFocused(focused) { this.sessionRoot.editor.setFocused?.(focused); }
196
+ dispose() { this.sessionRoot.dispose?.(); }
197
+ }
198
+ function selectedPanel(view) {
199
+ return view.selectedPanel ?? view.panels[0] ?? null;
200
+ }
201
+ function toOwnedView(workspace, panel, notice, columns, rows) {
202
+ const agent = workspace.workspace.agents.find(candidate => candidate.id === panel.agentId);
203
+ return {
204
+ contractVersion: OWNED_UI_CONTRACT_VERSION,
205
+ sessionId: panel.sessionId,
206
+ revision: workspace.workspace.revision + panel.lastSequence,
207
+ lifecycle: ownedLifecycle(panel.lifecycle),
208
+ transcript: panel.transcript.flatMap(messageBlocks),
209
+ editor: {
210
+ text: panel.editorText,
211
+ queuedSubmissions: [],
212
+ selection: null,
213
+ cursorOffset: panel.editorText.length,
214
+ historyRevision: panel.lastSequence,
215
+ submitEnabled: panel.lifecycle !== "stopping" && panel.lifecycle !== "stopped" && panel.lifecycle !== "failed",
216
+ },
217
+ status: {
218
+ title: agent?.displayName ?? panel.agentId,
219
+ workingMessage: panel.lifecycle === "busy" ? "Working" : null,
220
+ diagnostics: panel.failure ? [panel.failure] : notice ? [notice] : [],
221
+ badges: [
222
+ `${workspace.panels.length} agents`,
223
+ ...(agent?.unreadActivity ? [`${agent.unreadActivity} unread`] : []),
224
+ ...(agent?.attention ? ["attention"] : []),
225
+ ],
226
+ footer: { branch: null, sessionName: agent?.displayName ?? null, availableProviderCount: 0, extensionStatuses: [] },
227
+ },
228
+ terminal: { columns, rows, focusedRegion: "editor", hardwareCursor: true },
229
+ activeModel: null,
230
+ thinkingLevel: "medium",
231
+ activeCommandIds: panel.activeCommandIds,
232
+ dialog: null,
233
+ overlay: null,
234
+ customizations: [],
235
+ diagnostics: panel.failure ? [{ sequence: panel.lastSequence, code: "structured-agent", severity: "error", message: panel.failure, recoverable: true }] : [],
236
+ };
237
+ }
238
+ function messageBlocks(message) {
239
+ return message.content.map((content, index) => {
240
+ const base = { id: `${message.id}.${index}`, status: message.status === "streaming" ? "live" : "finalized", revision: index + 1, title: null };
241
+ if (content.kind === "thinking")
242
+ return { ...base, kind: "thinking", text: content.text, payload: {} };
243
+ if (content.kind === "tool-call")
244
+ return { ...base, kind: "tool-call", title: content.toolName, text: JSON.stringify(content.input), payload: content };
245
+ if (content.kind === "tool-result")
246
+ return { ...base, kind: "tool-result", text: JSON.stringify(content.output), payload: content };
247
+ if (content.kind === "image")
248
+ return { ...base, kind: "custom", title: content.mediaType, text: "[image]", payload: content };
249
+ if (content.kind === "unknown")
250
+ return { ...base, kind: "custom", title: content.sourceType, text: JSON.stringify(content.payload), payload: content };
251
+ return { ...base, kind: message.role === "user" ? "user" : message.role === "system" ? "system" : message.role === "tool" ? "tool-result" : "assistant", text: content.text, payload: {} };
252
+ });
253
+ }
254
+ function ownedLifecycle(lifecycle) {
255
+ return lifecycle;
256
+ }
257
+ function truncate(value, width) {
258
+ if (width <= 0)
259
+ return "";
260
+ return value.length <= width ? value : width === 1 ? "…" : `${value.slice(0, width - 1)}…`;
261
+ }
@@ -4,3 +4,4 @@ export * from "./reconciliation.js";
4
4
  export * from "./reducer.js";
5
5
  export * from "./router.js";
6
6
  export * from "./store.js";
7
+ export * from "./structured-tabs.js";
@@ -4,3 +4,4 @@ export * from "./reconciliation.js";
4
4
  export * from "./reducer.js";
5
5
  export * from "./router.js";
6
6
  export * from "./store.js";
7
+ export * from "./structured-tabs.js";
@@ -22,10 +22,15 @@ export declare class WorkspaceRouter {
22
22
  constructor(reducer: WorkspaceReducer);
23
23
  createAgent(agent: ManagedAgentDescriptor): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
24
24
  selectAgent(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
25
+ restartAgent(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
26
+ markRecovered(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
27
+ markFailed(agentId: string, code: string, message: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
28
+ requestAttention(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
25
29
  stopAgent(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
26
30
  removeAgent(agentId: string): Promise<WorkspaceRouterResult<WorkspaceAgentState>>;
27
31
  recordActivity(agentId: string, amount?: number): Promise<WorkspaceRouterResult<number>>;
28
32
  sendStructuredCommand(agentId: string, command: string, payload: unknown): Promise<WorkspaceRouterResult<RoutedStructuredCommand>>;
29
33
  cancelStructuredCommand(agentId: string, targetCorrelationId: string): Promise<WorkspaceRouterResult<StructuredCommandRecord>>;
34
+ settleStructuredCommand(agentId: string, correlationId: string, outcome: "completed" | "failed"): Promise<WorkspaceRouterResult<StructuredCommandRecord>>;
30
35
  view(): WorkspaceView;
31
36
  }
@@ -23,6 +23,18 @@ export class WorkspaceRouter {
23
23
  selectAgent(agentId) {
24
24
  return this.#enqueue(() => this.reducer.selectAgent(agentId));
25
25
  }
26
+ restartAgent(agentId) {
27
+ return this.#enqueue(() => this.reducer.restartAgent(agentId));
28
+ }
29
+ markRecovered(agentId) {
30
+ return this.#enqueue(() => this.reducer.markRecovered(agentId));
31
+ }
32
+ markFailed(agentId, code, message) {
33
+ return this.#enqueue(() => this.reducer.markFailed(agentId, code, message));
34
+ }
35
+ requestAttention(agentId) {
36
+ return this.#enqueue(() => this.reducer.requestAttention(agentId));
37
+ }
26
38
  stopAgent(agentId) {
27
39
  return this.#enqueue(() => this.reducer.stopAgent(agentId));
28
40
  }
@@ -92,6 +104,20 @@ export class WorkspaceRouter {
92
104
  return applied(this.reducer.view(), result.record);
93
105
  });
94
106
  }
107
+ settleStructuredCommand(agentId, correlationId, outcome) {
108
+ return this.#enqueue(() => {
109
+ const agent = this.reducer.view().agents.find(candidate => candidate.id === agentId);
110
+ if (!agent)
111
+ return reject("unknown-agent", `workspace agent does not exist: ${agentId}`);
112
+ const tracker = this.#trackerFor(agent);
113
+ if (!tracker)
114
+ return reject("capability-mismatch", `agent ${agent.id} is not structured`);
115
+ const result = tracker.complete(correlationId, outcome);
116
+ if (result.kind === "rejected")
117
+ return result;
118
+ return applied(this.reducer.view(), result.record);
119
+ });
120
+ }
95
121
  view() {
96
122
  return this.reducer.view();
97
123
  }
@@ -0,0 +1,83 @@
1
+ import { type AgentCommandOutcome, type AgentEnginePort, type AgentMessage, type AgentSessionLifecycle } from "../../foundation/agent-engine-contracts/index.js";
2
+ import { type WorkspacePresentationModel } from "./presentation.js";
3
+ import { type WorkspaceView } from "./reducer.js";
4
+ import { WorkspaceRouter } from "./router.js";
5
+ export interface StructuredWorkspaceLimits {
6
+ readonly maxAgents: number;
7
+ readonly maxMessagesPerAgent: number;
8
+ readonly maxMessageBytes: number;
9
+ readonly maxEditorBytes: number;
10
+ }
11
+ export interface StructuredWorkspaceTabsOptions {
12
+ readonly workspaceId?: string;
13
+ readonly cwd: string;
14
+ readonly createEngine: (agentId: string) => Promise<AgentEnginePort>;
15
+ readonly limits?: Partial<StructuredWorkspaceLimits>;
16
+ readonly now?: () => string;
17
+ }
18
+ export interface StructuredAgentTabView {
19
+ readonly role: "tabpanel";
20
+ readonly agentId: string;
21
+ readonly sessionId: string;
22
+ readonly selected: boolean;
23
+ readonly lifecycle: AgentSessionLifecycle;
24
+ readonly transcript: readonly AgentMessage[];
25
+ readonly toolMessages: readonly AgentMessage[];
26
+ readonly editorText: string;
27
+ readonly activeCommandIds: readonly string[];
28
+ readonly lastSequence: number;
29
+ readonly failure: string | null;
30
+ readonly accessibleDescription: string;
31
+ }
32
+ export interface StructuredWorkspaceTabSelector {
33
+ readonly role: "tab";
34
+ readonly agentId: string;
35
+ readonly label: string;
36
+ readonly selected: boolean;
37
+ readonly accessibleDescription: string;
38
+ }
39
+ export interface StructuredWorkspaceTabsView {
40
+ readonly role: "tablist";
41
+ readonly workspace: WorkspaceView;
42
+ readonly presentation: WorkspacePresentationModel;
43
+ readonly tabs: readonly StructuredWorkspaceTabSelector[];
44
+ readonly panels: readonly StructuredAgentTabView[];
45
+ readonly selectedPanel: StructuredAgentTabView | null;
46
+ }
47
+ export type StructuredWorkspaceTabsResult<T> = {
48
+ readonly kind: "applied";
49
+ readonly view: StructuredWorkspaceTabsView;
50
+ readonly value: T;
51
+ } | {
52
+ readonly kind: "rejected";
53
+ readonly code: string;
54
+ readonly diagnostic: string;
55
+ };
56
+ export declare class StructuredWorkspaceTabs {
57
+ #private;
58
+ readonly router: WorkspaceRouter;
59
+ constructor(options: StructuredWorkspaceTabsOptions);
60
+ subscribe(listener: (view: StructuredWorkspaceTabsView) => void): () => void;
61
+ createAgent(input: {
62
+ readonly id: string;
63
+ readonly displayName: string;
64
+ readonly sessionId?: string;
65
+ }): Promise<StructuredWorkspaceTabsResult<StructuredAgentTabView>>;
66
+ selectAgent(agentId: string): Promise<StructuredWorkspaceTabsResult<StructuredAgentTabView>>;
67
+ setEditorText(agentId: string, text: string): StructuredWorkspaceTabsResult<StructuredAgentTabView>;
68
+ submitSelected(): Promise<StructuredWorkspaceTabsResult<{
69
+ readonly correlationId: string;
70
+ readonly outcome: AgentCommandOutcome;
71
+ }>>;
72
+ sendPrompt(agentId: string, text: string): Promise<StructuredWorkspaceTabsResult<{
73
+ readonly correlationId: string;
74
+ readonly outcome: AgentCommandOutcome;
75
+ }>>;
76
+ stopAgent(agentId: string): Promise<StructuredWorkspaceTabsResult<StructuredAgentTabView>>;
77
+ restartAgent(agentId: string): Promise<StructuredWorkspaceTabsResult<StructuredAgentTabView>>;
78
+ refreshAgent(agentId: string): Promise<StructuredWorkspaceTabsResult<StructuredAgentTabView>>;
79
+ removeAgent(agentId: string): Promise<StructuredWorkspaceTabsResult<string>>;
80
+ flush(): Promise<void>;
81
+ view(): StructuredWorkspaceTabsView;
82
+ dispose(): Promise<void>;
83
+ }