agent-portal-2 0.1.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.
Files changed (120) hide show
  1. package/.continue/agents/new-config.yaml +22 -0
  2. package/AGENT_STEERING.md +36 -0
  3. package/ARCHITECTURE.md +13 -0
  4. package/CHANGELOG.md +97 -0
  5. package/CLI.md +38 -0
  6. package/CONTRIBUTING.md +55 -0
  7. package/INSTALLATION.md +58 -0
  8. package/LICENSE +60 -0
  9. package/PLUGIN_SYSTEM.md +33 -0
  10. package/PYTHON_SDK.md +22 -0
  11. package/QUICKSTART.md +19 -0
  12. package/README.md +385 -0
  13. package/RELEASE_NOTES_v0.1.0.md +281 -0
  14. package/ROADMAP.md +3 -0
  15. package/RUNTIME.md +44 -0
  16. package/SAFETY_MODEL.md +24 -0
  17. package/TESTING.md +35 -0
  18. package/TROUBLESHOOTING.md +30 -0
  19. package/UPGRADE_GUIDE.md +288 -0
  20. package/VS_CODE_EXTENSION.md +47 -0
  21. package/agent-portal.config.json +20 -0
  22. package/apps/desktop/agent-portal-desktop.zip +0 -0
  23. package/apps/desktop/fixtures/local-workflow.html +151 -0
  24. package/apps/desktop/package.json +18 -0
  25. package/apps/desktop/src/main.ts +117 -0
  26. package/apps/desktop/tsconfig.json +8 -0
  27. package/apps/vscode-extension/LICENSE +60 -0
  28. package/apps/vscode-extension/README.md +20 -0
  29. package/apps/vscode-extension/media/agent-portal-logo.png +0 -0
  30. package/apps/vscode-extension/package.json +149 -0
  31. package/apps/vscode-extension/src/extension.ts +614 -0
  32. package/apps/vscode-extension/tsconfig.json +12 -0
  33. package/assets/branding/agent-portal-logo.png +0 -0
  34. package/connectors/chatgpt-tools/README.md +9 -0
  35. package/connectors/claude-mcp-server/README.md +9 -0
  36. package/connectors/gemini-connector/README.md +9 -0
  37. package/connectors/rest-websocket-api/README.md +9 -0
  38. package/docs/MCP_SERVER.md +68 -0
  39. package/docs/architecture.md +214 -0
  40. package/docs/roadmap.md +125 -0
  41. package/package.json +21 -0
  42. package/packages/agent-portal-mcp/README.md +12 -0
  43. package/packages/agent-portal-mcp/agent_portal_mcp/__init__.py +3 -0
  44. package/packages/agent-portal-mcp/agent_portal_mcp/bridge/__init__.py +1 -0
  45. package/packages/agent-portal-mcp/agent_portal_mcp/bridge/runtime_client.py +180 -0
  46. package/packages/agent-portal-mcp/agent_portal_mcp/cli.py +32 -0
  47. package/packages/agent-portal-mcp/agent_portal_mcp/doctor.py +71 -0
  48. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/__init__.py +1 -0
  49. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/actions.py +17 -0
  50. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/results.py +24 -0
  51. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/risk.py +20 -0
  52. package/packages/agent-portal-mcp/agent_portal_mcp/security/__init__.py +1 -0
  53. package/packages/agent-portal-mcp/agent_portal_mcp/security/policy.py +27 -0
  54. package/packages/agent-portal-mcp/agent_portal_mcp/server.py +148 -0
  55. package/packages/agent-portal-mcp/agent_portal_mcp/tool_registry.py +58 -0
  56. package/packages/agent-portal-mcp/agent_portal_mcp/tools/__init__.py +1 -0
  57. package/packages/agent-portal-mcp/agent_portal_mcp/tools/browser.py +89 -0
  58. package/packages/agent-portal-mcp/agent_portal_mcp/tools/common.py +98 -0
  59. package/packages/agent-portal-mcp/agent_portal_mcp/tools/inspection.py +93 -0
  60. package/packages/agent-portal-mcp/agent_portal_mcp/tools/navigation.py +93 -0
  61. package/packages/agent-portal-mcp/agent_portal_mcp/tools/reports.py +34 -0
  62. package/packages/agent-portal-mcp/agent_portal_mcp/tools/steering.py +93 -0
  63. package/packages/agent-portal-mcp/pyproject.toml +20 -0
  64. package/packages/agent-portal-mcp/tests/test_doctor.py +20 -0
  65. package/packages/agent-portal-mcp/tests/test_mcp_server.py +161 -0
  66. package/packages/core/package.json +15 -0
  67. package/packages/core/src/index.ts +1842 -0
  68. package/packages/core/tsconfig.json +8 -0
  69. package/packages/mcp-server/package.json +15 -0
  70. package/packages/mcp-server/src/index.ts +73 -0
  71. package/packages/mcp-server/tsconfig.json +8 -0
  72. package/packages/sdk/package.json +15 -0
  73. package/packages/sdk/src/index.ts +544 -0
  74. package/packages/sdk/tsconfig.json +8 -0
  75. package/plugins/README.md +16 -0
  76. package/plugins/agent-portal-browser/plugin.json +19 -0
  77. package/plugins/agent-portal-python/plugin.json +16 -0
  78. package/plugins/agent-portal-skills/plugin.json +19 -0
  79. package/plugins/agent-portal-vscode/plugin.json +27 -0
  80. package/plugins/example-runtime-plugin/README.md +3 -0
  81. package/plugins/example-runtime-plugin/plugin.json +20 -0
  82. package/plugins/plugin.schema.json +53 -0
  83. package/python/README.md +18 -0
  84. package/python/agent_portal/__init__.py +5 -0
  85. package/python/agent_portal/__main__.py +5 -0
  86. package/python/agent_portal/browser.py +393 -0
  87. package/python/agent_portal/cli.py +164 -0
  88. package/python/agent_portal/config.py +31 -0
  89. package/python/agent_portal/doctor.py +165 -0
  90. package/python/agent_portal/exceptions.py +39 -0
  91. package/python/agent_portal/logging_utils.py +33 -0
  92. package/python/agent_portal/metrics.py +309 -0
  93. package/python/agent_portal/models.py +160 -0
  94. package/python/agent_portal/plugin_system.py +42 -0
  95. package/python/agent_portal/rate_limit.py +253 -0
  96. package/python/agent_portal/runtime.py +739 -0
  97. package/python/agent_portal/server.py +351 -0
  98. package/python/agent_portal/validation.py +299 -0
  99. package/python/pyproject.toml +29 -0
  100. package/python/tests/test_config.py +24 -0
  101. package/python/tests/test_doctor.py +19 -0
  102. package/python/tests/test_metrics.py +180 -0
  103. package/python/tests/test_rate_limit.py +237 -0
  104. package/python/tests/test_runtime.py +122 -0
  105. package/python/tests/test_server.py +53 -0
  106. package/python/tests/test_validation.py +170 -0
  107. package/releases/desktop/agent-portal-desktop/README.md +378 -0
  108. package/releases/desktop/agent-portal-desktop/RELEASE_NOTES.md +14 -0
  109. package/releases/desktop/agent-portal-desktop/assets/branding/agent-portal-logo.png +0 -0
  110. package/releases/desktop/agent-portal-desktop/fixtures/local-workflow.html +151 -0
  111. package/releases/desktop/agent-portal-desktop/launch-agent-portal.bat +4 -0
  112. package/releases/desktop/agent-portal-desktop.zip +0 -0
  113. package/releases/python/agent_portal-0.0.2-py3-none-any.whl +0 -0
  114. package/releases/python/agent_portal-0.0.2.tar.gz +0 -0
  115. package/scripts/package_desktop.mjs +117 -0
  116. package/scripts/release_python.py +46 -0
  117. package/tests/plugin-manifest.test.mjs +26 -0
  118. package/tests/runtime.test.mjs +41 -0
  119. package/tests/vscode-extension.test.mjs +22 -0
  120. package/tsconfig.base.json +16 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@agent-portal/mcp-server",
