@tinyviber/a2h 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.
- package/README.md +229 -0
- package/bin/a2h.js +7 -0
- package/dist/actions/engine.d.ts +40 -0
- package/dist/actions/engine.d.ts.map +1 -0
- package/dist/actions/engine.js +227 -0
- package/dist/actions/engine.js.map +1 -0
- package/dist/actions/policy.d.ts +8 -0
- package/dist/actions/policy.d.ts.map +1 -0
- package/dist/actions/policy.js +55 -0
- package/dist/actions/policy.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +258 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +69 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/build.d.ts +8 -0
- package/dist/ir/build.d.ts.map +1 -0
- package/dist/ir/build.js +397 -0
- package/dist/ir/build.js.map +1 -0
- package/dist/ir/enrich.d.ts +21 -0
- package/dist/ir/enrich.d.ts.map +1 -0
- package/dist/ir/enrich.js +177 -0
- package/dist/ir/enrich.js.map +1 -0
- package/dist/ir/identity.d.ts +3 -0
- package/dist/ir/identity.d.ts.map +1 -0
- package/dist/ir/identity.js +91 -0
- package/dist/ir/identity.js.map +1 -0
- package/dist/ir/scoring.d.ts +7 -0
- package/dist/ir/scoring.d.ts.map +1 -0
- package/dist/ir/scoring.js +88 -0
- package/dist/ir/scoring.js.map +1 -0
- package/dist/parsers/code.d.ts +3 -0
- package/dist/parsers/code.d.ts.map +1 -0
- package/dist/parsers/code.js +17 -0
- package/dist/parsers/code.js.map +1 -0
- package/dist/parsers/diff.d.ts +11 -0
- package/dist/parsers/diff.d.ts.map +1 -0
- package/dist/parsers/diff.js +125 -0
- package/dist/parsers/diff.js.map +1 -0
- package/dist/parsers/json.d.ts +3 -0
- package/dist/parsers/json.d.ts.map +1 -0
- package/dist/parsers/json.js +16 -0
- package/dist/parsers/json.js.map +1 -0
- package/dist/parsers/log.d.ts +3 -0
- package/dist/parsers/log.d.ts.map +1 -0
- package/dist/parsers/log.js +105 -0
- package/dist/parsers/log.js.map +1 -0
- package/dist/parsers/markdown.d.ts +15 -0
- package/dist/parsers/markdown.d.ts.map +1 -0
- package/dist/parsers/markdown.js +89 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/parsers/text.d.ts +24 -0
- package/dist/parsers/text.d.ts.map +1 -0
- package/dist/parsers/text.js +98 -0
- package/dist/parsers/text.js.map +1 -0
- package/dist/presentation/blocks.d.ts +14 -0
- package/dist/presentation/blocks.d.ts.map +1 -0
- package/dist/presentation/blocks.js +87 -0
- package/dist/presentation/blocks.js.map +1 -0
- package/dist/presentation/present.d.ts +12 -0
- package/dist/presentation/present.d.ts.map +1 -0
- package/dist/presentation/present.js +115 -0
- package/dist/presentation/present.js.map +1 -0
- package/dist/providers/mock.d.ts +3 -0
- package/dist/providers/mock.d.ts.map +1 -0
- package/dist/providers/mock.js +171 -0
- package/dist/providers/mock.js.map +1 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +20 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +64 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +40 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/renderer/assets/index.html +41 -0
- package/dist/renderer/assets/js/actions.js +205 -0
- package/dist/renderer/assets/js/api.js +75 -0
- package/dist/renderer/assets/js/artifacts.js +422 -0
- package/dist/renderer/assets/js/blocks.js +301 -0
- package/dist/renderer/assets/js/bus.js +21 -0
- package/dist/renderer/assets/js/cards.js +137 -0
- package/dist/renderer/assets/js/dom.js +61 -0
- package/dist/renderer/assets/js/format.js +118 -0
- package/dist/renderer/assets/js/main.js +125 -0
- package/dist/renderer/assets/js/nav.js +80 -0
- package/dist/renderer/assets/js/router.js +64 -0
- package/dist/renderer/assets/js/store.js +69 -0
- package/dist/renderer/assets/js/views.js +356 -0
- package/dist/renderer/assets/styles.css +856 -0
- package/dist/renderer/content.d.ts +11 -0
- package/dist/renderer/content.d.ts.map +1 -0
- package/dist/renderer/content.js +114 -0
- package/dist/renderer/content.js.map +1 -0
- package/dist/scanner/classify.d.ts +17 -0
- package/dist/scanner/classify.d.ts.map +1 -0
- package/dist/scanner/classify.js +117 -0
- package/dist/scanner/classify.js.map +1 -0
- package/dist/scanner/git.d.ts +3 -0
- package/dist/scanner/git.d.ts.map +1 -0
- package/dist/scanner/git.js +43 -0
- package/dist/scanner/git.js.map +1 -0
- package/dist/scanner/ignore.d.ts +22 -0
- package/dist/scanner/ignore.d.ts.map +1 -0
- package/dist/scanner/ignore.js +57 -0
- package/dist/scanner/ignore.js.map +1 -0
- package/dist/scanner/scan.d.ts +8 -0
- package/dist/scanner/scan.d.ts.map +1 -0
- package/dist/scanner/scan.js +108 -0
- package/dist/scanner/scan.js.map +1 -0
- package/dist/security/boundary.d.ts +19 -0
- package/dist/security/boundary.d.ts.map +1 -0
- package/dist/security/boundary.js +59 -0
- package/dist/security/boundary.js.map +1 -0
- package/dist/security/urlPolicy.d.ts +4 -0
- package/dist/security/urlPolicy.d.ts.map +1 -0
- package/dist/security/urlPolicy.js +56 -0
- package/dist/security/urlPolicy.js.map +1 -0
- package/dist/security/workspaceRead.d.ts +25 -0
- package/dist/security/workspaceRead.d.ts.map +1 -0
- package/dist/security/workspaceRead.js +103 -0
- package/dist/security/workspaceRead.js.map +1 -0
- package/dist/semantics/load.d.ts +11 -0
- package/dist/semantics/load.d.ts.map +1 -0
- package/dist/semantics/load.js +537 -0
- package/dist/semantics/load.js.map +1 -0
- package/dist/semantics/resolve.d.ts +37 -0
- package/dist/semantics/resolve.d.ts.map +1 -0
- package/dist/semantics/resolve.js +211 -0
- package/dist/semantics/resolve.js.map +1 -0
- package/dist/semantics/types.d.ts +158 -0
- package/dist/semantics/types.d.ts.map +1 -0
- package/dist/semantics/types.js +3 -0
- package/dist/semantics/types.js.map +1 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +510 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/workspace.d.ts +53 -0
- package/dist/server/workspace.d.ts.map +1 -0
- package/dist/server/workspace.js +211 -0
- package/dist/server/workspace.js.map +1 -0
- package/dist/types.d.ts +570 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/util/path.d.ts +11 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/path.js +35 -0
- package/dist/util/path.js.map +1 -0
- package/dist/util/safeRead.d.ts +38 -0
- package/dist/util/safeRead.d.ts.map +1 -0
- package/dist/util/safeRead.js +117 -0
- package/dist/util/safeRead.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# A2H — Agent to Human
|
|
2
|
+
|
|
3
|
+
**Turn agent workspaces into human-readable, inspectable, actionable local web interfaces.**
|
|
4
|
+
|
|
5
|
+
Agents are good at producing files, logs, diffs, reports, intermediate state, and machine-friendly metadata. Humans are not good at supervising all of that through a terminal or by opening folders one file at a time.
|
|
6
|
+
|
|
7
|
+
A2H is the layer in between:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Agent workspace
|
|
11
|
+
↓
|
|
12
|
+
scan + semantics
|
|
13
|
+
↓
|
|
14
|
+
presentation
|
|
15
|
+
↓
|
|
16
|
+
Human interface
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It works with an ordinary repository out of the box, and agents can optionally describe richer semantics through a small `.a2h/manifest.json` protocol.
|
|
20
|
+
|
|
21
|
+
A2H is **not** another agent runtime or workflow engine. It is the human-facing layer for reading, inspecting, comparing, judging, and acting on work produced by agents.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
Requires Node.js 18+.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx a2h render . --open
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A2H scans the current workspace and starts a local viewer on `http://localhost:8420`.
|
|
32
|
+
|
|
33
|
+
For live updates while an agent is working:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx a2h render . --watch --open
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
No configuration is required.
|
|
40
|
+
|
|
41
|
+
## Zero-config first
|
|
42
|
+
|
|
43
|
+
On an ordinary repository, A2H infers a useful presentation from the files already there:
|
|
44
|
+
|
|
45
|
+
- README and Markdown
|
|
46
|
+
- code
|
|
47
|
+
- diffs
|
|
48
|
+
- logs
|
|
49
|
+
- JSON / structured data
|
|
50
|
+
- images
|
|
51
|
+
- reports and generated artifacts
|
|
52
|
+
|
|
53
|
+
File names, directory conventions, content type, and lightweight heuristics are used only as fallbacks. Existing repositories do not need to adopt an A2H-specific structure.
|
|
54
|
+
|
|
55
|
+
## Explicit semantics for agents
|
|
56
|
+
|
|
57
|
+
When a producer wants more control, it can write `.a2h/manifest.json` and tell A2H what the workspace means.
|
|
58
|
+
|
|
59
|
+
Start from the current workspace automatically:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx a2h init .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A small manifest can look like this:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"a2h": 1,
|
|
70
|
+
"name": "Coding task",
|
|
71
|
+
"tasks": [
|
|
72
|
+
{
|
|
73
|
+
"id": "review",
|
|
74
|
+
"title": "Review implementation",
|
|
75
|
+
"status": "waiting-for-human",
|
|
76
|
+
"actions": ["approve"]
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"actions": [
|
|
80
|
+
{
|
|
81
|
+
"id": "approve",
|
|
82
|
+
"label": "Approve",
|
|
83
|
+
"kind": "approve",
|
|
84
|
+
"taskId": "review",
|
|
85
|
+
"sideEffect": "state"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"items": [
|
|
89
|
+
{
|
|
90
|
+
"path": "review/summary.md",
|
|
91
|
+
"role": "review",
|
|
92
|
+
"taskId": "review",
|
|
93
|
+
"priority": 900
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"path": "patches/change.diff",
|
|
97
|
+
"role": "implementation-diff",
|
|
98
|
+
"taskId": "review"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The important separation is:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
role = what this artifact means
|
|
108
|
+
content kind = how its bytes should be rendered
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
So a producer may invent roles such as `spec`, `evidence`, `decision`, `signal`, or `publish-candidate` without teaching the renderer new file types.
|
|
112
|
+
|
|
113
|
+
When signals conflict, A2H prefers explicit semantics and falls back progressively to conventions and inference.
|
|
114
|
+
|
|
115
|
+
## Tasks, runs, presentation blocks, and actions
|
|
116
|
+
|
|
117
|
+
The protocol can describe more than files. A workspace can expose:
|
|
118
|
+
|
|
119
|
+
- tasks and their status
|
|
120
|
+
- agent / workflow runs
|
|
121
|
+
- artifacts associated with a task or run
|
|
122
|
+
- metrics, tables, timelines, comparisons, notices, and other presentation blocks
|
|
123
|
+
- human-facing actions such as approve, reject, retry, or send back to an agent
|
|
124
|
+
|
|
125
|
+
The built-in executor currently **simulates actions by default**. The UI says when an action is simulated. Real agent integrations are intended to sit behind provider / adapter boundaries rather than being hard-coded into A2H.
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
Two example workspaces are included in the repository.
|
|
130
|
+
|
|
131
|
+
### Radar
|
|
132
|
+
|
|
133
|
+
A simulated daily agent workflow that ingests sources, clusters signals, writes publish candidates, and stops for human review.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx a2h render examples/radar --open
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
See [`examples/radar`](examples/radar) and its [manifest](examples/radar/.a2h/manifest.json).
|
|
140
|
+
|
|
141
|
+
### Coding task
|
|
142
|
+
|
|
143
|
+
A workspace centered on a coding-agent task with a spec, implementation, tests, patch, screenshots, and review decisions.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npx a2h render examples/coding-task --open
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [`examples/coding-task`](examples/coding-task).
|
|
150
|
+
|
|
151
|
+
## CLI
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
a2h [render] [path] [options]
|
|
155
|
+
a2h init [path]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Common options:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
-w, --watch Watch the workspace and refresh on changes
|
|
162
|
+
-p, --port <n> Viewer port (default: 8420)
|
|
163
|
+
-o, --open Open the viewer in the default browser
|
|
164
|
+
-f, --force Overwrite an existing manifest when using init
|
|
165
|
+
-v, --version Print the version
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx a2h render
|
|
172
|
+
npx a2h render ../agent-workspace --watch
|
|
173
|
+
npx a2h init .
|
|
174
|
+
npx a2h render . --port 9000 --open
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Design principles
|
|
178
|
+
|
|
179
|
+
A2H is deliberately local-first and content-first.
|
|
180
|
+
|
|
181
|
+
The interface is designed for humans to:
|
|
182
|
+
|
|
183
|
+
**read → inspect → compare → judge → act when necessary**
|
|
184
|
+
|
|
185
|
+
The semantic layer is designed for machines to emit structured meaning without coupling themselves to a specific frontend.
|
|
186
|
+
|
|
187
|
+
The zero-config path remains important: explicit metadata should improve a workspace, not become a requirement for using A2H.
|
|
188
|
+
|
|
189
|
+
## Security model
|
|
190
|
+
|
|
191
|
+
A2H treats the workspace as untrusted input.
|
|
192
|
+
|
|
193
|
+
Workspace file reads go through a bounded capability that checks the scanner allowlist, sensitive-file classification, symlinks, and real-path containment. Raw workspace files are not exposed as arbitrary same-origin documents.
|
|
194
|
+
|
|
195
|
+
Mutating browser requests go through a separate control-surface boundary. Actions must be declared, browser parameters are filtered against the declared schema, external effects require confirmation, and localhost mutations require a per-process session token.
|
|
196
|
+
|
|
197
|
+
The server binds to `127.0.0.1` by default.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm install
|
|
203
|
+
npm run build
|
|
204
|
+
npm test
|
|
205
|
+
npm run test:acceptance
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Run the CLI from the repository:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
node bin/a2h.js render examples/radar --open
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Before publishing:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
npm pack
|
|
218
|
+
npm publish --dry-run
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Status
|
|
222
|
+
|
|
223
|
+
A2H is early-stage. The protocol and presentation model are intentionally small and extensible, and real external agent providers are still evolving.
|
|
224
|
+
|
|
225
|
+
If you are experimenting with coding agents, scheduled agents, research pipelines, or any workflow where machines produce more state than a human wants to inspect manually, A2H is meant to be a thin human interface over that work.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|
package/bin/a2h.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Thin entry point: loads the compiled CLI and hands off control.
|
|
5
|
+
// Kept as plain JS so the shebang survives publishing regardless of
|
|
6
|
+
// how the TypeScript build emits its output.
|
|
7
|
+
require('../dist/cli/index.js').main(process.argv.slice(2));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ActionAuditEntry, ActionResult, ActionRequest, ActionView, RunView } from '../types';
|
|
2
|
+
import type { ProviderRegistry } from '../providers/types';
|
|
3
|
+
export type AuditEntry = ActionAuditEntry;
|
|
4
|
+
export interface ActionEngineOptions {
|
|
5
|
+
rootDir: string;
|
|
6
|
+
registry: ProviderRegistry;
|
|
7
|
+
now?: () => Date;
|
|
8
|
+
}
|
|
9
|
+
export declare class ActionEngine {
|
|
10
|
+
private readonly rootDir;
|
|
11
|
+
private readonly registry;
|
|
12
|
+
private readonly now;
|
|
13
|
+
private runtimeRuns;
|
|
14
|
+
private taskStatus;
|
|
15
|
+
private audit;
|
|
16
|
+
private seq;
|
|
17
|
+
constructor(options: ActionEngineOptions);
|
|
18
|
+
/** Runs produced by actions during this session, newest first. */
|
|
19
|
+
getRuntimeRuns(): RunView[];
|
|
20
|
+
/** Task status overrides applied by executed actions. */
|
|
21
|
+
getTaskStatus(taskId: string): string | undefined;
|
|
22
|
+
getAuditTrail(): AuditEntry[];
|
|
23
|
+
/**
|
|
24
|
+
* Executes a declared action.
|
|
25
|
+
*
|
|
26
|
+
* @param declared the action registry as published to the client. Anything
|
|
27
|
+
* not present here is rejected — this is the allowlist.
|
|
28
|
+
*/
|
|
29
|
+
execute(request: ActionRequest, declared: ActionView[]): Promise<ActionResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Records a refused or failed attempt and returns the result.
|
|
32
|
+
*
|
|
33
|
+
* Exactly one audit line is written per attempt — including the ones that
|
|
34
|
+
* never reached an executor. Attribution matters here: the trail is the
|
|
35
|
+
* record of what was asked for and what actually happened.
|
|
36
|
+
*/
|
|
37
|
+
private fail;
|
|
38
|
+
private record;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/actions/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAEhB,YAAY,EACZ,aAAa,EACb,UAAU,EACV,OAAO,EACR,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAkC3D,MAAM,MAAM,UAAU,GAAG,gBAAgB,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IAEjC,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,GAAG,CAAK;gBAEJ,OAAO,EAAE,mBAAmB;IAMxC,kEAAkE;IAClE,cAAc,IAAI,OAAO,EAAE;IAI3B,yDAAyD;IACzD,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,aAAa,IAAI,UAAU,EAAE;IAI7B;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAuHpF;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAoBZ,OAAO,CAAC,MAAM;CAIf"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ActionEngine = void 0;
|
|
4
|
+
const policy_1 = require("./policy");
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Action engine — the security boundary between the browser and anything with
|
|
7
|
+
// an effect.
|
|
8
|
+
//
|
|
9
|
+
// Guarantees, in order of importance:
|
|
10
|
+
//
|
|
11
|
+
// 1. Only actions *declared in the workspace manifest* can be executed. The
|
|
12
|
+
// browser sends an id; it can never send a command.
|
|
13
|
+
// 2. Values from the browser are filtered against the declared schema before
|
|
14
|
+
// an executor ever sees them: undeclared keys are dropped, and a value
|
|
15
|
+
// that is not legal for its declared type is refused. Nothing is
|
|
16
|
+
// interpolated into a shell or used as a path.
|
|
17
|
+
// 3. An effect that reaches beyond A2H's own state requires an explicit
|
|
18
|
+
// second confirmation from the human. This is decided here, from two
|
|
19
|
+
// claims — the workspace's and the executor's — merged with the stricter
|
|
20
|
+
// winning. Neither is trusted on its own: the manifest is untrusted
|
|
21
|
+
// input, and it is the executor's declared policy that a workspace cannot
|
|
22
|
+
// talk down.
|
|
23
|
+
// 4. Execution is delegated to a registered executor. The built-in executor
|
|
24
|
+
// only simulates state changes, so nothing dangerous can happen by
|
|
25
|
+
// default — but the protocol and the UI flow are fully exercised.
|
|
26
|
+
//
|
|
27
|
+
// The engine keeps an in-memory audit trail and in-memory run/status state. It
|
|
28
|
+
// deliberately writes nothing to the workspace.
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const MAX_PARAM_LENGTH = 2000;
|
|
31
|
+
const MAX_PARAMS = 20;
|
|
32
|
+
const MAX_RUNTIME_RUNS = 200;
|
|
33
|
+
const MAX_AUDIT = 200;
|
|
34
|
+
class ActionEngine {
|
|
35
|
+
rootDir;
|
|
36
|
+
registry;
|
|
37
|
+
now;
|
|
38
|
+
runtimeRuns = [];
|
|
39
|
+
taskStatus = new Map();
|
|
40
|
+
audit = [];
|
|
41
|
+
seq = 0;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.rootDir = options.rootDir;
|
|
44
|
+
this.registry = options.registry;
|
|
45
|
+
this.now = options.now ?? (() => new Date());
|
|
46
|
+
}
|
|
47
|
+
/** Runs produced by actions during this session, newest first. */
|
|
48
|
+
getRuntimeRuns() {
|
|
49
|
+
return this.runtimeRuns;
|
|
50
|
+
}
|
|
51
|
+
/** Task status overrides applied by executed actions. */
|
|
52
|
+
getTaskStatus(taskId) {
|
|
53
|
+
return this.taskStatus.get(taskId);
|
|
54
|
+
}
|
|
55
|
+
getAuditTrail() {
|
|
56
|
+
return this.audit;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Executes a declared action.
|
|
60
|
+
*
|
|
61
|
+
* @param declared the action registry as published to the client. Anything
|
|
62
|
+
* not present here is rejected — this is the allowlist.
|
|
63
|
+
*/
|
|
64
|
+
async execute(request, declared) {
|
|
65
|
+
const action = declared.find((a) => a.id === request.id);
|
|
66
|
+
if (!action) {
|
|
67
|
+
return this.fail(request.id, 'unknown_action', 'That action is not declared by this workspace.');
|
|
68
|
+
}
|
|
69
|
+
if (!action.enabled) {
|
|
70
|
+
return this.fail(action.id, 'disabled', `"${action.label}" is currently disabled.`);
|
|
71
|
+
}
|
|
72
|
+
const declaredParams = action.params ?? [];
|
|
73
|
+
const outcome = buildParams(request.params, declaredParams);
|
|
74
|
+
if (outcome.invalid) {
|
|
75
|
+
return this.fail(action.id, 'invalid_params', `"${outcome.invalid.name}" ${outcome.invalid.reason}.`);
|
|
76
|
+
}
|
|
77
|
+
const params = outcome.params;
|
|
78
|
+
const missing = declaredParams
|
|
79
|
+
.filter((p) => p.required && !String(params[p.name] ?? '').trim())
|
|
80
|
+
.map((p) => p.label ?? p.name);
|
|
81
|
+
if (missing.length > 0) {
|
|
82
|
+
return this.fail(action.id, 'missing_params', `Missing required input: ${missing.join(', ')}.`);
|
|
83
|
+
}
|
|
84
|
+
// Who runs this, and what they say it will do. Asked before the
|
|
85
|
+
// confirmation check, because the answer is half of that decision.
|
|
86
|
+
const resolution = this.registry.resolve(action);
|
|
87
|
+
if (!resolution) {
|
|
88
|
+
return this.fail(action.id, 'no_executor', `No provider can execute "${action.kind}" actions in this workspace.`);
|
|
89
|
+
}
|
|
90
|
+
// The hard boundary, recomputed from both claims rather than read off the
|
|
91
|
+
// incoming view: the workspace may escalate the level, the executor may
|
|
92
|
+
// escalate it, and the merge takes the higher one. A forged ActionView
|
|
93
|
+
// therefore cannot de-escalate anything either.
|
|
94
|
+
const policy = (0, policy_1.strictestPolicy)((0, policy_1.policyFromHint)(action.sideEffect, action.confirm), resolution.policy);
|
|
95
|
+
if (policy.confirmation === 'required' && request.confirm !== true) {
|
|
96
|
+
return this.fail(action.id, 'confirmation_required', `"${action.label}" has a ${policy.effect} side effect and needs confirmation.`, resolution.executor.id, policy.effect);
|
|
97
|
+
}
|
|
98
|
+
this.seq += 1;
|
|
99
|
+
const runId = `run-${this.seq}-${slug(action.kind)}`;
|
|
100
|
+
try {
|
|
101
|
+
const outcome = await resolution.executor.execute(action, params, {
|
|
102
|
+
workspaceRoot: this.rootDir,
|
|
103
|
+
runId,
|
|
104
|
+
now: this.now(),
|
|
105
|
+
});
|
|
106
|
+
// Provenance is the registry's answer OR the executor's, never the
|
|
107
|
+
// executor's alone: a simulator that forgets to label its output, or a
|
|
108
|
+
// provider that claims a real effect it did not have, both end up
|
|
109
|
+
// reported as simulated rather than the reverse.
|
|
110
|
+
const simulated = resolution.simulated || outcome.simulated === true;
|
|
111
|
+
const run = outcome.run
|
|
112
|
+
? {
|
|
113
|
+
...outcome.run,
|
|
114
|
+
id: outcome.run.id ?? runId,
|
|
115
|
+
taskId: action.taskId,
|
|
116
|
+
simulated,
|
|
117
|
+
}
|
|
118
|
+
: undefined;
|
|
119
|
+
if (run) {
|
|
120
|
+
this.runtimeRuns.unshift(run);
|
|
121
|
+
if (this.runtimeRuns.length > MAX_RUNTIME_RUNS) {
|
|
122
|
+
this.runtimeRuns.length = MAX_RUNTIME_RUNS;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (action.taskId && outcome.taskStatus) {
|
|
126
|
+
this.taskStatus.set(action.taskId, outcome.taskStatus);
|
|
127
|
+
}
|
|
128
|
+
this.record({
|
|
129
|
+
at: this.now().toISOString(),
|
|
130
|
+
actionId: action.id,
|
|
131
|
+
kind: action.kind,
|
|
132
|
+
sideEffect: policy.effect,
|
|
133
|
+
ok: true,
|
|
134
|
+
simulated,
|
|
135
|
+
message: outcome.message,
|
|
136
|
+
executor: resolution.executor.id,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
actionId: action.id,
|
|
141
|
+
message: outcome.message,
|
|
142
|
+
simulated,
|
|
143
|
+
run,
|
|
144
|
+
task: action.taskId && outcome.taskStatus
|
|
145
|
+
? { id: action.taskId, status: outcome.taskStatus }
|
|
146
|
+
: undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
const message = `Executor "${resolution.executor.id}" failed: ${err.message}`;
|
|
151
|
+
return this.fail(action.id, 'executor_failed', message, resolution.executor.id, policy.effect);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Records a refused or failed attempt and returns the result.
|
|
156
|
+
*
|
|
157
|
+
* Exactly one audit line is written per attempt — including the ones that
|
|
158
|
+
* never reached an executor. Attribution matters here: the trail is the
|
|
159
|
+
* record of what was asked for and what actually happened.
|
|
160
|
+
*/
|
|
161
|
+
fail(actionId, error, message, executor = 'none', sideEffect = 'none') {
|
|
162
|
+
this.record({
|
|
163
|
+
at: this.now().toISOString(),
|
|
164
|
+
actionId,
|
|
165
|
+
kind: 'n/a',
|
|
166
|
+
sideEffect,
|
|
167
|
+
ok: false,
|
|
168
|
+
simulated: false,
|
|
169
|
+
message,
|
|
170
|
+
executor,
|
|
171
|
+
});
|
|
172
|
+
return { ok: false, actionId, message, simulated: false, error };
|
|
173
|
+
}
|
|
174
|
+
record(entry) {
|
|
175
|
+
this.audit.unshift(entry);
|
|
176
|
+
if (this.audit.length > MAX_AUDIT)
|
|
177
|
+
this.audit.length = MAX_AUDIT;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.ActionEngine = ActionEngine;
|
|
181
|
+
/**
|
|
182
|
+
* Untrusted browser input, filtered against the action's declared schema.
|
|
183
|
+
*
|
|
184
|
+
* The distinguishing property is what is *absent* from the result: a key the
|
|
185
|
+
* action never declared. An executor written against the spec therefore cannot
|
|
186
|
+
* be reached by `shell`, `cwd`, or anything else a hostile page invents — not
|
|
187
|
+
* because the executor remembers to ignore them, but because they never arrive.
|
|
188
|
+
*
|
|
189
|
+
* Values that are legal for their type are normalized (`boolean` becomes the
|
|
190
|
+
* string "true"/"false"), so an executor has one shape to handle.
|
|
191
|
+
*/
|
|
192
|
+
function buildParams(input, declared) {
|
|
193
|
+
const params = {};
|
|
194
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
195
|
+
return { params };
|
|
196
|
+
const supplied = input;
|
|
197
|
+
for (const spec of declared.slice(0, MAX_PARAMS)) {
|
|
198
|
+
const raw = supplied[spec.name];
|
|
199
|
+
if (raw === undefined)
|
|
200
|
+
continue;
|
|
201
|
+
if (typeof raw !== 'string' && typeof raw !== 'number' && typeof raw !== 'boolean')
|
|
202
|
+
continue;
|
|
203
|
+
const value = String(raw).slice(0, MAX_PARAM_LENGTH);
|
|
204
|
+
if (spec.type === 'select') {
|
|
205
|
+
const options = spec.options ?? [];
|
|
206
|
+
// An out-of-range select is refused rather than passed through: a
|
|
207
|
+
// provider that branches on this value would otherwise be steerable.
|
|
208
|
+
if (options.length > 0 && !options.includes(value)) {
|
|
209
|
+
return { params, invalid: { name: spec.name, reason: `must be one of: ${options.join(', ')}` } };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (spec.type === 'boolean') {
|
|
213
|
+
const normalized = value.trim().toLowerCase();
|
|
214
|
+
if (normalized !== 'true' && normalized !== 'false') {
|
|
215
|
+
return { params, invalid: { name: spec.name, reason: 'must be true or false' } };
|
|
216
|
+
}
|
|
217
|
+
params[spec.name] = normalized;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
params[spec.name] = value;
|
|
221
|
+
}
|
|
222
|
+
return { params };
|
|
223
|
+
}
|
|
224
|
+
function slug(kind) {
|
|
225
|
+
return kind.replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'action';
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/actions/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EffectPolicy, SideEffect } from '../types';
|
|
2
|
+
/** The policy a workspace's own declaration amounts to. */
|
|
3
|
+
export declare function policyFromHint(sideEffect?: SideEffect, confirm?: boolean): EffectPolicy;
|
|
4
|
+
/** The stricter of two policies: the higher effect, and confirmation if either asks. */
|
|
5
|
+
export declare function stricterPolicy(a: EffectPolicy, b: EffectPolicy): EffectPolicy;
|
|
6
|
+
/** Folds a list of claims into the policy that governs the action. */
|
|
7
|
+
export declare function strictestPolicy(...policies: EffectPolicy[]): EffectPolicy;
|
|
8
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/actions/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAoCzD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,YAAY,CAKvF;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,YAAY,CAM7E;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,GAAG,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,CAEzE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.policyFromHint = policyFromHint;
|
|
4
|
+
exports.stricterPolicy = stricterPolicy;
|
|
5
|
+
exports.strictestPolicy = strictestPolicy;
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Effect policy — the one place the confirmation boundary is computed.
|
|
8
|
+
//
|
|
9
|
+
// There are two statements about how dangerous an action is:
|
|
10
|
+
//
|
|
11
|
+
// * the workspace's claim — `sideEffect` + `confirm` in the manifest. The
|
|
12
|
+
// workspace is untrusted input, so this is a *hint*.
|
|
13
|
+
// * the provider's claim — `ActionExecutor.policy()`. The provider is the
|
|
14
|
+
// code that will actually run, so this is *authoritative*.
|
|
15
|
+
//
|
|
16
|
+
// They are merged, and the stricter one wins, every time. A workspace can say
|
|
17
|
+
// "this is worse than you think" and get a confirmation prompt; it can never
|
|
18
|
+
// say "this is fine" and remove one. The old model let it: a manifest that
|
|
19
|
+
// called a future `publish` action `sideEffect: "state"` decided its own
|
|
20
|
+
// safety level, which is the wrong direction for untrusted input to point.
|
|
21
|
+
//
|
|
22
|
+
// The merge also normalizes one invariant: an effect that leaves this machine
|
|
23
|
+
// always requires confirmation, whatever either side said. Spelling it here
|
|
24
|
+
// rather than at the call site means there is no path that forgets it.
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const RANK = { none: 0, state: 1, external: 2 };
|
|
27
|
+
/**
|
|
28
|
+
* The invariant, applied to every policy that leaves this module: an effect
|
|
29
|
+
* that reaches outside this machine is always confirmed. Stated once here
|
|
30
|
+
* rather than at each call site, because there is no path that should skip it.
|
|
31
|
+
*/
|
|
32
|
+
function normalize(policy) {
|
|
33
|
+
return policy.effect === 'external' && policy.confirmation !== 'required'
|
|
34
|
+
? { effect: 'external', confirmation: 'required' }
|
|
35
|
+
: policy;
|
|
36
|
+
}
|
|
37
|
+
/** The policy a workspace's own declaration amounts to. */
|
|
38
|
+
function policyFromHint(sideEffect, confirm) {
|
|
39
|
+
return normalize({
|
|
40
|
+
effect: sideEffect ?? 'state',
|
|
41
|
+
confirmation: confirm ? 'required' : 'optional',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** The stricter of two policies: the higher effect, and confirmation if either asks. */
|
|
45
|
+
function stricterPolicy(a, b) {
|
|
46
|
+
return normalize({
|
|
47
|
+
effect: RANK[a.effect] >= RANK[b.effect] ? a.effect : b.effect,
|
|
48
|
+
confirmation: a.confirmation === 'required' || b.confirmation === 'required' ? 'required' : 'optional',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Folds a list of claims into the policy that governs the action. */
|
|
52
|
+
function strictestPolicy(...policies) {
|
|
53
|
+
return policies.reduce(stricterPolicy, { effect: 'none', confirmation: 'optional' });
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/actions/policy.ts"],"names":[],"mappings":";;AAqCA,wCAKC;AAGD,wCAMC;AAGD,0CAEC;AAtDD,8EAA8E;AAC9E,uEAAuE;AACvE,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,6EAA6E;AAC7E,yDAAyD;AACzD,8EAA8E;AAC9E,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,uEAAuE;AACvE,8EAA8E;AAE9E,MAAM,IAAI,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAE5E;;;;GAIG;AACH,SAAS,SAAS,CAAC,MAAoB;IACrC,OAAO,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,YAAY,KAAK,UAAU;QACvE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE;QAClD,CAAC,CAAC,MAAM,CAAC;AACb,CAAC;AAED,2DAA2D;AAC3D,SAAgB,cAAc,CAAC,UAAuB,EAAE,OAAiB;IACvE,OAAO,SAAS,CAAC;QACf,MAAM,EAAE,UAAU,IAAI,OAAO;QAC7B,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;KAChD,CAAC,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,SAAgB,cAAc,CAAC,CAAe,EAAE,CAAe;IAC7D,OAAO,SAAS,CAAC;QACf,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;QAC9D,YAAY,EACV,CAAC,CAAC,YAAY,KAAK,UAAU,IAAI,CAAC,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;KAC3F,CAAC,CAAC;AACL,CAAC;AAED,sEAAsE;AACtE,SAAgB,eAAe,CAAC,GAAG,QAAwB;IACzD,OAAO,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAsBA,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAoBzC"}
|