@ai-agent-forge/agent-forge 0.88.4 → 0.88.6
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/CHANGELOG.md +30 -0
- package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
- package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
- package/bundled-plugins/alignment/agent-forge.json +19 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts +12 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/baseline.js +68 -0
- package/bundled-plugins/alignment/dist/baseline.js.map +1 -0
- package/bundled-plugins/alignment/dist/engine.d.ts +102 -0
- package/bundled-plugins/alignment/dist/engine.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/engine.js +654 -0
- package/bundled-plugins/alignment/dist/engine.js.map +1 -0
- package/bundled-plugins/alignment/dist/entry.d.ts +14 -0
- package/bundled-plugins/alignment/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/entry.js +23 -0
- package/bundled-plugins/alignment/dist/entry.js.map +1 -0
- package/bundled-plugins/alignment/dist/export.d.ts +15 -0
- package/bundled-plugins/alignment/dist/export.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/export.js +122 -0
- package/bundled-plugins/alignment/dist/export.js.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts +33 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.js +119 -0
- package/bundled-plugins/alignment/dist/http-server.js.map +1 -0
- package/bundled-plugins/alignment/dist/index.d.ts +24 -0
- package/bundled-plugins/alignment/dist/index.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/index.js +719 -0
- package/bundled-plugins/alignment/dist/index.js.map +1 -0
- package/bundled-plugins/alignment/dist/page.d.ts +15 -0
- package/bundled-plugins/alignment/dist/page.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/page.js +832 -0
- package/bundled-plugins/alignment/dist/page.js.map +1 -0
- package/bundled-plugins/alignment/dist/render.d.ts +40 -0
- package/bundled-plugins/alignment/dist/render.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/render.js +194 -0
- package/bundled-plugins/alignment/dist/render.js.map +1 -0
- package/bundled-plugins/alignment/dist/types.d.ts +217 -0
- package/bundled-plugins/alignment/dist/types.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/types.js +30 -0
- package/bundled-plugins/alignment/dist/types.js.map +1 -0
- package/bundled-plugins/alignment/plugin.json +12 -0
- package/bundled-plugins/browser-playwright/MANUAL.md +60 -0
- package/bundled-plugins/browser-playwright/agent-forge.json +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/capability.js +612 -0
- package/bundled-plugins/browser-playwright/dist/capability.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts +13 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.js +14 -0
- package/bundled-plugins/browser-playwright/dist/entry.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts +7 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.js +5 -0
- package/bundled-plugins/browser-playwright/dist/index.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts +69 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.js +149 -0
- package/bundled-plugins/browser-playwright/dist/operations.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts +275 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js +953 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts +88 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.js +252 -0
- package/bundled-plugins/browser-playwright/dist/setup.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts +270 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.js +2 -0
- package/bundled-plugins/browser-playwright/dist/types.js.map +1 -0
- package/bundled-plugins/browser-playwright/plugin.json +20 -0
- package/bundled-plugins/coding-tools/agent-forge.json +1 -1
- package/bundled-plugins/coding-tools/dist/bash.d.ts +2 -7
- package/bundled-plugins/coding-tools/dist/bash.d.ts.map +1 -1
- package/bundled-plugins/coding-tools/dist/bash.js +2 -34
- package/bundled-plugins/coding-tools/dist/bash.js.map +1 -1
- package/bundled-plugins/coding-tools/plugin.json +1 -1
- package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
- package/bundled-plugins/compaction-policy/plugin.json +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.d.ts.map +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.js +84 -31
- package/bundled-plugins/mcp-http/dist/mcp-http.js.map +1 -1
- package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
- package/bundled-plugins/overflow-policy/plugin.json +1 -1
- package/bundled-plugins/provider-failover/agent-forge.json +1 -1
- package/bundled-plugins/provider-failover/plugin.json +1 -1
- package/bundled-plugins/retry-policy/agent-forge.json +1 -1
- package/bundled-plugins/retry-policy/plugin.json +1 -1
- package/dist/bundle/.build-manifest.json +60 -58
- package/dist/bundle/chunks/{anthropic-messages-3FJHRAJL.js → anthropic-messages-NL4Y6WPB.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-GCL47IDS.js → azure-openai-responses-CPXC42HO.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-5LRQFZFE.js +2 -0
- package/dist/bundle/chunks/{chunk-HTEVUY7F.js → chunk-7HSJPQKN.js} +1 -1
- package/dist/bundle/chunks/{chunk-AC2FIS4G.js → chunk-EDO7Q6XR.js} +1 -1
- package/dist/bundle/chunks/{chunk-755VNWIF.js → chunk-ISLHFRER.js} +6 -6
- package/dist/bundle/chunks/{chunk-HV64ASMT.js → chunk-K3S6GTL7.js} +1 -1
- package/dist/bundle/chunks/{chunk-HEL6TAWE.js → chunk-POLDAUYA.js} +37 -36
- package/dist/bundle/chunks/{chunk-V3ACW7VD.js → chunk-WJSKB3FC.js} +1 -1
- package/dist/bundle/chunks/{chunk-32YDZI4J.js → chunk-ZS5JEI76.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-6BMXWA44.js → google-generative-ai-UMZ53EFA.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-6H7NLRTK.js → google-vertex-ZNR5NS6H.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{mistral-conversations-IXUHSFSJ.js → mistral-conversations-IT66WUNN.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-YHUAEVCM.js → openai-codex-responses-FOVCVG6I.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-ULQCFC72.js → openai-completions-FLQ7CBOL.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-NLLAJDPS.js → openai-responses-ABGYHS2T.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-EJTI2EJ3.js → pi-messages-6K5W6LUN.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-TQWEGNNZ.js → virtual-modules-2QGILFV7.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/client/remote-session.d.ts.map +1 -1
- package/dist/client/remote-session.js +65 -6
- package/dist/client/remote-session.js.map +1 -1
- package/dist/core/agent-session-store.d.ts.map +1 -1
- package/dist/core/agent-session-store.js +20 -3
- package/dist/core/agent-session-store.js.map +1 -1
- package/dist/core/extensions/agent-composition.d.ts.map +1 -1
- package/dist/core/extensions/agent-composition.js +20 -1
- package/dist/core/extensions/agent-composition.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.js +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +10 -3
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
- package/dist/core/keybindings.d.ts +35 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +34 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +62 -15
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +11 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/capability-mount.d.ts.map +1 -1
- package/dist/core/tools/capability-mount.js +4 -21
- package/dist/core/tools/capability-mount.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -2
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +2 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +25 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/changelog-pager.d.ts +53 -0
- package/dist/modes/interactive/components/changelog-pager.d.ts.map +1 -0
- package/dist/modes/interactive/components/changelog-pager.js +239 -0
- package/dist/modes/interactive/components/changelog-pager.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.d.ts +41 -11
- package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.js +126 -14
- package/dist/modes/interactive/components/tool-run-group.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +23 -13
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-events.d.ts +14 -4
- package/dist/modes/interactive/interactive-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-events.js +94 -12
- package/dist/modes/interactive/interactive-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +6 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +21 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts +2 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.js +110 -4
- package/dist/plugins/ecosystem/node-local-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +106 -39
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +59 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/suites/builtin-suites.d.ts.map +1 -1
- package/dist/suites/builtin-suites.js +2 -2
- package/dist/suites/builtin-suites.js.map +1 -1
- package/dist/utils/changelog.d.ts +27 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +110 -0
- package/dist/utils/changelog.js.map +1 -1
- package/docs/keybindings.md +14 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/package.json +1 -1
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data model of the alignment capability plugin
|
|
3
|
+
* (`docs/design/需求对齐插件设计.md` §4). This module declares shapes only —
|
|
4
|
+
* every conversion rule lives in `engine.ts` and every host-facing side effect
|
|
5
|
+
* (entries, events, injection, HTTP) lives in `index.ts` / `http-server.ts`.
|
|
6
|
+
*/
|
|
7
|
+
/** The three funnel steps, in canonical order. */
|
|
8
|
+
export declare const STEP_ORDER: readonly ["objective", "detail", "design"];
|
|
9
|
+
export type AlignmentStepId = (typeof STEP_ORDER)[number];
|
|
10
|
+
export type AlignmentBlockKind = "fact" | "assumption" | "decision" | "missing" | "visual";
|
|
11
|
+
export type AlignmentCommentAuthor = "user" | "assistant";
|
|
12
|
+
export type AlignmentCommentResolutionKind = "answered" | "dismissed" | "incorporated";
|
|
13
|
+
/** Bidirectional block annotation (设计 §4: author distinguishes the direction). */
|
|
14
|
+
export interface AlignmentComment {
|
|
15
|
+
id: string;
|
|
16
|
+
author: AlignmentCommentAuthor;
|
|
17
|
+
/** Exact in-block quote; required for user comments, optional for assistant ones. */
|
|
18
|
+
quote?: string;
|
|
19
|
+
note: string;
|
|
20
|
+
/** User flag: the objection targets an earlier (upstream) step — retreat candidate. */
|
|
21
|
+
escalate?: boolean;
|
|
22
|
+
status: "open" | "resolved";
|
|
23
|
+
resolution?: {
|
|
24
|
+
kind: AlignmentCommentResolutionKind;
|
|
25
|
+
text?: string;
|
|
26
|
+
round?: number;
|
|
27
|
+
};
|
|
28
|
+
createdAtRound: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AlignmentBlock {
|
|
31
|
+
id: string;
|
|
32
|
+
kind: AlignmentBlockKind;
|
|
33
|
+
content: string;
|
|
34
|
+
rationale?: string;
|
|
35
|
+
impact?: string[];
|
|
36
|
+
status: "pending" | "approved";
|
|
37
|
+
/** Engine-computed fingerprint (kind+content+rationale+impact); cross-round approval inheritance key. */
|
|
38
|
+
contentHash: string;
|
|
39
|
+
/** Round in which the content last changed; drives the "modified this round" badge. */
|
|
40
|
+
modifiedInRound?: number;
|
|
41
|
+
/** Stored fill-in for a missing block — the user's own words, kept verbatim until the model restructures the block. */
|
|
42
|
+
draftAnswer?: string;
|
|
43
|
+
comments: AlignmentComment[];
|
|
44
|
+
}
|
|
45
|
+
export interface AlignmentStepState {
|
|
46
|
+
step: AlignmentStepId;
|
|
47
|
+
/** pending = not unlocked yet (terminal-audit D2); reset = retreated into, confirmations cleared. */
|
|
48
|
+
status: "pending" | "locked" | "active" | "reset";
|
|
49
|
+
/** Round count at confirmation time (entry-sequence-like anchor, not wall clock). */
|
|
50
|
+
baselineConfirmedAtRound?: number;
|
|
51
|
+
blocks: AlignmentBlock[];
|
|
52
|
+
}
|
|
53
|
+
export interface AlignmentRecord {
|
|
54
|
+
id: string;
|
|
55
|
+
title: string;
|
|
56
|
+
/** Soft reference to goal.state (advisory, #10): dangling only warns. */
|
|
57
|
+
goalId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Created-as-aligning (no draft state, terminal audit D3). `finalizing`
|
|
60
|
+
* (走查 2026-10-03) = all three steps locked, waiting for the final
|
|
61
|
+
* review tab confirmation — only that confirmation completes the
|
|
62
|
+
* alignment and writes the baseline (方案确认 ≠ 终版确认).
|
|
63
|
+
*/
|
|
64
|
+
status: "aligning" | "finalizing" | "completed" | "cancelled";
|
|
65
|
+
/**
|
|
66
|
+
* Monotonic for alignment_set callers; engine-triggered conversions
|
|
67
|
+
* (freeze/unlock/retreat/complete) auto-increment it too (workflow
|
|
68
|
+
* precedent, terminal audit D4). Carried on round results so the model
|
|
69
|
+
* always knows the next acceptable revision.
|
|
70
|
+
*/
|
|
71
|
+
revision: number;
|
|
72
|
+
/** Points at the single step with status "active"; pending/reset/locked steps are not writable. */
|
|
73
|
+
activeStep: AlignmentStepId;
|
|
74
|
+
steps: AlignmentStepState[];
|
|
75
|
+
/** Cumulative feedback rounds across all steps. */
|
|
76
|
+
roundCount: number;
|
|
77
|
+
/**
|
|
78
|
+
* Additions submitted from the page but not yet structured into blocks by
|
|
79
|
+
* the model — the server-side truth of gate condition three (terminal
|
|
80
|
+
* audit D7). Cleared by the next alignment_set on the active step.
|
|
81
|
+
*/
|
|
82
|
+
pendingFeedback?: {
|
|
83
|
+
round: number;
|
|
84
|
+
additions: string[];
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Historical version snapshots (走查 2026-10-03): the active-step block
|
|
88
|
+
* set archived before each revisoning `alignment_set`. Append-only with
|
|
89
|
+
* bounded rolling; the page renders them read-only — only the latest
|
|
90
|
+
* version accepts approvals/comments/answers.
|
|
91
|
+
*/
|
|
92
|
+
versions?: AlignmentVersionSnapshot[];
|
|
93
|
+
}
|
|
94
|
+
export interface AlignmentState {
|
|
95
|
+
alignments: AlignmentRecord[];
|
|
96
|
+
}
|
|
97
|
+
export type AlignmentTransitionEvent = "round.submitted" | "round.delivered" | "step.confirmed" | "step.retreated" | "design.locked" | "version.rolled-back" | "alignment.cancelled" | "alignment.completed";
|
|
98
|
+
/** Append-only audit entry: one per user round, confirmation, retreat, cancel, completion. */
|
|
99
|
+
export interface AlignmentTransitionEntry {
|
|
100
|
+
alignmentId: string;
|
|
101
|
+
step?: AlignmentStepId;
|
|
102
|
+
event: AlignmentTransitionEvent;
|
|
103
|
+
round?: number;
|
|
104
|
+
reason: {
|
|
105
|
+
code: string;
|
|
106
|
+
detail?: string;
|
|
107
|
+
};
|
|
108
|
+
/** Anchors the adjacent alignment.state snapshot. */
|
|
109
|
+
revision: number;
|
|
110
|
+
}
|
|
111
|
+
/** A user comment submitted from the page (§5 round flow 3). */
|
|
112
|
+
export interface RoundPostComment {
|
|
113
|
+
blockId: string;
|
|
114
|
+
quote: string;
|
|
115
|
+
note: string;
|
|
116
|
+
escalate?: boolean;
|
|
117
|
+
}
|
|
118
|
+
/** A reply to an assistant comment (answer/dismiss are mutually exclusive). */
|
|
119
|
+
export interface RoundPostCommentReply {
|
|
120
|
+
commentId: string;
|
|
121
|
+
blockId: string;
|
|
122
|
+
answer?: string;
|
|
123
|
+
dismiss?: boolean;
|
|
124
|
+
}
|
|
125
|
+
/** An answer to a missing-block fill-in. */
|
|
126
|
+
export interface RoundPostAnswer {
|
|
127
|
+
blockId: string;
|
|
128
|
+
value: string;
|
|
129
|
+
}
|
|
130
|
+
/** POST /round body (§5 round flow 3; baseRevision is the anti-staleness gate, terminal audit D6). */
|
|
131
|
+
export interface AlignmentRoundPost {
|
|
132
|
+
baseRevision: number;
|
|
133
|
+
/**
|
|
134
|
+
* Page-driven action (走查 2026-10-03): default `feedback`; `retreat`
|
|
135
|
+
* rolls the task back to an earlier locked step (`targetStep`, downstream
|
|
136
|
+
* steps are discarded); `rollback` restores an archived version of the
|
|
137
|
+
* active step (`targetRevision`, step-scoped, comments survive).
|
|
138
|
+
*/
|
|
139
|
+
action?: "feedback" | "retreat" | "rollback";
|
|
140
|
+
targetStep?: AlignmentStepId;
|
|
141
|
+
targetRevision?: number;
|
|
142
|
+
/** Full approved-block id set of the active step (server mirrors the page). */
|
|
143
|
+
approvals: string[];
|
|
144
|
+
comments: RoundPostComment[];
|
|
145
|
+
commentReplies: RoundPostCommentReply[];
|
|
146
|
+
answers: RoundPostAnswer[];
|
|
147
|
+
additions: string[];
|
|
148
|
+
stepConfirmed: boolean;
|
|
149
|
+
}
|
|
150
|
+
/** Structured feedback injected into the session after a user round (§5 round flow 3). */
|
|
151
|
+
export interface AlignmentRoundResult {
|
|
152
|
+
alignmentId: string;
|
|
153
|
+
round: number;
|
|
154
|
+
step: AlignmentStepId;
|
|
155
|
+
revision: number;
|
|
156
|
+
approvals: string[];
|
|
157
|
+
comments: RoundPostComment[];
|
|
158
|
+
commentReplies: RoundPostCommentReply[];
|
|
159
|
+
answers: RoundPostAnswer[];
|
|
160
|
+
additions: string[];
|
|
161
|
+
stepConfirmed: boolean;
|
|
162
|
+
/** Present when the whole alignment completed with this round (design step confirmed). */
|
|
163
|
+
finalBaseline?: {
|
|
164
|
+
step: AlignmentStepId;
|
|
165
|
+
kind: AlignmentBlockKind;
|
|
166
|
+
id: string;
|
|
167
|
+
line: string;
|
|
168
|
+
}[];
|
|
169
|
+
baselineFilePath?: string;
|
|
170
|
+
}
|
|
171
|
+
/** Why a block still needs attention — drives the page spotlight and `/alignment why`. */
|
|
172
|
+
export interface PendingItem {
|
|
173
|
+
blockId: string;
|
|
174
|
+
reason: "unapproved" | "missing-empty" | "assistant-question-open";
|
|
175
|
+
}
|
|
176
|
+
/** Gate facts (总则 2): three server-checkable conditions plus pending items. */
|
|
177
|
+
export interface GateSnapshot {
|
|
178
|
+
unapproved: number;
|
|
179
|
+
openComments: number;
|
|
180
|
+
pendingAdditions: number;
|
|
181
|
+
allGreen: boolean;
|
|
182
|
+
pending: PendingItem[];
|
|
183
|
+
}
|
|
184
|
+
/** Error codes thrown by the engine; index.ts lets them surface as tool errors verbatim. */
|
|
185
|
+
export type AlignmentErrorCode = "invalid-id" | "active-alignment-exists" | "not-found" | "terminal-state" | "stale-revision" | "stale-state" | "step-not-writable" | "step-not-unlocked" | "block-has-open-comments" | "approved-blocks-conflict" | "comment-ownership" | "comment-quote-mismatch" | "gate-not-satisfied" | "confirmed-with-feedback" | "invalid-block" | "invalid-comment" | "invalid-input" | "retreat-target-invalid";
|
|
186
|
+
export declare class AlignmentError extends Error {
|
|
187
|
+
readonly code: AlignmentErrorCode;
|
|
188
|
+
constructor(code: AlignmentErrorCode, message: string);
|
|
189
|
+
}
|
|
190
|
+
/** Hard bounds (设计 §4 有界性): alignment state enters session entries, keep it bounded. */
|
|
191
|
+
/** Archived pre-revision step block set; the page views these read-only. */
|
|
192
|
+
export interface AlignmentVersionSnapshot {
|
|
193
|
+
/** Revision at archive time — the version's anchor. */
|
|
194
|
+
revision: number;
|
|
195
|
+
/** Round at archive time. */
|
|
196
|
+
round: number;
|
|
197
|
+
/** Which step's block set was archived. */
|
|
198
|
+
step: AlignmentStepId;
|
|
199
|
+
/** Full archived block set (rendered read-only). */
|
|
200
|
+
blocks: AlignmentBlock[];
|
|
201
|
+
/** Set when the block set exceeded the per-snapshot size budget. */
|
|
202
|
+
truncated?: true;
|
|
203
|
+
}
|
|
204
|
+
export declare const ALIGNMENT_LIMITS: {
|
|
205
|
+
readonly contentMaxLength: 8192;
|
|
206
|
+
readonly rationaleMaxLength: 2048;
|
|
207
|
+
readonly notesPerBlockMax: 50;
|
|
208
|
+
readonly noteMaxLength: 2048;
|
|
209
|
+
readonly blocksPerStepMax: 300;
|
|
210
|
+
/** Rolling retention for version snapshots (走查 2026-10-03). */
|
|
211
|
+
readonly versionsMax: 10;
|
|
212
|
+
/** Per-snapshot serialization budget; over this only the anchor is kept. */
|
|
213
|
+
readonly versionsSnapshotMaxBytes: 262144;
|
|
214
|
+
readonly additionMaxLength: 4096;
|
|
215
|
+
readonly answerMaxLength: 4096;
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kDAAkD;AAClD,eAAO,MAAM,UAAU,4CAA6C,CAAC;AAErE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE3F,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,8BAA8B,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC;AAEvF,uFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,sBAAsB,CAAC;IAC/B,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,yFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,UAAU,CAAC,EAAE;QACZ,IAAI,EAAE,8BAA8B,CAAC;QACrC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC;IAC/B,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yHAAuH;IACvH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,qGAAqG;IACrG,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IAClD,qFAAqF;IACrF,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,MAAM,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,CAAC;IAC9D;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,UAAU,EAAE,eAAe,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACzD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,wBAAwB,GACjC,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,CAAC;AAEzB,8FAA8F;AAC9F,MAAM,WAAW,wBAAwB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,wBAAwB,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,iEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,uGAAsG;AACtG,MAAM,WAAW,kBAAkB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;IAC7C,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,cAAc,EAAE,qBAAqB,EAAE,CAAC;IACxC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,2FAA0F;AAC1F,MAAM,WAAW,oBAAoB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,cAAc,EAAE,qBAAqB,EAAE,CAAC;IACxC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,0FAA0F;IAC1F,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,IAAI,EAAE,kBAAkB,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,4FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,GAAG,eAAe,GAAG,yBAAyB,CAAC;CACnE;AAED,mFAA+E;AAC/E,MAAM,WAAW,YAAY;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,4FAA4F;AAC5F,MAAM,MAAM,kBAAkB,GAC3B,YAAY,GACZ,yBAAyB,GACzB,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,mBAAmB,GACnB,mBAAmB,GACnB,yBAAyB,GACzB,0BAA0B,GAC1B,mBAAmB,GACnB,wBAAwB,GACxB,oBAAoB,GACpB,yBAAyB,GACzB,eAAe,GACf,iBAAiB,GACjB,eAAe,GACf,wBAAwB,CAAC;AAE5B,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAElC,YAAY,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAIpD;CACD;AAED,mGAAwF;AACxF,4EAA4E;AAC5E,MAAM,WAAW,wBAAwB;IACxC,yDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,EAAE,eAAe,CAAC;IACtB,oDAAoD;IACpD,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,oEAAoE;IACpE,SAAS,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,gBAAgB;;;;;;IAM5B,mEAA+D;;IAE/D,4EAA4E;;;;CAInE,CAAC","sourcesContent":["/**\n * Data model of the alignment capability plugin\n * (`docs/design/需求对齐插件设计.md` §4). This module declares shapes only —\n * every conversion rule lives in `engine.ts` and every host-facing side effect\n * (entries, events, injection, HTTP) lives in `index.ts` / `http-server.ts`.\n */\n\n/** The three funnel steps, in canonical order. */\nexport const STEP_ORDER = [\"objective\", \"detail\", \"design\"] as const;\n\nexport type AlignmentStepId = (typeof STEP_ORDER)[number];\n\nexport type AlignmentBlockKind = \"fact\" | \"assumption\" | \"decision\" | \"missing\" | \"visual\";\n\nexport type AlignmentCommentAuthor = \"user\" | \"assistant\";\n\nexport type AlignmentCommentResolutionKind = \"answered\" | \"dismissed\" | \"incorporated\";\n\n/** Bidirectional block annotation (设计 §4: author distinguishes the direction). */\nexport interface AlignmentComment {\n\tid: string; // unique within the owning block\n\tauthor: AlignmentCommentAuthor;\n\t/** Exact in-block quote; required for user comments, optional for assistant ones. */\n\tquote?: string;\n\tnote: string;\n\t/** User flag: the objection targets an earlier (upstream) step — retreat candidate. */\n\tescalate?: boolean;\n\tstatus: \"open\" | \"resolved\";\n\tresolution?: {\n\t\tkind: AlignmentCommentResolutionKind;\n\t\ttext?: string;\n\t\tround?: number;\n\t};\n\tcreatedAtRound: number;\n}\n\nexport interface AlignmentBlock {\n\tid: string; // stable within the step\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\tstatus: \"pending\" | \"approved\";\n\t/** Engine-computed fingerprint (kind+content+rationale+impact); cross-round approval inheritance key. */\n\tcontentHash: string;\n\t/** Round in which the content last changed; drives the \"modified this round\" badge. */\n\tmodifiedInRound?: number;\n\t/** Stored fill-in for a missing block — the user's own words, kept verbatim until the model restructures the block. */\n\tdraftAnswer?: string;\n\tcomments: AlignmentComment[];\n}\n\nexport interface AlignmentStepState {\n\tstep: AlignmentStepId;\n\t/** pending = not unlocked yet (terminal-audit D2); reset = retreated into, confirmations cleared. */\n\tstatus: \"pending\" | \"locked\" | \"active\" | \"reset\";\n\t/** Round count at confirmation time (entry-sequence-like anchor, not wall clock). */\n\tbaselineConfirmedAtRound?: number;\n\tblocks: AlignmentBlock[];\n}\n\nexport interface AlignmentRecord {\n\tid: string;\n\ttitle: string;\n\t/** Soft reference to goal.state (advisory, #10): dangling only warns. */\n\tgoalId?: string;\n\t/**\n\t * Created-as-aligning (no draft state, terminal audit D3). `finalizing`\n\t * (走查 2026-10-03) = all three steps locked, waiting for the final\n\t * review tab confirmation — only that confirmation completes the\n\t * alignment and writes the baseline (方案确认 ≠ 终版确认).\n\t */\n\tstatus: \"aligning\" | \"finalizing\" | \"completed\" | \"cancelled\";\n\t/**\n\t * Monotonic for alignment_set callers; engine-triggered conversions\n\t * (freeze/unlock/retreat/complete) auto-increment it too (workflow\n\t * precedent, terminal audit D4). Carried on round results so the model\n\t * always knows the next acceptable revision.\n\t */\n\trevision: number;\n\t/** Points at the single step with status \"active\"; pending/reset/locked steps are not writable. */\n\tactiveStep: AlignmentStepId;\n\tsteps: AlignmentStepState[]; // fixed three elements in STEP_ORDER\n\t/** Cumulative feedback rounds across all steps. */\n\troundCount: number;\n\t/**\n\t * Additions submitted from the page but not yet structured into blocks by\n\t * the model — the server-side truth of gate condition three (terminal\n\t * audit D7). Cleared by the next alignment_set on the active step.\n\t */\n\tpendingFeedback?: { round: number; additions: string[] };\n\t/**\n\t * Historical version snapshots (走查 2026-10-03): the active-step block\n\t * set archived before each revisoning `alignment_set`. Append-only with\n\t * bounded rolling; the page renders them read-only — only the latest\n\t * version accepts approvals/comments/answers.\n\t */\n\tversions?: AlignmentVersionSnapshot[];\n}\n\nexport interface AlignmentState {\n\talignments: AlignmentRecord[];\n}\n\nexport type AlignmentTransitionEvent =\n\t| \"round.submitted\"\n\t| \"round.delivered\"\n\t| \"step.confirmed\"\n\t| \"step.retreated\"\n\t| \"design.locked\" // 方案锁定、进入终审总览(走查 2026-10-03)\n\t| \"version.rolled-back\" // 步内版本回退(走查 2026-10-03)\n\t| \"alignment.cancelled\"\n\t| \"alignment.completed\";\n\n/** Append-only audit entry: one per user round, confirmation, retreat, cancel, completion. */\nexport interface AlignmentTransitionEntry {\n\talignmentId: string;\n\tstep?: AlignmentStepId; // alignment-level transitions carry no step\n\tevent: AlignmentTransitionEvent;\n\tround?: number;\n\treason: { code: string; detail?: string };\n\t/** Anchors the adjacent alignment.state snapshot. */\n\trevision: number;\n}\n\n/** A user comment submitted from the page (§5 round flow 3). */\nexport interface RoundPostComment {\n\tblockId: string;\n\tquote: string;\n\tnote: string;\n\tescalate?: boolean;\n}\n\n/** A reply to an assistant comment (answer/dismiss are mutually exclusive). */\nexport interface RoundPostCommentReply {\n\tcommentId: string;\n\tblockId: string;\n\tanswer?: string;\n\tdismiss?: boolean;\n}\n\n/** An answer to a missing-block fill-in. */\nexport interface RoundPostAnswer {\n\tblockId: string;\n\tvalue: string;\n}\n\n/** POST /round body (§5 round flow 3; baseRevision is the anti-staleness gate, terminal audit D6). */\nexport interface AlignmentRoundPost {\n\tbaseRevision: number;\n\t/**\n\t * Page-driven action (走查 2026-10-03): default `feedback`; `retreat`\n\t * rolls the task back to an earlier locked step (`targetStep`, downstream\n\t * steps are discarded); `rollback` restores an archived version of the\n\t * active step (`targetRevision`, step-scoped, comments survive).\n\t */\n\taction?: \"feedback\" | \"retreat\" | \"rollback\";\n\ttargetStep?: AlignmentStepId;\n\ttargetRevision?: number;\n\t/** Full approved-block id set of the active step (server mirrors the page). */\n\tapprovals: string[];\n\tcomments: RoundPostComment[];\n\tcommentReplies: RoundPostCommentReply[];\n\tanswers: RoundPostAnswer[];\n\tadditions: string[];\n\tstepConfirmed: boolean;\n}\n\n/** Structured feedback injected into the session after a user round (§5 round flow 3). */\nexport interface AlignmentRoundResult {\n\talignmentId: string;\n\tround: number;\n\tstep: AlignmentStepId;\n\trevision: number;\n\tapprovals: string[];\n\tcomments: RoundPostComment[];\n\tcommentReplies: RoundPostCommentReply[];\n\tanswers: RoundPostAnswer[];\n\tadditions: string[];\n\tstepConfirmed: boolean;\n\t/** Present when the whole alignment completed with this round (design step confirmed). */\n\tfinalBaseline?: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[];\n\tbaselineFilePath?: string;\n}\n\n/** Why a block still needs attention — drives the page spotlight and `/alignment why`. */\nexport interface PendingItem {\n\tblockId: string;\n\treason: \"unapproved\" | \"missing-empty\" | \"assistant-question-open\";\n}\n\n/** Gate facts (总则 2): three server-checkable conditions plus pending items. */\nexport interface GateSnapshot {\n\tunapproved: number;\n\topenComments: number;\n\tpendingAdditions: number;\n\tallGreen: boolean;\n\tpending: PendingItem[];\n}\n\n/** Error codes thrown by the engine; index.ts lets them surface as tool errors verbatim. */\nexport type AlignmentErrorCode =\n\t| \"invalid-id\"\n\t| \"active-alignment-exists\"\n\t| \"not-found\"\n\t| \"terminal-state\"\n\t| \"stale-revision\"\n\t| \"stale-state\"\n\t| \"step-not-writable\"\n\t| \"step-not-unlocked\"\n\t| \"block-has-open-comments\"\n\t| \"approved-blocks-conflict\" // v3.7:改写已批准块指纹且未携带 reviseApproved(§5 总则 3)\n\t| \"comment-ownership\"\n\t| \"comment-quote-mismatch\"\n\t| \"gate-not-satisfied\"\n\t| \"confirmed-with-feedback\"\n\t| \"invalid-block\"\n\t| \"invalid-comment\"\n\t| \"invalid-input\"\n\t| \"retreat-target-invalid\";\n\nexport class AlignmentError extends Error {\n\treadonly code: AlignmentErrorCode;\n\n\tconstructor(code: AlignmentErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"AlignmentError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Hard bounds (设计 §4 有界性): alignment state enters session entries, keep it bounded. */\n/** Archived pre-revision step block set; the page views these read-only. */\nexport interface AlignmentVersionSnapshot {\n\t/** Revision at archive time — the version's anchor. */\n\trevision: number;\n\t/** Round at archive time. */\n\tround: number;\n\t/** Which step's block set was archived. */\n\tstep: AlignmentStepId;\n\t/** Full archived block set (rendered read-only). */\n\tblocks: AlignmentBlock[];\n\t/** Set when the block set exceeded the per-snapshot size budget. */\n\ttruncated?: true;\n}\n\nexport const ALIGNMENT_LIMITS = {\n\tcontentMaxLength: 8192,\n\trationaleMaxLength: 2048,\n\tnotesPerBlockMax: 50,\n\tnoteMaxLength: 2048,\n\tblocksPerStepMax: 300,\n\t/** Rolling retention for version snapshots (走查 2026-10-03). */\n\tversionsMax: 10,\n\t/** Per-snapshot serialization budget; over this only the anchor is kept. */\n\tversionsSnapshotMaxBytes: 262_144,\n\tadditionMaxLength: 4096,\n\tanswerMaxLength: 4096,\n} as const;\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data model of the alignment capability plugin
|
|
3
|
+
* (`docs/design/需求对齐插件设计.md` §4). This module declares shapes only —
|
|
4
|
+
* every conversion rule lives in `engine.ts` and every host-facing side effect
|
|
5
|
+
* (entries, events, injection, HTTP) lives in `index.ts` / `http-server.ts`.
|
|
6
|
+
*/
|
|
7
|
+
/** The three funnel steps, in canonical order. */
|
|
8
|
+
export const STEP_ORDER = ["objective", "detail", "design"];
|
|
9
|
+
export class AlignmentError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "AlignmentError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const ALIGNMENT_LIMITS = {
|
|
18
|
+
contentMaxLength: 8192,
|
|
19
|
+
rationaleMaxLength: 2048,
|
|
20
|
+
notesPerBlockMax: 50,
|
|
21
|
+
noteMaxLength: 2048,
|
|
22
|
+
blocksPerStepMax: 300,
|
|
23
|
+
/** Rolling retention for version snapshots (走查 2026-10-03). */
|
|
24
|
+
versionsMax: 10,
|
|
25
|
+
/** Per-snapshot serialization budget; over this only the anchor is kept. */
|
|
26
|
+
versionsSnapshotMaxBytes: 262_144,
|
|
27
|
+
additionMaxLength: 4096,
|
|
28
|
+
answerMaxLength: 4096,
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kDAAkD;AAClD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAqNrE,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC/B,IAAI,CAAqB;IAElC,YAAY,IAAwB,EAAE,OAAe,EAAE;QACtD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;CACD;AAiBD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC/B,gBAAgB,EAAE,IAAI;IACtB,kBAAkB,EAAE,IAAI;IACxB,gBAAgB,EAAE,EAAE;IACpB,aAAa,EAAE,IAAI;IACnB,gBAAgB,EAAE,GAAG;IACrB,mEAA+D;IAC/D,WAAW,EAAE,EAAE;IACf,4EAA4E;IAC5E,wBAAwB,EAAE,OAAO;IACjC,iBAAiB,EAAE,IAAI;IACvB,eAAe,EAAE,IAAI;CACZ,CAAC","sourcesContent":["/**\n * Data model of the alignment capability plugin\n * (`docs/design/需求对齐插件设计.md` §4). This module declares shapes only —\n * every conversion rule lives in `engine.ts` and every host-facing side effect\n * (entries, events, injection, HTTP) lives in `index.ts` / `http-server.ts`.\n */\n\n/** The three funnel steps, in canonical order. */\nexport const STEP_ORDER = [\"objective\", \"detail\", \"design\"] as const;\n\nexport type AlignmentStepId = (typeof STEP_ORDER)[number];\n\nexport type AlignmentBlockKind = \"fact\" | \"assumption\" | \"decision\" | \"missing\" | \"visual\";\n\nexport type AlignmentCommentAuthor = \"user\" | \"assistant\";\n\nexport type AlignmentCommentResolutionKind = \"answered\" | \"dismissed\" | \"incorporated\";\n\n/** Bidirectional block annotation (设计 §4: author distinguishes the direction). */\nexport interface AlignmentComment {\n\tid: string; // unique within the owning block\n\tauthor: AlignmentCommentAuthor;\n\t/** Exact in-block quote; required for user comments, optional for assistant ones. */\n\tquote?: string;\n\tnote: string;\n\t/** User flag: the objection targets an earlier (upstream) step — retreat candidate. */\n\tescalate?: boolean;\n\tstatus: \"open\" | \"resolved\";\n\tresolution?: {\n\t\tkind: AlignmentCommentResolutionKind;\n\t\ttext?: string;\n\t\tround?: number;\n\t};\n\tcreatedAtRound: number;\n}\n\nexport interface AlignmentBlock {\n\tid: string; // stable within the step\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\tstatus: \"pending\" | \"approved\";\n\t/** Engine-computed fingerprint (kind+content+rationale+impact); cross-round approval inheritance key. */\n\tcontentHash: string;\n\t/** Round in which the content last changed; drives the \"modified this round\" badge. */\n\tmodifiedInRound?: number;\n\t/** Stored fill-in for a missing block — the user's own words, kept verbatim until the model restructures the block. */\n\tdraftAnswer?: string;\n\tcomments: AlignmentComment[];\n}\n\nexport interface AlignmentStepState {\n\tstep: AlignmentStepId;\n\t/** pending = not unlocked yet (terminal-audit D2); reset = retreated into, confirmations cleared. */\n\tstatus: \"pending\" | \"locked\" | \"active\" | \"reset\";\n\t/** Round count at confirmation time (entry-sequence-like anchor, not wall clock). */\n\tbaselineConfirmedAtRound?: number;\n\tblocks: AlignmentBlock[];\n}\n\nexport interface AlignmentRecord {\n\tid: string;\n\ttitle: string;\n\t/** Soft reference to goal.state (advisory, #10): dangling only warns. */\n\tgoalId?: string;\n\t/**\n\t * Created-as-aligning (no draft state, terminal audit D3). `finalizing`\n\t * (走查 2026-10-03) = all three steps locked, waiting for the final\n\t * review tab confirmation — only that confirmation completes the\n\t * alignment and writes the baseline (方案确认 ≠ 终版确认).\n\t */\n\tstatus: \"aligning\" | \"finalizing\" | \"completed\" | \"cancelled\";\n\t/**\n\t * Monotonic for alignment_set callers; engine-triggered conversions\n\t * (freeze/unlock/retreat/complete) auto-increment it too (workflow\n\t * precedent, terminal audit D4). Carried on round results so the model\n\t * always knows the next acceptable revision.\n\t */\n\trevision: number;\n\t/** Points at the single step with status \"active\"; pending/reset/locked steps are not writable. */\n\tactiveStep: AlignmentStepId;\n\tsteps: AlignmentStepState[]; // fixed three elements in STEP_ORDER\n\t/** Cumulative feedback rounds across all steps. */\n\troundCount: number;\n\t/**\n\t * Additions submitted from the page but not yet structured into blocks by\n\t * the model — the server-side truth of gate condition three (terminal\n\t * audit D7). Cleared by the next alignment_set on the active step.\n\t */\n\tpendingFeedback?: { round: number; additions: string[] };\n\t/**\n\t * Historical version snapshots (走查 2026-10-03): the active-step block\n\t * set archived before each revisoning `alignment_set`. Append-only with\n\t * bounded rolling; the page renders them read-only — only the latest\n\t * version accepts approvals/comments/answers.\n\t */\n\tversions?: AlignmentVersionSnapshot[];\n}\n\nexport interface AlignmentState {\n\talignments: AlignmentRecord[];\n}\n\nexport type AlignmentTransitionEvent =\n\t| \"round.submitted\"\n\t| \"round.delivered\"\n\t| \"step.confirmed\"\n\t| \"step.retreated\"\n\t| \"design.locked\" // 方案锁定、进入终审总览(走查 2026-10-03)\n\t| \"version.rolled-back\" // 步内版本回退(走查 2026-10-03)\n\t| \"alignment.cancelled\"\n\t| \"alignment.completed\";\n\n/** Append-only audit entry: one per user round, confirmation, retreat, cancel, completion. */\nexport interface AlignmentTransitionEntry {\n\talignmentId: string;\n\tstep?: AlignmentStepId; // alignment-level transitions carry no step\n\tevent: AlignmentTransitionEvent;\n\tround?: number;\n\treason: { code: string; detail?: string };\n\t/** Anchors the adjacent alignment.state snapshot. */\n\trevision: number;\n}\n\n/** A user comment submitted from the page (§5 round flow 3). */\nexport interface RoundPostComment {\n\tblockId: string;\n\tquote: string;\n\tnote: string;\n\tescalate?: boolean;\n}\n\n/** A reply to an assistant comment (answer/dismiss are mutually exclusive). */\nexport interface RoundPostCommentReply {\n\tcommentId: string;\n\tblockId: string;\n\tanswer?: string;\n\tdismiss?: boolean;\n}\n\n/** An answer to a missing-block fill-in. */\nexport interface RoundPostAnswer {\n\tblockId: string;\n\tvalue: string;\n}\n\n/** POST /round body (§5 round flow 3; baseRevision is the anti-staleness gate, terminal audit D6). */\nexport interface AlignmentRoundPost {\n\tbaseRevision: number;\n\t/**\n\t * Page-driven action (走查 2026-10-03): default `feedback`; `retreat`\n\t * rolls the task back to an earlier locked step (`targetStep`, downstream\n\t * steps are discarded); `rollback` restores an archived version of the\n\t * active step (`targetRevision`, step-scoped, comments survive).\n\t */\n\taction?: \"feedback\" | \"retreat\" | \"rollback\";\n\ttargetStep?: AlignmentStepId;\n\ttargetRevision?: number;\n\t/** Full approved-block id set of the active step (server mirrors the page). */\n\tapprovals: string[];\n\tcomments: RoundPostComment[];\n\tcommentReplies: RoundPostCommentReply[];\n\tanswers: RoundPostAnswer[];\n\tadditions: string[];\n\tstepConfirmed: boolean;\n}\n\n/** Structured feedback injected into the session after a user round (§5 round flow 3). */\nexport interface AlignmentRoundResult {\n\talignmentId: string;\n\tround: number;\n\tstep: AlignmentStepId;\n\trevision: number;\n\tapprovals: string[];\n\tcomments: RoundPostComment[];\n\tcommentReplies: RoundPostCommentReply[];\n\tanswers: RoundPostAnswer[];\n\tadditions: string[];\n\tstepConfirmed: boolean;\n\t/** Present when the whole alignment completed with this round (design step confirmed). */\n\tfinalBaseline?: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[];\n\tbaselineFilePath?: string;\n}\n\n/** Why a block still needs attention — drives the page spotlight and `/alignment why`. */\nexport interface PendingItem {\n\tblockId: string;\n\treason: \"unapproved\" | \"missing-empty\" | \"assistant-question-open\";\n}\n\n/** Gate facts (总则 2): three server-checkable conditions plus pending items. */\nexport interface GateSnapshot {\n\tunapproved: number;\n\topenComments: number;\n\tpendingAdditions: number;\n\tallGreen: boolean;\n\tpending: PendingItem[];\n}\n\n/** Error codes thrown by the engine; index.ts lets them surface as tool errors verbatim. */\nexport type AlignmentErrorCode =\n\t| \"invalid-id\"\n\t| \"active-alignment-exists\"\n\t| \"not-found\"\n\t| \"terminal-state\"\n\t| \"stale-revision\"\n\t| \"stale-state\"\n\t| \"step-not-writable\"\n\t| \"step-not-unlocked\"\n\t| \"block-has-open-comments\"\n\t| \"approved-blocks-conflict\" // v3.7:改写已批准块指纹且未携带 reviseApproved(§5 总则 3)\n\t| \"comment-ownership\"\n\t| \"comment-quote-mismatch\"\n\t| \"gate-not-satisfied\"\n\t| \"confirmed-with-feedback\"\n\t| \"invalid-block\"\n\t| \"invalid-comment\"\n\t| \"invalid-input\"\n\t| \"retreat-target-invalid\";\n\nexport class AlignmentError extends Error {\n\treadonly code: AlignmentErrorCode;\n\n\tconstructor(code: AlignmentErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"AlignmentError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Hard bounds (设计 §4 有界性): alignment state enters session entries, keep it bounded. */\n/** Archived pre-revision step block set; the page views these read-only. */\nexport interface AlignmentVersionSnapshot {\n\t/** Revision at archive time — the version's anchor. */\n\trevision: number;\n\t/** Round at archive time. */\n\tround: number;\n\t/** Which step's block set was archived. */\n\tstep: AlignmentStepId;\n\t/** Full archived block set (rendered read-only). */\n\tblocks: AlignmentBlock[];\n\t/** Set when the block set exceeded the per-snapshot size budget. */\n\ttruncated?: true;\n}\n\nexport const ALIGNMENT_LIMITS = {\n\tcontentMaxLength: 8192,\n\trationaleMaxLength: 2048,\n\tnotesPerBlockMax: 50,\n\tnoteMaxLength: 2048,\n\tblocksPerStepMax: 300,\n\t/** Rolling retention for version snapshots (走查 2026-10-03). */\n\tversionsMax: 10,\n\t/** Per-snapshot serialization budget; over this only the anchor is kept. */\n\tversionsSnapshotMaxBytes: 262_144,\n\tadditionMaxLength: 4096,\n\tanswerMaxLength: 4096,\n} as const;\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# browser-playwright 能力手册(AI 用)
|
|
2
|
+
|
|
3
|
+
内置 Playwright 浏览器操作:导航、DOM/a11y 快照、点击/填写/拖拽/滚动、截图、console/网络/性能观测、存储、多标签。headless Chromium,跨平台。
|
|
4
|
+
|
|
5
|
+
## 首次使用(最重要)
|
|
6
|
+
|
|
7
|
+
Playwright **不随宿主预装**。第一次调用任何 browser_* 工具会返回结构化安装指引。两条路:
|
|
8
|
+
|
|
9
|
+
1. **推荐**:直接调 `browser_setup`,自动安装 Playwright 与 Chromium(可能耗时数分钟,Chromium 约 200MB + headless shell 约 100MB)。
|
|
10
|
+
2. 用户自己执行指引给出的两条命令(npm install + playwright install chromium)。
|
|
11
|
+
|
|
12
|
+
安装完成后重试即可,无需重启会话。安装进度/失败原因都在指引与错误消息里;npm 慢网时离线 tarball 安装也受支持(指引含精确命令)。
|
|
13
|
+
|
|
14
|
+
## 工具速查
|
|
15
|
+
|
|
16
|
+
| 工具 | 用途 | 硬约束 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| browser_navigate | 打开 URL | waitUntil 默认 load |
|
|
19
|
+
| browser_snapshot | DOM/a11y/截图证据 | 只读 |
|
|
20
|
+
| browser_locator_action | 单元素 13 种动作(click/dblclick/rightclick/fill/type/press/hover/select/check/uncheck/clear/upload/scroll) | scroll 值格式 `down:300` |
|
|
21
|
+
| browser_assert_postcondition | 断言 url/element/text/console/network | 无副作用 |
|
|
22
|
+
| browser_collect_evidence | 证据打包(snapshot/console/network) | 只读 |
|
|
23
|
+
| browser_evaluate | JS 逃生舱 | 见下方"关键坑" |
|
|
24
|
+
| browser_console / browser_network | 环形缓冲读取(可过滤) | 只读 |
|
|
25
|
+
| browser_performance | Navigation Timing/FCP/资源/DOM 数 | 只读 |
|
|
26
|
+
| browser_wait | selector/navigation/networkidle/time | 钳制 30s |
|
|
27
|
+
| browser_drag | hover→down→步进移动→up | pointer capture 页面安全 |
|
|
28
|
+
| browser_mouse | 裸坐标 move/click/dblclick/rightclick/wheel | 坐标是视口像素 |
|
|
29
|
+
| browser_press_key | 页面级键盘(Enter/PageDown/Control+a) | — |
|
|
30
|
+
| browser_tabs | list/new/switch/close | index 从 0 |
|
|
31
|
+
| browser_storage | cookies + localStorage 读写清 | — |
|
|
32
|
+
| browser_viewport | set/get(320..7680) | — |
|
|
33
|
+
|
|
34
|
+
readonly 模式只挂观察类(snapshot/assert/collect/console/network/performance/wait)。
|
|
35
|
+
|
|
36
|
+
## 正例
|
|
37
|
+
|
|
38
|
+
```jsonc
|
|
39
|
+
// 定位器语义:kind ∈ css|id|role|text|test-id
|
|
40
|
+
{ "locator": { "kind": "css", "value": "#submit" }, "action": "click" }
|
|
41
|
+
{ "locator": { "kind": "role", "value": "button", "name": "提交" }, "action": "click" }
|
|
42
|
+
{ "locator": { "kind": "css", "value": "#name" }, "action": "fill", "value": "张三" }
|
|
43
|
+
{ "locator": { "kind": "css", "value": "#list" }, "action": "scroll", "value": "down:300" }
|
|
44
|
+
{ "until": "selector", "locator": { "kind": "css", "value": "#delayed" } }
|
|
45
|
+
{ "action": "get-local", "key": "token" }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 反例与关键坑(全部真实踩过)
|
|
49
|
+
|
|
50
|
+
1. **browser_evaluate 只接受纯表达式**:`document.title`、`1 + 1`。多语句必须 IIFE 字符串:`(() => { ...; return x; })()`。**裸箭头字符串 `() => ...` 不会被调用,返回 undefined**——真实 Playwright 的语义,不是 bug。
|
|
51
|
+
2. **裸坐标点击前先拿实时坐标**:用 evaluate 读 `getBoundingClientRect()` 中心再 `browser_mouse`,别猜固定坐标(布局会变)。
|
|
52
|
+
3. **容器滚动用 locator_action scroll**,不要用 mouse wheel 打坐标——wheel 落在鼠标悬停处,不一定滚目标容器(scroll 工具已内置先移到元素中心)。
|
|
53
|
+
4. **文件上传优先 `upload` 动作**(绝对路径);**原生文件选择对话框 Playwright 无法操作**——那是 OS 窗口,需要桌面真实输入(desktop_* 能力,见下)。
|
|
54
|
+
5. **dialog(alert/confirm)自动接受**并记入 console 环,不会挂死;别为此写等待。
|
|
55
|
+
6. headless 无桌面形态:需要真实用户手势、反自动化检测场景、Canvas/WebGL 黑盒页面,改用 **desktop_* 桌面通道**(真实鼠标键盘),两通道互补而非二选一。
|
|
56
|
+
|
|
57
|
+
## 与 desktop 能力协作
|
|
58
|
+
|
|
59
|
+
- Playwright 通道:自带内核、语义级元素操作、稳定可断言——**默认通道**。
|
|
60
|
+
- desktop 通道:真实硬件级输入(CGEvent/mouse_event)、能过 UIA/AX 看到浏览器窗口与页面控件、能操作原生对话框——**Playwright 覆盖不了时的第二通道**。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.browser-playwright",
|
|
4
|
+
"pluginVersion": "0.88.6",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "browser",
|
|
10
|
+
"version": 1
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"platform": {
|
|
14
|
+
"os": [
|
|
15
|
+
"windows",
|
|
16
|
+
"macos",
|
|
17
|
+
"linux"
|
|
18
|
+
],
|
|
19
|
+
"runtime": [
|
|
20
|
+
{
|
|
21
|
+
"name": "node"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser capability factory: registers the seventeen browser tools on a
|
|
3
|
+
* `CapabilityAPI` and shapes every result as `PluginResultV1`.
|
|
4
|
+
*
|
|
5
|
+
* Tool surface: `browser_navigate`, `browser_snapshot`, `browser_locator_action`
|
|
6
|
+
* (click/dblclick/rightclick/fill/type/press/hover/select/check/uncheck/clear/
|
|
7
|
+
* upload/scroll), `browser_assert_postcondition`, `browser_collect_evidence`,
|
|
8
|
+
* the self-installing `browser_setup`, plus the prewritten operation tools
|
|
9
|
+
* `browser_evaluate` (escape hatch), `browser_console`, `browser_network`,
|
|
10
|
+
* `browser_performance`, `browser_wait`, `browser_drag`, `browser_mouse`,
|
|
11
|
+
* `browser_press_key`, `browser_tabs`, `browser_storage`, and
|
|
12
|
+
* `browser_viewport`. The readonly mode registers only the observation-only
|
|
13
|
+
* tools (`snapshot`, `assert_postcondition`, `collect_evidence`, `console`,
|
|
14
|
+
* `network`, `performance`, `wait`); everything that changes browser or disk
|
|
15
|
+
* state never registers there.
|
|
16
|
+
*
|
|
17
|
+
* All browser state lives behind the injected `BrowserRuntimeV1`; the factory
|
|
18
|
+
* itself has no Playwright knowledge. Error messages are sanitized like the
|
|
19
|
+
* pre-split capability: URLs collapse to origin + pathname (<=512) and the
|
|
20
|
+
* total message stays within 2048 chars.
|
|
21
|
+
*/
|
|
22
|
+
import { type CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
23
|
+
import type { BrowserRuntimeV1 } from "./types.ts";
|
|
24
|
+
export declare function createBrowserCapabilityFactoryV1(runtime: BrowserRuntimeV1): CapabilityPluginSyncFactory;
|
|
25
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAGN,KAAK,2BAA2B,EAGhC,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAUX,gBAAgB,EAMhB,MAAM,YAAY,CAAC;AAwLpB,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,gBAAgB,GAAG,2BAA2B,CAohBvG","sourcesContent":["/**\n * Browser capability factory: registers the seventeen browser tools on a\n * `CapabilityAPI` and shapes every result as `PluginResultV1`.\n *\n * Tool surface: `browser_navigate`, `browser_snapshot`, `browser_locator_action`\n * (click/dblclick/rightclick/fill/type/press/hover/select/check/uncheck/clear/\n * upload/scroll), `browser_assert_postcondition`, `browser_collect_evidence`,\n * the self-installing `browser_setup`, plus the prewritten operation tools\n * `browser_evaluate` (escape hatch), `browser_console`, `browser_network`,\n * `browser_performance`, `browser_wait`, `browser_drag`, `browser_mouse`,\n * `browser_press_key`, `browser_tabs`, `browser_storage`, and\n * `browser_viewport`. The readonly mode registers only the observation-only\n * tools (`snapshot`, `assert_postcondition`, `collect_evidence`, `console`,\n * `network`, `performance`, `wait`); everything that changes browser or disk\n * state never registers there.\n *\n * All browser state lives behind the injected `BrowserRuntimeV1`; the factory\n * itself has no Playwright knowledge. Error messages are sanitized like the\n * pre-split capability: URLs collapse to origin + pathname (<=512) and the\n * total message stays within 2048 chars.\n */\nimport {\n\tbuildPluginResultV1,\n\ttype CapabilityAPI,\n\ttype CapabilityPluginSyncFactory,\n\ttype DisposableRegistration,\n\ttype PluginResultV1,\n} from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { boundedPreviewV1 } from \"./setup.ts\";\nimport type {\n\tBrowserDriverV1,\n\tBrowserEvidenceKindV1,\n\tBrowserLocatorActionInputV1,\n\tBrowserLocatorV1,\n\tBrowserMouseButtonV1,\n\tBrowserMouseInputV1,\n\tBrowserNavigateInputV1,\n\tBrowserOperationOptionsV1,\n\tBrowserPostconditionV1,\n\tBrowserRuntimeV1,\n\tBrowserSetupOutcomeV1,\n\tBrowserSnapshotInputV1,\n\tBrowserStorageInputV1,\n\tBrowserTabsInputV1,\n\tBrowserViewportInputV1,\n} from \"./types.ts\";\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Plugin tool input must be an object\");\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction toolInvocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId: typeof first === \"string\" && first.trim() !== \"\" ? first : \"profile-runtime-tool-call\",\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction nonEmptyString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") throw new Error(`${label} must be a non-empty string`);\n\treturn value;\n}\n\nfunction requiredInteger(value: unknown, label: string): number {\n\tif (typeof value !== \"number\" || !Number.isInteger(value)) throw new Error(`${label} must be an integer`);\n\treturn value;\n}\n\n/** URL-sanitized, 2048-char bounded error text (pre-split browser contract). */\nfunction browserVisibleError(value: unknown): string {\n\tconst raw = value instanceof Error && value.message.trim() !== \"\" ? value.message : \"Browser operation failed\";\n\tconst sanitized = raw.replace(/https?:\\/\\/[^\\s\"'<>]+/giu, (candidate) => {\n\t\ttry {\n\t\t\tconst parsed = new URL(candidate);\n\t\t\treturn `${parsed.origin}${parsed.pathname.slice(0, 512)}`;\n\t\t} catch {\n\t\t\treturn candidate.slice(0, 512);\n\t\t}\n\t});\n\treturn sanitized.length > 2048 ? `${sanitized.slice(0, 2047)}…` : sanitized;\n}\n\nfunction browserAbort(value: unknown): boolean {\n\tif (value instanceof Error && value.name === \"AbortError\") return true;\n\treturn typeof value === \"object\" && value !== null && (value as { code?: unknown }).code === \"ABORT_ERR\";\n}\n\nconst browserLocatorSchema = Type.Object({\n\tkind: Type.Union([\n\t\tType.Literal(\"css\"),\n\t\tType.Literal(\"id\"),\n\t\tType.Literal(\"role\"),\n\t\tType.Literal(\"text\"),\n\t\tType.Literal(\"test-id\"),\n\t]),\n\tvalue: Type.String({ minLength: 1 }),\n\tname: Type.Optional(Type.String()),\n});\n\nconst browserMouseActionSchema = Type.Union([\n\tType.Literal(\"move\"),\n\tType.Literal(\"click\"),\n\tType.Literal(\"dblclick\"),\n\tType.Literal(\"rightclick\"),\n\tType.Literal(\"wheel\"),\n]);\n\nconst browserMouseButtonSchema = Type.Union([Type.Literal(\"left\"), Type.Literal(\"right\"), Type.Literal(\"middle\")]);\n\nconst browserTabsActionSchema = Type.Union([\n\tType.Literal(\"list\"),\n\tType.Literal(\"new\"),\n\tType.Literal(\"switch\"),\n\tType.Literal(\"close\"),\n]);\n\nconst browserStorageActionSchema = Type.Union([\n\tType.Literal(\"get-cookies\"),\n\tType.Literal(\"set-cookie\"),\n\tType.Literal(\"clear-cookies\"),\n\tType.Literal(\"get-local\"),\n\tType.Literal(\"set-local\"),\n\tType.Literal(\"clear-local\"),\n]);\n\nconst browserViewportActionSchema = Type.Union([Type.Literal(\"set\"), Type.Literal(\"get\")]);\n\nconst browserWaitUntilKindSchema = Type.Union([\n\tType.Literal(\"selector\"),\n\tType.Literal(\"navigation\"),\n\tType.Literal(\"networkidle\"),\n\tType.Literal(\"time\"),\n]);\n\nconst browserConsoleLevelSchema = Type.String({\n\tpattern: \"^(error|warning|info|debug)(,(error|warning|info|debug))*$\",\n});\n\nconst browserPostconditionSchema = Type.Union([\n\tType.Object({\n\t\tkind: Type.Literal(\"url\"),\n\t\tequals: Type.String({ minLength: 1 }),\n\t\tcontains: Type.Optional(Type.String({ minLength: 1 })),\n\t}),\n\tType.Object({\n\t\tkind: Type.Literal(\"url\"),\n\t\tequals: Type.Optional(Type.String({ minLength: 1 })),\n\t\tcontains: Type.String({ minLength: 1 }),\n\t}),\n\tType.Object({\n\t\tkind: Type.Literal(\"element\"),\n\t\tlocator: browserLocatorSchema,\n\t\tstate: Type.Union([Type.Literal(\"visible\"), Type.Literal(\"hidden\"), Type.Literal(\"value\")]),\n\t\tvalue: Type.Optional(Type.String()),\n\t}),\n\tType.Object({\n\t\tkind: Type.Literal(\"text\"),\n\t\tlocator: Type.Optional(browserLocatorSchema),\n\t\tcontains: Type.String({ minLength: 1 }),\n\t}),\n\tType.Object({\n\t\tkind: Type.Literal(\"console\"),\n\t\tcontains: Type.String({ minLength: 1 }),\n\t\tlevel: Type.Optional(Type.String()),\n\t}),\n\tType.Object({\n\t\tkind: Type.Literal(\"network\"),\n\t\tcontainsUrl: Type.String({ minLength: 1 }),\n\t\tstatus: Type.Optional(Type.Integer({ minimum: 100, maximum: 599 })),\n\t}),\n]);\n\nconst browserPostconditionInputSchema = Type.Union([\n\tbrowserPostconditionSchema,\n\tType.Array(browserPostconditionSchema, { minItems: 1, maxItems: 32 }),\n]);\n\nfunction setupResult(operationId: string, outcome: BrowserSetupOutcomeV1): PluginResultV1 {\n\tif (!outcome.ok) {\n\t\treturn buildPluginResultV1({\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"failed\",\n\t\t\tsummary: \"Playwright browser dependency setup failed\",\n\t\t\tartifacts: [],\n\t\t\tevidence: [],\n\t\t\terror: {\n\t\t\t\tversion: 1,\n\t\t\t\tcode: \"browser_setup_failed\",\n\t\t\t\tmessage: boundedPreviewV1(outcome.message, 8192),\n\t\t\t},\n\t\t});\n\t}\n\treturn buildPluginResultV1({\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"succeeded\",\n\t\tsummary: outcome.alreadyInstalled\n\t\t\t? \"Playwright browser dependencies are already installed\"\n\t\t\t: \"Playwright browser dependencies installed\",\n\t\tartifacts: [],\n\t\tevidence: [\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\tevidenceId: `${operationId}-evidence-1`,\n\t\t\t\tkind: \"log\",\n\t\t\t\toperationId,\n\t\t\t\tsummary: outcome.log.trim() === \"\" ? \"(no output)\" : boundedPreviewV1(outcome.log, 4096),\n\t\t\t},\n\t\t],\n\t\tverification: {\n\t\t\tversion: 1,\n\t\t\tstatus: \"passed\",\n\t\t\tchecks: [outcome.alreadyInstalled ? \"playwright-already-installed\" : \"playwright-install-completed\"],\n\t\t},\n\t});\n}\n\nexport function createBrowserCapabilityFactoryV1(runtime: BrowserRuntimeV1): CapabilityPluginSyncFactory {\n\treturn (api: CapabilityAPI) => {\n\t\tconst readonlyMode = api.host.mode === \"readonly\";\n\t\tlet session: Promise<BrowserDriverV1> | undefined;\n\t\tconst connect = (): Promise<BrowserDriverV1> => {\n\t\t\tif (session === undefined) session = runtime.connect();\n\t\t\treturn session;\n\t\t};\n\t\tconst withSession = async (\n\t\t\toperationId: string,\n\t\t\taction: (active: BrowserDriverV1) => Promise<PluginResultV1>,\n\t\t): Promise<PluginResultV1> => {\n\t\t\tlet active: BrowserDriverV1;\n\t\t\ttry {\n\t\t\t\tactive = await connect();\n\t\t\t} catch (error) {\n\t\t\t\tsession = undefined;\n\t\t\t\treturn buildPluginResultV1({\n\t\t\t\t\tversion: 1,\n\t\t\t\t\toperationId,\n\t\t\t\t\tstatus: browserAbort(error) ? \"cancelled\" : \"failed\",\n\t\t\t\t\tsummary: \"Browser session connection failed\",\n\t\t\t\t\tartifacts: [],\n\t\t\t\t\tevidence: [],\n\t\t\t\t\terror: {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\tcode: browserAbort(error) ? \"cancelled\" : \"browser_connection_failed\",\n\t\t\t\t\t\tmessage: browserAbort(error) ? \"Browser operation was cancelled\" : browserVisibleError(error),\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await action(active);\n\t\t\t} catch (error) {\n\t\t\t\treturn buildPluginResultV1({\n\t\t\t\t\tversion: 1,\n\t\t\t\t\toperationId,\n\t\t\t\t\tstatus: browserAbort(error) ? \"cancelled\" : \"failed\",\n\t\t\t\t\tsummary: \"Browser operation failed\",\n\t\t\t\t\tartifacts: [],\n\t\t\t\t\tevidence: [],\n\t\t\t\t\terror: {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\tcode: browserAbort(error) ? \"cancelled\" : \"browser_operation_failed\",\n\t\t\t\t\t\tmessage: browserAbort(error) ? \"Browser operation was cancelled\" : browserVisibleError(error),\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst operationOptions = (operationId: string, third: unknown): BrowserOperationOptionsV1 => ({\n\t\t\toperationId,\n\t\t\t...(signalFrom(third) === undefined ? {} : { signal: signalFrom(third) }),\n\t\t});\n\t\tconst registrations: DisposableRegistration[] = [];\n\t\tif (!readonlyMode) {\n\t\t\tregistrations.push(\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_navigate\",\n\t\t\t\t\tlabel: \"Browser navigate\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Navigate the active browser session and return structured verification evidence. Requires Playwright in <agentDir>/browser-deps; when missing, run browser_setup or follow the returned install commands\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\turl: Type.String({ minLength: 1 }),\n\t\t\t\t\t\twaitUntil: Type.Optional(\n\t\t\t\t\t\t\tType.Union([\n\t\t\t\t\t\t\t\tType.Literal(\"commit\"),\n\t\t\t\t\t\t\t\tType.Literal(\"domcontentloaded\"),\n\t\t\t\t\t\t\t\tType.Literal(\"load\"),\n\t\t\t\t\t\t\t\tType.Literal(\"networkidle\"),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.navigate(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\turl: nonEmptyString(input.url, \"url\"),\n\t\t\t\t\t\t\t\t\t...(input.waitUntil === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { waitUntil: input.waitUntil as BrowserNavigateInputV1[\"waitUntil\"] }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tregistrations.push(\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_snapshot\",\n\t\t\t\tlabel: \"Browser snapshot\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Capture accessibility, DOM, or screenshot evidence from the active browser session (styles capture is not supported in this build)\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tincludeDom: Type.Optional(Type.Boolean()),\n\t\t\t\t\tincludeA11y: Type.Optional(Type.Boolean()),\n\t\t\t\t\tincludeStyles: Type.Optional(Type.Boolean()),\n\t\t\t\t\tincludeScreenshot: Type.Optional(Type.Boolean()),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.snapshot(input as BrowserSnapshotInputV1, operationOptions(operationId, third)),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tif (!readonlyMode) {\n\t\t\tregistrations.push(\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_locator_action\",\n\t\t\t\t\tlabel: \"Browser locator action\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Perform a locator-based browser action (click/dblclick/rightclick/fill/type/press/hover/select/check/uncheck/clear/upload/scroll) and return post-action accessibility evidence\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\tlocator: browserLocatorSchema,\n\t\t\t\t\t\taction: Type.Union([\n\t\t\t\t\t\t\tType.Literal(\"click\"),\n\t\t\t\t\t\t\tType.Literal(\"dblclick\"),\n\t\t\t\t\t\t\tType.Literal(\"rightclick\"),\n\t\t\t\t\t\t\tType.Literal(\"fill\"),\n\t\t\t\t\t\t\tType.Literal(\"type\"),\n\t\t\t\t\t\t\tType.Literal(\"press\"),\n\t\t\t\t\t\t\tType.Literal(\"hover\"),\n\t\t\t\t\t\t\tType.Literal(\"select\"),\n\t\t\t\t\t\t\tType.Literal(\"check\"),\n\t\t\t\t\t\t\tType.Literal(\"uncheck\"),\n\t\t\t\t\t\t\tType.Literal(\"clear\"),\n\t\t\t\t\t\t\tType.Literal(\"upload\"),\n\t\t\t\t\t\t\tType.Literal(\"scroll\"),\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tvalue: Type.Optional(Type.String({ maxLength: 4096 })),\n\t\t\t\t\t\tkey: Type.Optional(Type.String({ maxLength: 128 })),\n\t\t\t\t\t\tdelay: Type.Optional(Type.Integer({ minimum: 0, maximum: 60_000 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.locatorAction(\n\t\t\t\t\t\t\t\tinput as unknown as BrowserLocatorActionInputV1,\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tregistrations.push(\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_assert_postcondition\",\n\t\t\t\tlabel: \"Browser assertion\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Assert a URL, element, text, console, or network postcondition (single condition or an array of 1..32, all ANDed) without changing the browser\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tcondition: browserPostconditionInputSchema,\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.assertPostcondition(\n\t\t\t\t\t\t\tinput.condition as BrowserPostconditionV1 | readonly BrowserPostconditionV1[],\n\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tregistrations.push(\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_collect_evidence\",\n\t\t\t\tlabel: \"Browser evidence\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Collect console, network, or accessibility-snapshot evidence from the active browser session without changing it\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tkind: Type.Union([Type.Literal(\"console\"), Type.Literal(\"network\"), Type.Literal(\"snapshot\")]),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\tconst kind = input.kind;\n\t\t\t\t\tif (kind !== \"console\" && kind !== \"network\" && kind !== \"snapshot\") {\n\t\t\t\t\t\tthrow new Error(\"kind must be console, network, or snapshot\");\n\t\t\t\t\t}\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.collectEvidence(kind as BrowserEvidenceKindV1, operationOptions(operationId, third)),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tregistrations.push(\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_console\",\n\t\t\t\tlabel: \"Browser console\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Read the bounded console ring buffer (page messages plus auto-accepted [dialog] traces) with optional level/contains filters, without changing the browser\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tlevel: Type.Optional(browserConsoleLevelSchema),\n\t\t\t\t\tcontains: Type.Optional(Type.String({ minLength: 1 })),\n\t\t\t\t\tlimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 200 })),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.consoleEntries(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(typeof input.level === \"string\" ? { level: input.level } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.contains === \"string\" ? { contains: input.contains } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.limit === \"number\" ? { limit: input.limit } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_network\",\n\t\t\t\tlabel: \"Browser network\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Read the bounded network ring buffer (method/url/status/duration/size when available) with optional containsUrl/status/method filters, without changing the browser\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tcontainsUrl: Type.Optional(Type.String({ minLength: 1 })),\n\t\t\t\t\tstatus: Type.Optional(Type.Integer({ minimum: 100, maximum: 599 })),\n\t\t\t\t\tmethod: Type.Optional(Type.String({ minLength: 1, maxLength: 16 })),\n\t\t\t\t\tlimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 200 })),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.networkEntries(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(typeof input.containsUrl === \"string\" ? { containsUrl: input.containsUrl } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.status === \"number\" ? { status: input.status } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.method === \"string\" ? { method: input.method } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.limit === \"number\" ? { limit: input.limit } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_performance\",\n\t\t\t\tlabel: \"Browser performance\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Read structured page performance metrics (Navigation Timing, paint, resource summary, DOM node count) without changing the browser\",\n\t\t\t\tparameters: Type.Object({}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { operationId } = toolInvocation(first, second);\n\t\t\t\t\treturn withSession(operationId, (active) => active.performance(operationOptions(operationId, third)));\n\t\t\t\t},\n\t\t\t}),\n\t\t\tapi.registerTool({\n\t\t\t\tname: \"browser_wait\",\n\t\t\t\tlabel: \"Browser wait\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Wait until a selector is visible, navigation completes, the network goes idle, or a fixed time passes (max 30s) and report the actual wait duration\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tuntil: browserWaitUntilKindSchema,\n\t\t\t\t\tlocator: Type.Optional(browserLocatorSchema),\n\t\t\t\t\tms: Type.Optional(Type.Integer({ minimum: 0, maximum: 30_000 })),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\tconst until = input.until;\n\t\t\t\t\tif (until !== \"selector\" && until !== \"navigation\" && until !== \"networkidle\" && until !== \"time\") {\n\t\t\t\t\t\tthrow new Error(\"until must be selector, navigation, networkidle, or time\");\n\t\t\t\t\t}\n\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\tactive.wait(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tuntil,\n\t\t\t\t\t\t\t\t...(until === \"selector\" ? { locator: input.locator as BrowserLocatorV1 | undefined } : {}),\n\t\t\t\t\t\t\t\t...(typeof input.ms === \"number\" ? { ms: input.ms } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t\tif (!readonlyMode) {\n\t\t\tregistrations.push(\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_evaluate\",\n\t\t\t\t\tlabel: \"Browser evaluate\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Escape hatch: evaluate a JavaScript expression in the page context and return the JSON-serialized result (bounded 64KiB, 30s timeout; dialogs are auto-accepted). Keep it a plain expression; for multi-statement logic use an IIFE string like ((() => { ... return x; })()) — a bare () => ... arrow string is not invoked and yields undefined. Only use this when no built-in browser tool covers the operation\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\texpression: Type.String({ minLength: 1, maxLength: 32_768 }),\n\t\t\t\t\t\targ: Type.Optional(Type.Unknown()),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.evaluate(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\texpression: nonEmptyString(input.expression, \"expression\"),\n\t\t\t\t\t\t\t\t\t...(input.arg === undefined ? {} : { arg: input.arg }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_drag\",\n\t\t\t\t\tlabel: \"Browser drag\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Drag from one element to another (hover source, press, hover target, stepped move to target center, release)\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\tfrom: browserLocatorSchema,\n\t\t\t\t\t\tto: browserLocatorSchema,\n\t\t\t\t\t\tsteps: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.drag(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfrom: input.from as BrowserLocatorV1,\n\t\t\t\t\t\t\t\t\tto: input.to as BrowserLocatorV1,\n\t\t\t\t\t\t\t\t\t...(typeof input.steps === \"number\" ? { steps: input.steps } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_mouse\",\n\t\t\t\t\tlabel: \"Browser mouse\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Raw mouse actions at viewport coordinates without needing an element (move/click/dblclick/rightclick/wheel); wheel is the basic page-scroll primitive\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\taction: browserMouseActionSchema,\n\t\t\t\t\t\tx: Type.Integer({ minimum: 0 }),\n\t\t\t\t\t\ty: Type.Integer({ minimum: 0 }),\n\t\t\t\t\t\tdeltaX: Type.Optional(Type.Integer({ minimum: -100_000, maximum: 100_000 })),\n\t\t\t\t\t\tdeltaY: Type.Optional(Type.Integer({ minimum: -100_000, maximum: 100_000 })),\n\t\t\t\t\t\tbutton: Type.Optional(browserMouseButtonSchema),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.mouse(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\taction: input.action as BrowserMouseInputV1[\"action\"],\n\t\t\t\t\t\t\t\t\tx: requiredInteger(input.x, \"x\"),\n\t\t\t\t\t\t\t\t\ty: requiredInteger(input.y, \"y\"),\n\t\t\t\t\t\t\t\t\t...(typeof input.deltaX === \"number\" ? { deltaX: input.deltaX } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.deltaY === \"number\" ? { deltaY: input.deltaY } : {}),\n\t\t\t\t\t\t\t\t\t...(input.button === undefined ? {} : { button: input.button as BrowserMouseButtonV1 }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_press_key\",\n\t\t\t\t\tlabel: \"Browser press key\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Press a page-level key combination by Playwright key name (e.g. Control+a, Enter, Meta+c) without needing an element\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\tkey: Type.String({ minLength: 1, maxLength: 128 }),\n\t\t\t\t\t\tdelay: Type.Optional(Type.Integer({ minimum: 0, maximum: 60_000 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.pressKey(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tkey: nonEmptyString(input.key, \"key\"),\n\t\t\t\t\t\t\t\t\t...(typeof input.delay === \"number\" ? { delay: input.delay } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_tabs\",\n\t\t\t\t\tlabel: \"Browser tabs\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Manage browser tabs: list (title/url/index), new (optional url), switch by index, or close by index\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\taction: browserTabsActionSchema,\n\t\t\t\t\t\tindex: Type.Optional(Type.Integer({ minimum: 0 })),\n\t\t\t\t\t\turl: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.tabs(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\taction: input.action as BrowserTabsInputV1[\"action\"],\n\t\t\t\t\t\t\t\t\t...(typeof input.index === \"number\" ? { index: input.index } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.url === \"string\" ? { url: input.url } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_storage\",\n\t\t\t\t\tlabel: \"Browser storage\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Manage browser storage: cookies (get-cookies/set-cookie/clear-cookies via the context) and localStorage (get-local/set-local/clear-local via the page)\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\taction: browserStorageActionSchema,\n\t\t\t\t\t\tname: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })),\n\t\t\t\t\t\tvalue: Type.Optional(Type.String({ maxLength: 4096 })),\n\t\t\t\t\t\tdomain: Type.Optional(Type.String({ minLength: 1, maxLength: 255 })),\n\t\t\t\t\t\tpath: Type.Optional(Type.String({ maxLength: 1024 })),\n\t\t\t\t\t\texpires: Type.Optional(Type.Number()),\n\t\t\t\t\t\tkey: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.storage(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\taction: input.action as BrowserStorageInputV1[\"action\"],\n\t\t\t\t\t\t\t\t\t...(typeof input.name === \"string\" ? { name: input.name } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.value === \"string\" ? { value: input.value } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.domain === \"string\" ? { domain: input.domain } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.path === \"string\" ? { path: input.path } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.expires === \"number\" ? { expires: input.expires } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.key === \"string\" ? { key: input.key } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_viewport\",\n\t\t\t\t\tlabel: \"Browser viewport\",\n\t\t\t\t\tdescription: \"Set the viewport size (width/height clamped into 320..7680) or read the current one\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\taction: browserViewportActionSchema,\n\t\t\t\t\t\twidth: Type.Optional(Type.Integer({ minimum: 0, maximum: 100_000 })),\n\t\t\t\t\t\theight: Type.Optional(Type.Integer({ minimum: 0, maximum: 100_000 })),\n\t\t\t\t\t}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\t\treturn withSession(operationId, (active) =>\n\t\t\t\t\t\t\tactive.viewport(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\taction: input.action as BrowserViewportInputV1[\"action\"],\n\t\t\t\t\t\t\t\t\t...(typeof input.width === \"number\" ? { width: input.width } : {}),\n\t\t\t\t\t\t\t\t\t...(typeof input.height === \"number\" ? { height: input.height } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toperationOptions(operationId, third),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (!readonlyMode) {\n\t\t\tregistrations.push(\n\t\t\t\tapi.registerTool({\n\t\t\t\t\tname: \"browser_setup\",\n\t\t\t\t\tlabel: \"Browser setup\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Install Playwright and the Chromium browser into <agentDir>/browser-deps (npm install playwright --prefix <dir>, then npx playwright install chromium). Reports alreadyInstalled when dependencies are present\",\n\t\t\t\t\tparameters: Type.Object({}),\n\t\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\t\tconst { operationId } = toolInvocation(first, second);\n\t\t\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treturn buildPluginResultV1({\n\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\tstatus: \"cancelled\",\n\t\t\t\t\t\t\t\tsummary: \"Browser operation was cancelled\",\n\t\t\t\t\t\t\t\tartifacts: [],\n\t\t\t\t\t\t\t\tevidence: [],\n\t\t\t\t\t\t\t\terror: { version: 1, code: \"cancelled\", message: \"Browser operation was cancelled\" },\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Call through the runtime: extracting the method would drop\n\t\t\t\t\t\t// `this`, and the production class reads `this.options` in setup().\n\t\t\t\t\t\tif (runtime.setup === undefined) {\n\t\t\t\t\t\t\treturn buildPluginResultV1({\n\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\t\t\tsummary: \"This browser runtime does not support self-installation\",\n\t\t\t\t\t\t\t\tartifacts: [],\n\t\t\t\t\t\t\t\tevidence: [],\n\t\t\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\tcode: \"browser_setup_unsupported\",\n\t\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t\t\"The injected browser runtime exposes no setup face. Install Playwright manually with npm install playwright --prefix <agentDir>/browser-deps and npx playwright install chromium.\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\treturn setupResult(operationId, await runtime.setup());\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treturn buildPluginResultV1({\n\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\t\t\tsummary: \"Playwright browser dependency setup failed\",\n\t\t\t\t\t\t\t\tartifacts: [],\n\t\t\t\t\t\t\t\tevidence: [],\n\t\t\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\tcode: \"browser_setup_failed\",\n\t\t\t\t\t\t\t\t\tmessage: browserVisibleError(error),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tif (session === undefined) return;\n\t\t\t\t// Both a failed connection and a failed close happen after this synchronous\n\t\t\t\t// disposer returns. Consume either rejection so disposal cannot leak a promise.\n\t\t\t\tvoid session.then((active) => active.close()).catch(() => undefined);\n\t\t\t},\n\t\t};\n\t};\n}\n"]}
|