3
+ "version": "0.0.2",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json",
10
+ "typecheck": "tsc -p tsconfig.json --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@agent-portal/core": "0.0.2"
14
+ }
15
+ }
@@ -0,0 +1,73 @@
1
+ import { BrowserAction } from "@agent-portal/core";
2
+
3
+ export interface ToolRequest {
4
+ tool: string;
5
+ args: Record<string, unknown>;
6
+ }
7
+
8
+ export interface ToolResponse {
9
+ ok: boolean;
10
+ message: string;
11
+ }
12
+
13
+ const supportedTools = new Set([
14
+ "portal.open",
15
+ "portal.click",
16
+ "portal.type",
17
+ "portal.hover",
18
+ "portal.scroll",
19
+ "portal.wait",
20
+ "portal.execute",
21
+ "portal.capture",
22
+ "portal.inspect",
23
+ "portal.agent.start",
24
+ "portal.agent.block",
25
+ "portal.agent.complete",
26
+ "portal.agent.reset"
27
+ ]);
28
+
29
+ export function handleToolRequest(request: ToolRequest): ToolResponse {
30
+ if (!supportedTools.has(request.tool)) {
31
+ return {
32
+ ok: false,
33
+ message: `Unsupported tool: ${request.tool}`
34
+ };
35
+ }
36
+
37
+ const action = mapToolToAction(request);
38
+
39
+ return {
40
+ ok: true,
41
+ message: `Accepted ${action.kind}${action.target ? ` for ${action.target}` : ""}`
42
+ };
43
+ }
44
+
45
+ function mapToolToAction(request: ToolRequest): BrowserAction {
46
+ const target =
47
+ typeof request.args.target === "string" ? request.args.target : undefined;
48
+ const payload =
49
+ typeof request.args.payload === "string" ? request.args.payload : undefined;
50
+
51
+ switch (request.tool) {
52
+ case "portal.open":
53
+ return { kind: "open", target };
54
+ case "portal.click":
55
+ return { kind: "click", target };
56
+ case "portal.type":
57
+ return { kind: "type", target, payload };
58
+ case "portal.hover":
59
+ return { kind: "hover", target };
60
+ case "portal.scroll":
61
+ return { kind: "scroll", target, payload };
62
+ case "portal.wait":
63
+ return { kind: "wait", target };
64
+ case "portal.execute":
65
+ return { kind: "execute", payload };
66
+ case "portal.capture":
67
+ return { kind: "capture" };
68
+ case "portal.inspect":
69
+ return { kind: "inspect", target };
70
+ default:
71
+ return { kind: "inspect" };
72
+ }
73
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@agent-portal/sdk",
3
+ "version": "0.0.2",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json",
10
+ "typecheck": "tsc -p tsconfig.json --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@agent-portal/core": "0.0.2"
14
+ }
15
+ }
@@ -0,0 +1,544 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import {
4
+ ActionQueueItem,
5
+ AgentDefinition,
6
+ BrowserExecuteResult,
7
+ BrowserInspectionResult,
8
+ BrowserElementText,
9
+ ConsoleLogEntry,
10
+ GoalDefinition,
11
+ GoalPlan,
12
+ GoalPlanner,
13
+ MemoryRecord,
14
+ NetworkEvent,
15
+ PortalGraph,
16
+ PortalGraphNode,
17
+ ProjectAwareness,
18
+ RuntimeStatus,
19
+ ScreenUnderstanding,
20
+ ScrollOptions,
21
+ SteeringState,
22
+ VisionCore,
23
+ VisualSnapshot,
24
+ WorkspaceDefinition,
25
+ createDefaultWorkspace
26
+ } from "@agent-portal/core";
27
+
28
+ export interface PortalOptions {
29
+ runtimeUrl?: string;
30
+ apiToken?: string;
31
+ workspaceName?: string;
32
+ workspace?: WorkspaceDefinition;
33
+ workspaceBasePath?: string;
34
+ }
35
+
36
+ interface RuntimeStatusResponse {
37
+ session: {
38
+ runtime_status: RuntimeStatus | string;
39
+ current_goal?: string | null;
40
+ pending_actions: Array<{
41
+ action_id: string;
42
+ action_type: string;
43
+ target?: string;
44
+ payload?: string;
45
+ reason: string;
46
+ risk_level: string;
47
+ created_at: string;
48
+ status: string;
49
+ result?: string | null;
50
+ before_screenshot?: string | null;
51
+ after_screenshot?: string | null;
52
+ }>;
53
+ };
54
+ browser: {
55
+ connected: boolean;
56
+ current_url?: string | null;
57
+ page_title?: string | null;
58
+ last_error?: string | null;
59
+ };
60
+ policy?: {
61
+ mode?: string;
62
+ approval_threshold?: string;
63
+ domain_lock?: string | null;
64
+ tab_lock?: string | null;
65
+ read_only?: boolean;
66
+ };
67
+ }
68
+
69
+ interface RuntimeCaptureResponse {
70
+ action?: {
71
+ after_screenshot?: string | null;
72
+ };
73
+ inspection: {
74
+ url?: string | null;
75
+ title?: string | null;
76
+ dom?: string;
77
+ consoleErrors?: string[];
78
+ networkErrors?: string[];
79
+ };
80
+ screenshotPath?: string | null;
81
+ }
82
+
83
+ export class Portal {
84
+ private readonly runtimeUrl: string;
85
+ private readonly apiToken?: string;
86
+ private readonly workspace: WorkspaceDefinition;
87
+ private readonly workspaceBasePath: string;
88
+ private readonly vision = new VisionCore();
89
+ private readonly planner = new GoalPlanner();
90
+ private readonly graphBuilder = new PortalGraph();
91
+ private readonly graphNodes: PortalGraphNode[] = [];
92
+ private readonly memoryRecords: MemoryRecord[] = [];
93
+ private readonly agents = new Map<string, AgentDefinition>();
94
+
95
+ constructor(options: PortalOptions = {}) {
96
+ this.runtimeUrl = options.runtimeUrl ?? "http://127.0.0.1:8765";
97
+ this.apiToken = options.apiToken;
98
+ this.workspace =
99
+ options.workspace ?? createDefaultWorkspace(options.workspaceName ?? "default");
100
+ this.workspaceBasePath = options.workspaceBasePath ?? process.cwd();
101
+ }
102
+
103
+ launch(): string {
104
+ return `Portal client ready for ${this.runtimeUrl}`;
105
+ }
106
+
107
+ async startRuntime(): Promise<RuntimeStatusResponse> {
108
+ return this.post("/control/start");
109
+ }
110
+
111
+ async stopRuntime(): Promise<unknown> {
112
+ return this.post("/control/stop");
113
+ }
114
+
115
+ async restartRuntime(): Promise<RuntimeStatusResponse> {
116
+ return this.post("/control/restart");
117
+ }
118
+
119
+ async startBrowser(): Promise<RuntimeStatusResponse> {
120
+ return this.post("/browser/start");
121
+ }
122
+
123
+ async open(target: string): Promise<string> {
124
+ await this.post("/browser/open", { url: target });
125
+ return `open on ${target}`;
126
+ }
127
+
128
+ async click(target: string, reason = "Click element"): Promise<string> {
129
+ await this.post("/browser/click", { selector: target, reason });
130
+ return `click on ${target}`;
131
+ }
132
+
133
+ async type(target: string, payload: string, reason = "Type into element"): Promise<string> {
134
+ await this.post("/browser/type", { selector: target, value: payload, reason });
135
+ return `type on ${target}`;
136
+ }
137
+
138
+ async hover(target: string, reason = "Hover over element"): Promise<string> {
139
+ await this.post("/browser/hover", { selector: target, reason });
140
+ return `hover on ${target}`;
141
+ }
142
+
143
+ async scroll(options: ScrollOptions = {}): Promise<string> {
144
+ await this.post("/browser/scroll", {
145
+ selector: options.selector,
146
+ reason: options.selector ? "Scroll target into view" : "Scroll page",
147
+ deltaX: options.deltaX,
148
+ deltaY: options.deltaY
149
+ });
150
+ return `scroll${options.selector ? ` on ${options.selector}` : ""}`;
151
+ }
152
+
153
+ async waitFor(target: string, timeoutMs?: number): Promise<string> {
154
+ await this.post("/browser/wait", {
155
+ selector: target,
156
+ reason: timeoutMs
157
+ ? `Wait for element within ${timeoutMs}ms`
158
+ : "Wait for element"
159
+ });
160
+ return `wait on ${target}`;
161
+ }
162
+
163
+ async execute(script: string): Promise<BrowserExecuteResult> {
164
+ const response = await this.post<{ result: unknown }>("/browser/execute", { script });
165
+ return {
166
+ result: response.result
167
+ };
168
+ }
169
+
170
+ async capture(label = "capture"): Promise<BrowserInspectionResult> {
171
+ const response = await this.post<RuntimeCaptureResponse>("/browser/capture", { label });
172
+ return this.buildInspectionResult(response.inspection, response.screenshotPath ?? undefined, label);
173
+ }
174
+
175
+ async inspect(target = "inspect"): Promise<BrowserInspectionResult> {
176
+ const response = await this.post<RuntimeCaptureResponse>("/browser/capture", { label: target });
177
+ return this.buildInspectionResult(response.inspection, response.screenshotPath ?? undefined, target);
178
+ }
179
+
180
+ async close(): Promise<void> {
181
+ await this.stopRuntime().catch(() => undefined);
182
+ }
183
+
184
+ async readText(target: string): Promise<BrowserElementText> {
185
+ const response = await this.post<{ text: string | null; selector: string }>(
186
+ "/browser/read-text",
187
+ { selector: target }
188
+ );
189
+ return {
190
+ selector: response.selector,
191
+ text: response.text
192
+ };
193
+ }
194
+
195
+ async writeReport(): Promise<string> {
196
+ const response = await this.post<{ reportPath: string }>("/report/generate");
197
+ return response.reportPath;
198
+ }
199
+
200
+ async status(): Promise<RuntimeStatusResponse> {
201
+ return this.get("/status");
202
+ }
203
+
204
+ async health(): Promise<Record<string, unknown>> {
205
+ return this.get("/health");
206
+ }
207
+
208
+ understand(result: BrowserInspectionResult, goal?: string): ScreenUnderstanding {
209
+ return this.vision.analyze({
210
+ snapshot: result.snapshot,
211
+ goal
212
+ });
213
+ }
214
+
215
+ planGoal(goal: GoalDefinition, understanding?: ScreenUnderstanding): GoalPlan {
216
+ return this.planner.createPlan(goal, understanding);
217
+ }
218
+
219
+ async remember(record: MemoryRecord): Promise<string> {
220
+ this.memoryRecords.push(record);
221
+ return path.join(this.workspaceBasePath, this.workspace.directories.memory, `${record.id}.json`);
222
+ }
223
+
224
+ mapCurrentPage(result: BrowserInspectionResult, understanding: ScreenUnderstanding): void {
225
+ const next = this.graphBuilder.fromSnapshot(result.snapshot, understanding);
226
+ const index = this.graphNodes.findIndex((entry) => entry.id === next.id);
227
+ if (index >= 0) {
228
+ this.graphNodes[index] = next;
229
+ return;
230
+ }
231
+ this.graphNodes.push(next);
232
+ }
233
+
234
+ async detectProjectAwareness(): Promise<ProjectAwareness> {
235
+ const packageJsonPath = path.resolve(this.workspaceBasePath, "package.json");
236
+ const frameworks = new Set<string>();
237
+ const services = new Set<string>();
238
+ const packageManagers = new Set<string>();
239
+
240
+ try {
241
+ const raw = await readFile(packageJsonPath, "utf8");
242
+ const pkg = JSON.parse(raw) as {
243
+ dependencies?: Record<string, string>;
244
+ devDependencies?: Record<string, string>;
245
+ packageManager?: string;
246
+ };
247
+ const names = Object.keys({
248
+ ...(pkg.dependencies ?? {}),
249
+ ...(pkg.devDependencies ?? {})
250
+ });
251
+
252
+ if (names.includes("next")) frameworks.add("Next.js");
253
+ if (names.includes("react")) frameworks.add("React");
254
+ if (names.includes("vue")) frameworks.add("Vue");
255
+ if (names.includes("angular")) frameworks.add("Angular");
256
+ if (names.includes("express")) frameworks.add("Express");
257
+ if (names.includes("fastapi")) frameworks.add("FastAPI");
258
+ if (names.includes("@supabase/supabase-js")) services.add("Supabase");
259
+ if (names.includes("stripe")) services.add("Stripe");
260
+ if (pkg.packageManager) packageManagers.add(pkg.packageManager.split("@")[0]);
261
+ } catch {
262
+ return {
263
+ frameworks: [],
264
+ services: [],
265
+ packageManagers: [],
266
+ summary: "Project awareness could not be derived from package.json."
267
+ };
268
+ }
269
+
270
+ return {
271
+ frameworks: [...frameworks],
272
+ services: [...services],
273
+ packageManagers: [...packageManagers],
274
+ summary: `Detected ${[...frameworks, ...services].join(", ") || "no known frameworks or services"}.`
275
+ };
276
+ }
277
+
278
+ startAgent(agentId: string): AgentDefinition {
279
+ return this.updateAgent(agentId, "running");
280
+ }
281
+
282
+ blockAgent(agentId: string): AgentDefinition {
283
+ return this.updateAgent(agentId, "blocked");
284
+ }
285
+
286
+ completeAgent(agentId: string): AgentDefinition {
287
+ return this.updateAgent(agentId, "completed");
288
+ }
289
+
290
+ resetAgent(agentId: string): AgentDefinition {
291
+ return this.updateAgent(agentId, "idle");
292
+ }
293
+
294
+ async pause(): Promise<void> {
295
+ await this.post("/control/pause");
296
+ }
297
+
298
+ async resume(): Promise<void> {
299
+ await this.post("/control/resume");
300
+ }
301
+
302
+ async stop(): Promise<void> {
303
+ await this.post("/control/stop");
304
+ }
305
+
306
+ clearStop(): void {
307
+ return;
308
+ }
309
+
310
+ enableStepByStep(): void {
311
+ return;
312
+ }
313
+
314
+ disableStepByStep(): void {
315
+ return;
316
+ }
317
+
318
+ async redirectGoal(goal: string): Promise<void> {
319
+ await this.post("/control/goal", { goal });
320
+ }
321
+
322
+ async lockToDomain(domain: string): Promise<void> {
323
+ await this.post("/control/domain-lock", { domain });
324
+ }
325
+
326
+ async unlockDomain(): Promise<void> {
327
+ await this.post("/control/domain-lock", { domain: null });
328
+ }
329
+
330
+ async lockToTab(url: string): Promise<void> {
331
+ await this.post("/control/tab-lock", { url });
332
+ }
333
+
334
+ async unlockTab(): Promise<void> {
335
+ await this.post("/control/tab-lock", { url: null });
336
+ }
337
+
338
+ async approveNextAction(): Promise<ActionQueueItem | undefined> {
339
+ const result = await this.post<Record<string, unknown>>("/control/approve-next");
340
+ return this.normalizeQueueItem(result);
341
+ }
342
+
343
+ async rejectNextAction(reason?: string): Promise<ActionQueueItem | undefined> {
344
+ const result = await this.post<Record<string, unknown>>("/control/reject-next", {
345
+ reason
346
+ });
347
+ return this.normalizeQueueItem(result);
348
+ }
349
+
350
+ editNextAction(): ActionQueueItem | undefined {
351
+ return undefined;
352
+ }
353
+
354
+ async pendingActions(): Promise<ActionQueueItem[]> {
355
+ const status = await this.status();
356
+ return status.session.pending_actions.map((action) => this.mapRuntimeAction(action));
357
+ }
358
+
359
+ async steering(): Promise<SteeringState> {
360
+ const status = await this.status();
361
+ return {
362
+ paused: status.session.runtime_status === "paused",
363
+ stopped: status.session.runtime_status === "stopped",
364
+ stepByStepMode: false,
365
+ currentGoal: status.session.current_goal ?? undefined,
366
+ lockedDomain: status.policy?.domain_lock ?? undefined,
367
+ lockedTabUrl: status.policy?.tab_lock ?? undefined,
368
+ requireApprovalAtOrAbove:
369
+ (status.policy?.approval_threshold as SteeringState["requireApprovalAtOrAbove"]) ?? "medium"
370
+ };
371
+ }
372
+
373
+ async runtimeStatus(): Promise<RuntimeStatus> {
374
+ const status = await this.status();
375
+ return this.normalizeRuntimeStatus(status.session.runtime_status);
376
+ }
377
+
378
+ async report(): Promise<{
379
+ runtimeStatus: RuntimeStatus;
380
+ browserConnected: boolean;
381
+ currentUrl?: string;
382
+ pendingActions: ActionQueueItem[];
383
+ }> {
384
+ const status = await this.status();
385
+ return {
386
+ runtimeStatus: this.normalizeRuntimeStatus(status.session.runtime_status),
387
+ browserConnected: status.browser.connected,
388
+ currentUrl: status.browser.current_url ?? undefined,
389
+ pendingActions: status.session.pending_actions.map((action) => this.mapRuntimeAction(action))
390
+ };
391
+ }
392
+
393
+ private async get<T>(route: string): Promise<T> {
394
+ return this.request<T>(route, {
395
+ method: "GET"
396
+ });
397
+ }
398
+
399
+ private async post<T>(route: string, payload?: Record<string, unknown>): Promise<T> {
400
+ return this.request<T>(route, {
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/json"
404
+ },
405
+ body: JSON.stringify(payload ?? {})
406
+ });
407
+ }
408
+
409
+ private async request<T>(route: string, init: RequestInit): Promise<T> {
410
+ const headers = new Headers(init.headers);
411
+ if (this.apiToken) {
412
+ headers.set("Authorization", `Bearer ${this.apiToken}`);
413
+ }
414
+
415
+ const response = await fetch(`${this.runtimeUrl}${route}`, {
416
+ ...init,
417
+ headers,
418
+ signal: AbortSignal.timeout(5_000)
419
+ });
420
+
421
+ if (!response.ok) {
422
+ let message = `${response.status} ${response.statusText}`;
423
+ try {
424
+ const errorBody = (await response.json()) as { message?: string; error?: string };
425
+ message = errorBody.message ?? errorBody.error ?? message;
426
+ } catch {
427
+ // Keep default message when the response body is not JSON.
428
+ }
429
+ throw new Error(`Agent Portal runtime request failed: ${message}`);
430
+ }
431
+
432
+ return (await response.json()) as T;
433
+ }
434
+
435
+ private buildInspectionResult(
436
+ inspection: RuntimeCaptureResponse["inspection"],
437
+ screenshotPath: string | undefined,
438
+ label: string
439
+ ): BrowserInspectionResult {
440
+ const consoleLogs = (inspection.consoleErrors ?? []).map<ConsoleLogEntry>((text) => ({
441
+ type: "error",
442
+ text,
443
+ at: new Date().toISOString()
444
+ }));
445
+ const networkEvents = (inspection.networkErrors ?? []).map<NetworkEvent>((text) => ({
446
+ url: text,
447
+ method: "GET",
448
+ resourceType: "unknown",
449
+ outcome: "failed",
450
+ failureText: text,
451
+ at: new Date().toISOString()
452
+ }));
453
+ const snapshot: VisualSnapshot = {
454
+ id: `${Date.now()}-${label}`,
455
+ source: "browser",
456
+ capturedAt: new Date().toISOString(),
457
+ screenshotPath,
458
+ dom: inspection.dom ?? "",
459
+ consoleLogs,
460
+ networkEvents,
461
+ label,
462
+ url: inspection.url ?? undefined,
463
+ detectedElements: []
464
+ };
465
+
466
+ return {
467
+ url: inspection.url ?? "",
468
+ title: inspection.title ?? "",
469
+ dom: inspection.dom ?? "",
470
+ screenshotPath: screenshotPath ?? "",
471
+ consoleLogs,
472
+ networkEvents,
473
+ snapshot
474
+ };
475
+ }
476
+
477
+ private updateAgent(
478
+ agentId: string,
479
+ status: AgentDefinition["status"]
480
+ ): AgentDefinition {
481
+ const current =
482
+ this.agents.get(agentId) ?? {
483
+ id: agentId,
484
+ role: "custom",
485
+ status: "idle"
486
+ };
487
+ const next = {
488
+ ...current,
489
+ status
490
+ };
491
+ this.agents.set(agentId, next);
492
+ return next;
493
+ }
494
+
495
+ private mapRuntimeAction(action: RuntimeStatusResponse["session"]["pending_actions"][number]): ActionQueueItem {
496
+ return {
497
+ id: action.action_id,
498
+ actionType: action.action_type as ActionQueueItem["actionType"],
499
+ target: action.target,
500
+ payload: action.payload,
501
+ reason: action.reason,
502
+ riskLevel: action.risk_level as ActionQueueItem["riskLevel"],
503
+ timestamp: action.created_at,
504
+ status: action.status as ActionQueueItem["status"],
505
+ result: action.result ?? undefined,
506
+ requiresApproval: action.status === "pending"
507
+ };
508
+ }
509
+
510
+ private normalizeQueueItem(value: Record<string, unknown>): ActionQueueItem | undefined {
511
+ if (typeof value.action_id !== "string" || typeof value.action_type !== "string") {
512
+ return undefined;
513
+ }
514
+
515
+ return {
516
+ id: value.action_id,
517
+ actionType: value.action_type as ActionQueueItem["actionType"],
518
+ target: typeof value.target === "string" ? value.target : undefined,
519
+ payload: typeof value.payload === "string" ? value.payload : undefined,
520
+ reason: typeof value.reason === "string" ? value.reason : "Runtime action",
521
+ riskLevel: (typeof value.risk_level === "string" ? value.risk_level : "low") as ActionQueueItem["riskLevel"],
522
+ timestamp: typeof value.created_at === "string" ? value.created_at : new Date().toISOString(),
523
+ status: (typeof value.status === "string" ? value.status : "pending") as ActionQueueItem["status"],
524
+ result: typeof value.result === "string" ? value.result : undefined,
525
+ requiresApproval: value.status === "pending"
526
+ };
527
+ }
528
+
529
+ private normalizeRuntimeStatus(status: string): RuntimeStatus {
530
+ switch (status) {
531
+ case "thinking":
532
+ case "waiting-approval":
533
+ case "acting":
534
+ case "paused":
535
+ case "blocked":
536
+ case "finished":
537
+ case "failed":
538
+ case "stopped":
539
+ return status;
540
+ default:
541
+ return "idle";
542
+ }
543
+ }
544
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,16 @@
1
+ # Plugins
2
+
3
+ Agent Portal plugins are discovered from the `plugins/` folder.
4
+
5
+ ## Included Assets
6
+
7
+ - manifest examples for VS Code, browser, Python, and skill plugins
8
+ - shared schema at `plugins/plugin.schema.json`
9
+
10
+ ## Validation
11
+
12
+ Run:
13
+
14
+ ```bash
15
+ agent-portal plugins validate
16
+ ```