@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.
Files changed (162) hide show
  1. package/README.md +229 -0
  2. package/bin/a2h.js +7 -0
  3. package/dist/actions/engine.d.ts +40 -0
  4. package/dist/actions/engine.d.ts.map +1 -0
  5. package/dist/actions/engine.js +227 -0
  6. package/dist/actions/engine.js.map +1 -0
  7. package/dist/actions/policy.d.ts +8 -0
  8. package/dist/actions/policy.d.ts.map +1 -0
  9. package/dist/actions/policy.js +55 -0
  10. package/dist/actions/policy.js.map +1 -0
  11. package/dist/cli/index.d.ts +2 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +258 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/init.d.ts +8 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +69 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/index.d.ts +26 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +66 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/ir/build.d.ts +8 -0
  24. package/dist/ir/build.d.ts.map +1 -0
  25. package/dist/ir/build.js +397 -0
  26. package/dist/ir/build.js.map +1 -0
  27. package/dist/ir/enrich.d.ts +21 -0
  28. package/dist/ir/enrich.d.ts.map +1 -0
  29. package/dist/ir/enrich.js +177 -0
  30. package/dist/ir/enrich.js.map +1 -0
  31. package/dist/ir/identity.d.ts +3 -0
  32. package/dist/ir/identity.d.ts.map +1 -0
  33. package/dist/ir/identity.js +91 -0
  34. package/dist/ir/identity.js.map +1 -0
  35. package/dist/ir/scoring.d.ts +7 -0
  36. package/dist/ir/scoring.d.ts.map +1 -0
  37. package/dist/ir/scoring.js +88 -0
  38. package/dist/ir/scoring.js.map +1 -0
  39. package/dist/parsers/code.d.ts +3 -0
  40. package/dist/parsers/code.d.ts.map +1 -0
  41. package/dist/parsers/code.js +17 -0
  42. package/dist/parsers/code.js.map +1 -0
  43. package/dist/parsers/diff.d.ts +11 -0
  44. package/dist/parsers/diff.d.ts.map +1 -0
  45. package/dist/parsers/diff.js +125 -0
  46. package/dist/parsers/diff.js.map +1 -0
  47. package/dist/parsers/json.d.ts +3 -0
  48. package/dist/parsers/json.d.ts.map +1 -0
  49. package/dist/parsers/json.js +16 -0
  50. package/dist/parsers/json.js.map +1 -0
  51. package/dist/parsers/log.d.ts +3 -0
  52. package/dist/parsers/log.d.ts.map +1 -0
  53. package/dist/parsers/log.js +105 -0
  54. package/dist/parsers/log.js.map +1 -0
  55. package/dist/parsers/markdown.d.ts +15 -0
  56. package/dist/parsers/markdown.d.ts.map +1 -0
  57. package/dist/parsers/markdown.js +89 -0
  58. package/dist/parsers/markdown.js.map +1 -0
  59. package/dist/parsers/text.d.ts +24 -0
  60. package/dist/parsers/text.d.ts.map +1 -0
  61. package/dist/parsers/text.js +98 -0
  62. package/dist/parsers/text.js.map +1 -0
  63. package/dist/presentation/blocks.d.ts +14 -0
  64. package/dist/presentation/blocks.d.ts.map +1 -0
  65. package/dist/presentation/blocks.js +87 -0
  66. package/dist/presentation/blocks.js.map +1 -0
  67. package/dist/presentation/present.d.ts +12 -0
  68. package/dist/presentation/present.d.ts.map +1 -0
  69. package/dist/presentation/present.js +115 -0
  70. package/dist/presentation/present.js.map +1 -0
  71. package/dist/providers/mock.d.ts +3 -0
  72. package/dist/providers/mock.d.ts.map +1 -0
  73. package/dist/providers/mock.js +171 -0
  74. package/dist/providers/mock.js.map +1 -0
  75. package/dist/providers/registry.d.ts +12 -0
  76. package/dist/providers/registry.d.ts.map +1 -0
  77. package/dist/providers/registry.js +20 -0
  78. package/dist/providers/registry.js.map +1 -0
  79. package/dist/providers/types.d.ts +64 -0
  80. package/dist/providers/types.d.ts.map +1 -0
  81. package/dist/providers/types.js +40 -0
  82. package/dist/providers/types.js.map +1 -0
  83. package/dist/renderer/assets/index.html +41 -0
  84. package/dist/renderer/assets/js/actions.js +205 -0
  85. package/dist/renderer/assets/js/api.js +75 -0
  86. package/dist/renderer/assets/js/artifacts.js +422 -0
  87. package/dist/renderer/assets/js/blocks.js +301 -0
  88. package/dist/renderer/assets/js/bus.js +21 -0
  89. package/dist/renderer/assets/js/cards.js +137 -0
  90. package/dist/renderer/assets/js/dom.js +61 -0
  91. package/dist/renderer/assets/js/format.js +118 -0
  92. package/dist/renderer/assets/js/main.js +125 -0
  93. package/dist/renderer/assets/js/nav.js +80 -0
  94. package/dist/renderer/assets/js/router.js +64 -0
  95. package/dist/renderer/assets/js/store.js +69 -0
  96. package/dist/renderer/assets/js/views.js +356 -0
  97. package/dist/renderer/assets/styles.css +856 -0
  98. package/dist/renderer/content.d.ts +11 -0
  99. package/dist/renderer/content.d.ts.map +1 -0
  100. package/dist/renderer/content.js +114 -0
  101. package/dist/renderer/content.js.map +1 -0
  102. package/dist/scanner/classify.d.ts +17 -0
  103. package/dist/scanner/classify.d.ts.map +1 -0
  104. package/dist/scanner/classify.js +117 -0
  105. package/dist/scanner/classify.js.map +1 -0
  106. package/dist/scanner/git.d.ts +3 -0
  107. package/dist/scanner/git.d.ts.map +1 -0
  108. package/dist/scanner/git.js +43 -0
  109. package/dist/scanner/git.js.map +1 -0
  110. package/dist/scanner/ignore.d.ts +22 -0
  111. package/dist/scanner/ignore.d.ts.map +1 -0
  112. package/dist/scanner/ignore.js +57 -0
  113. package/dist/scanner/ignore.js.map +1 -0
  114. package/dist/scanner/scan.d.ts +8 -0
  115. package/dist/scanner/scan.d.ts.map +1 -0
  116. package/dist/scanner/scan.js +108 -0
  117. package/dist/scanner/scan.js.map +1 -0
  118. package/dist/security/boundary.d.ts +19 -0
  119. package/dist/security/boundary.d.ts.map +1 -0
  120. package/dist/security/boundary.js +59 -0
  121. package/dist/security/boundary.js.map +1 -0
  122. package/dist/security/urlPolicy.d.ts +4 -0
  123. package/dist/security/urlPolicy.d.ts.map +1 -0
  124. package/dist/security/urlPolicy.js +56 -0
  125. package/dist/security/urlPolicy.js.map +1 -0
  126. package/dist/security/workspaceRead.d.ts +25 -0
  127. package/dist/security/workspaceRead.d.ts.map +1 -0
  128. package/dist/security/workspaceRead.js +103 -0
  129. package/dist/security/workspaceRead.js.map +1 -0
  130. package/dist/semantics/load.d.ts +11 -0
  131. package/dist/semantics/load.d.ts.map +1 -0
  132. package/dist/semantics/load.js +537 -0
  133. package/dist/semantics/load.js.map +1 -0
  134. package/dist/semantics/resolve.d.ts +37 -0
  135. package/dist/semantics/resolve.d.ts.map +1 -0
  136. package/dist/semantics/resolve.js +211 -0
  137. package/dist/semantics/resolve.js.map +1 -0
  138. package/dist/semantics/types.d.ts +158 -0
  139. package/dist/semantics/types.d.ts.map +1 -0
  140. package/dist/semantics/types.js +3 -0
  141. package/dist/semantics/types.js.map +1 -0
  142. package/dist/server/server.d.ts +17 -0
  143. package/dist/server/server.d.ts.map +1 -0
  144. package/dist/server/server.js +510 -0
  145. package/dist/server/server.js.map +1 -0
  146. package/dist/server/workspace.d.ts +53 -0
  147. package/dist/server/workspace.d.ts.map +1 -0
  148. package/dist/server/workspace.js +211 -0
  149. package/dist/server/workspace.js.map +1 -0
  150. package/dist/types.d.ts +570 -0
  151. package/dist/types.d.ts.map +1 -0
  152. package/dist/types.js +19 -0
  153. package/dist/types.js.map +1 -0
  154. package/dist/util/path.d.ts +11 -0
  155. package/dist/util/path.d.ts.map +1 -0
  156. package/dist/util/path.js +35 -0
  157. package/dist/util/path.js.map +1 -0
  158. package/dist/util/safeRead.d.ts +38 -0
  159. package/dist/util/safeRead.d.ts.map +1 -0
  160. package/dist/util/safeRead.js +117 -0
  161. package/dist/util/safeRead.js.map +1 -0
  162. package/package.json +40 -0
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMockExecutor = createMockExecutor;
4
+ const types_1 = require("./types");
5
+ const SIMULATIONS = {
6
+ approve: {
7
+ taskStatus: 'succeeded',
8
+ runStatus: 'succeeded',
9
+ message: 'Approved. A downstream agent would now pick this up.',
10
+ steps: [
11
+ { title: 'Record decision', status: 'succeeded' },
12
+ { title: 'Notify owner', status: 'succeeded', detail: 'no agent connected — simulated' },
13
+ { title: 'Emit approved marker', status: 'succeeded' },
14
+ ],
15
+ },
16
+ reject: {
17
+ taskStatus: 'cancelled',
18
+ runStatus: 'succeeded',
19
+ message: 'Rejected. The task is closed without action.',
20
+ steps: [
21
+ { title: 'Record decision', status: 'succeeded' },
22
+ { title: 'Close task', status: 'succeeded' },
23
+ ],
24
+ },
25
+ retry: {
26
+ taskStatus: 'running',
27
+ runStatus: 'running',
28
+ message: 'Retry queued. A connected agent would re-run the task now.',
29
+ steps: [
30
+ { title: 'Requeue task', status: 'succeeded' },
31
+ { title: 'Dispatch to agent', status: 'pending', detail: 'no agent connected — simulated' },
32
+ ],
33
+ },
34
+ continue: {
35
+ taskStatus: 'running',
36
+ runStatus: 'running',
37
+ message: 'Continuing. Remaining work handed back to the agent.',
38
+ steps: [
39
+ { title: 'Acknowledge human input', status: 'succeeded' },
40
+ { title: 'Resume remaining steps', status: 'running' },
41
+ ],
42
+ },
43
+ run: {
44
+ taskStatus: 'running',
45
+ runStatus: 'running',
46
+ message: 'Run started (simulated).',
47
+ steps: [
48
+ { title: 'Prepare inputs', status: 'succeeded' },
49
+ { title: 'Execute', status: 'running', detail: 'simulated execution' },
50
+ ],
51
+ },
52
+ publish: {
53
+ taskStatus: 'succeeded',
54
+ runStatus: 'succeeded',
55
+ message: 'Publish simulated — nothing left this machine.',
56
+ steps: [
57
+ { title: 'Validate output', status: 'succeeded' },
58
+ { title: 'Publish', status: 'succeeded', detail: 'simulated, no external call' },
59
+ ],
60
+ },
61
+ open: {
62
+ taskStatus: 'info',
63
+ runStatus: 'succeeded',
64
+ message: 'Opened. No state change.',
65
+ steps: [{ title: 'Resolve target', status: 'succeeded' }],
66
+ },
67
+ discard: {
68
+ taskStatus: 'cancelled',
69
+ runStatus: 'succeeded',
70
+ message: 'Discarded (simulated). The artifact was not modified.',
71
+ steps: [{ title: 'Mark discarded', status: 'succeeded' }],
72
+ },
73
+ 'apply-diff': {
74
+ taskStatus: 'succeeded',
75
+ runStatus: 'succeeded',
76
+ message: 'Apply simulated. No file on disk was modified.',
77
+ steps: [
78
+ { title: 'Check patch applies', status: 'succeeded' },
79
+ { title: 'Apply patch', status: 'succeeded', detail: 'simulated, files untouched' },
80
+ ],
81
+ },
82
+ 'send-to-agent': {
83
+ taskStatus: 'running',
84
+ runStatus: 'running',
85
+ message: 'Handed to agent (simulated) — no provider connected.',
86
+ steps: [
87
+ { title: 'Package context', status: 'succeeded' },
88
+ { title: 'Deliver to agent', status: 'pending', detail: 'no provider connected' },
89
+ ],
90
+ },
91
+ };
92
+ const FALLBACK = {
93
+ taskStatus: 'succeeded',
94
+ runStatus: 'succeeded',
95
+ message: 'Action recorded (simulated).',
96
+ steps: [{ title: 'Record action', status: 'succeeded' }],
97
+ };
98
+ function createMockExecutor() {
99
+ const executor = {
100
+ id: 'mock',
101
+ description: 'Built-in simulator: changes A2H state only, never touches the system.',
102
+ handles() {
103
+ // The mock is the catch-all: it stands in for every action until a real
104
+ // provider registers itself ahead of it.
105
+ return true;
106
+ },
107
+ policy() {
108
+ // The honest answer for this executor, not a guess about the action: the
109
+ // mock changes A2H's own in-memory state and nothing else. No network,
110
+ // no shell, no writes.
111
+ //
112
+ // Note what this is *not*: a defence against a workspace that understates
113
+ // an action. A workspace can declare `sideEffect: "state"` for a publish
114
+ // and the mock will agree, because the mock really does nothing — there
115
+ // is no harm to prevent. The defence is that a *real* provider states its
116
+ // own policy and the merge takes the stricter of the two, so the same
117
+ // lie is caught the moment a provider that publishes is registered.
118
+ return { effect: 'state', confirmation: 'optional' };
119
+ },
120
+ execute(action, params, ctx) {
121
+ const sim = SIMULATIONS[action.kind] ?? FALLBACK;
122
+ const now = ctx.now.toISOString();
123
+ const steps = sim.steps.map((s, i) => ({
124
+ id: `${ctx.runId}-s${i + 1}`,
125
+ title: s.title,
126
+ status: s.status,
127
+ detail: s.detail,
128
+ at: now,
129
+ }));
130
+ const paramNote = formatParams(params);
131
+ const run = {
132
+ title: `${action.label}${action.target ? ` — ${action.target}` : ''}`,
133
+ status: sim.runStatus,
134
+ startedAt: now,
135
+ endedAt: sim.runStatus === 'running' ? undefined : now,
136
+ durationMs: sim.runStatus === 'running' ? undefined : 40,
137
+ summary: paramNote ? `${sim.message} (${paramNote})` : sim.message,
138
+ artifacts: [],
139
+ steps,
140
+ simulated: true,
141
+ blocks: [
142
+ {
143
+ type: 'notice',
144
+ tone: 'info',
145
+ text: 'This run was produced by the built-in mock executor. No agent, ' +
146
+ 'model, or shell command was involved.',
147
+ },
148
+ ],
149
+ };
150
+ return {
151
+ message: sim.message,
152
+ taskStatus: sim.taskStatus,
153
+ run,
154
+ simulated: true,
155
+ };
156
+ },
157
+ };
158
+ return (0, types_1.markSimulated)(executor);
159
+ }
160
+ function formatParams(params) {
161
+ if (!params)
162
+ return '';
163
+ const entries = Object.entries(params).filter(([, v]) => v !== '');
164
+ if (entries.length === 0)
165
+ return '';
166
+ return entries.map(([k, v]) => `${k}=${truncate(v)}`).join(', ');
167
+ }
168
+ function truncate(v) {
169
+ return v.length > 60 ? `${v.slice(0, 57)}…` : v;
170
+ }
171
+ //# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/providers/mock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type ActionExecutor, type ProviderRegistry } from './types';
2
+ export interface RegistryOptions {
3
+ extraExecutors?: ActionExecutor[];
4
+ /** Disable the built-in simulator entirely (used by tests). */
5
+ withoutMock?: boolean;
6
+ }
7
+ export declare function createDefaultRegistry(options?: RegistryOptions): ProviderRegistry;
8
+ export { createMockExecutor } from './mock';
9
+ export type { ActionExecutor, ActionResolution, ProviderRegistry, ExecutionOutcome, ActionContext, } from './types';
10
+ /** Everything a caller needs to reason about run provenance. */
11
+ export declare function describeProviders(registry: ProviderRegistry): string;
12
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAUrF,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,eAAoB,GAAG,gBAAgB,CAIrF;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,GACd,MAAM,SAAS,CAAC;AAEjB,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAEpE"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMockExecutor = void 0;
4
+ exports.createDefaultRegistry = createDefaultRegistry;
5
+ exports.describeProviders = describeProviders;
6
+ const mock_1 = require("./mock");
7
+ const types_1 = require("./types");
8
+ function createDefaultRegistry(options = {}) {
9
+ const executors = [...(options.extraExecutors ?? [])];
10
+ if (!options.withoutMock)
11
+ executors.push((0, mock_1.createMockExecutor)());
12
+ return (0, types_1.createRegistry)(executors);
13
+ }
14
+ var mock_2 = require("./mock");
15
+ Object.defineProperty(exports, "createMockExecutor", { enumerable: true, get: function () { return mock_2.createMockExecutor; } });
16
+ /** Everything a caller needs to reason about run provenance. */
17
+ function describeProviders(registry) {
18
+ return registry.executors.map((e) => `${e.id} (${e.description})`).join('; ');
19
+ }
20
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":";;;AAkBA,sDAIC;AAYD,8CAEC;AAnCD,iCAA4C;AAC5C,mCAAqF;AAgBrF,SAAgB,qBAAqB,CAAC,UAA2B,EAAE;IACjE,MAAM,SAAS,GAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC;IACxE,IAAI,CAAC,OAAO,CAAC,WAAW;QAAE,SAAS,CAAC,IAAI,CAAC,IAAA,yBAAkB,GAAE,CAAC,CAAC;IAC/D,OAAO,IAAA,sBAAc,EAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,+BAA4C;AAAnC,0GAAA,kBAAkB,OAAA;AAS3B,gEAAgE;AAChE,SAAgB,iBAAiB,CAAC,QAA0B;IAC1D,OAAO,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,64 @@
1
+ import type { ActionView, EffectPolicy, RunView } from '../types';
2
+ export interface ActionContext {
3
+ /** Workspace root, so an executor can write real artifacts if it wants to. */
4
+ workspaceRoot: string;
5
+ /** Id assigned to the run this execution will produce. */
6
+ runId: string;
7
+ now: Date;
8
+ }
9
+ export interface ExecutionOutcome {
10
+ /** Human-readable result line shown back in the UI. */
11
+ message: string;
12
+ /** New status for the owning task, when the action changes it. */
13
+ taskStatus?: string;
14
+ /** Run record to append, when the executor produced one. */
15
+ run?: Omit<RunView, 'id' | 'taskId'> & {
16
+ id?: string;
17
+ };
18
+ /** Whether the effect really happened. A simulation must say `true`. */
19
+ simulated: boolean;
20
+ }
21
+ export interface ActionExecutor {
22
+ /** Stable id, e.g. "mock" or "workbuddy". */
23
+ id: string;
24
+ /** Description shown in diagnostics, not in the main UI. */
25
+ description: string;
26
+ handles(action: ActionView): boolean;
27
+ /**
28
+ * What this executor will actually do with the action, and whether the human
29
+ * has to confirm first. Required, not optional: writing a provider means
30
+ * stating its effect, and A2H will not run one that has not said.
31
+ */
32
+ policy(action: ActionView): EffectPolicy;
33
+ execute(action: ActionView, params: Record<string, string> | undefined, ctx: ActionContext): Promise<ExecutionOutcome> | ExecutionOutcome;
34
+ }
35
+ /** The answer to "what happens if I click this?" — computed, not guessed. */
36
+ export interface ActionResolution {
37
+ executor: ActionExecutor;
38
+ /** True when this executor only simulates the effect. */
39
+ simulated: boolean;
40
+ /** The executor's authoritative policy for this action. */
41
+ policy: EffectPolicy;
42
+ }
43
+ export interface ProviderRegistry {
44
+ executors: ActionExecutor[];
45
+ /**
46
+ * Resolves the single executor that owns an action, with its provenance and
47
+ * its policy. This is the only supported way to ask "who runs this?" —
48
+ * asking the registry as a whole cannot answer it per action.
49
+ */
50
+ resolve(action: ActionView): ActionResolution | undefined;
51
+ /** Convenience for callers that only need the executor. */
52
+ findExecutor(action: ActionView): ActionExecutor | undefined;
53
+ /**
54
+ * True when *every* registered executor is a simulation, i.e. nothing real
55
+ * is connected. Never use this to label an individual action: a workspace
56
+ * can mix a real provider with the built-in simulator, and then this is
57
+ * false while some actions are still simulated. Use `resolve()`.
58
+ */
59
+ readonly allSimulated: boolean;
60
+ }
61
+ export declare function createRegistry(executors: ActionExecutor[]): ProviderRegistry;
62
+ /** Marks an executor as a simulation so the UI can say so plainly. */
63
+ export declare function markSimulated<T extends ActionExecutor>(executor: T): T;
64
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAyBlE,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,QAAQ,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;IACrC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAAC;IACzC,OAAO,CACL,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC1C,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;CACjD;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC1D,2DAA2D;IAC3D,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,GAAG,SAAS,CAAC;IAC7D;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,gBAAgB,CAmB5E;AAID,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,CAAC,SAAS,cAAc,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAGtE"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRegistry = createRegistry;
4
+ exports.markSimulated = markSimulated;
5
+ function createRegistry(executors) {
6
+ return {
7
+ executors,
8
+ resolve(action) {
9
+ const executor = executors.find((e) => e.handles(action));
10
+ if (!executor)
11
+ return undefined;
12
+ return {
13
+ executor,
14
+ simulated: isSimulated(executor),
15
+ policy: executor.policy(action),
16
+ };
17
+ },
18
+ findExecutor(action) {
19
+ return executors.find((e) => e.handles(action));
20
+ },
21
+ get allSimulated() {
22
+ return executors.length > 0 && executors.every((e) => isSimulated(e));
23
+ },
24
+ };
25
+ }
26
+ const SIMULATED = new WeakSet();
27
+ /** Marks an executor as a simulation so the UI can say so plainly. */
28
+ function markSimulated(executor) {
29
+ SIMULATED.add(executor);
30
+ return executor;
31
+ }
32
+ /**
33
+ * Registered as a simulation either by name or by declaration. The id check
34
+ * catches an executor constructed elsewhere; being wrong in this direction
35
+ * under-promises a real effect, which is the side to err on.
36
+ */
37
+ function isSimulated(executor) {
38
+ return SIMULATED.has(executor) || executor.id === 'mock';
39
+ }
40
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":";;AA2FA,wCAmBC;AAKD,sCAGC;AA3BD,SAAgB,cAAc,CAAC,SAA2B;IACxD,OAAO;QACL,SAAS;QACT,OAAO,CAAC,MAAM;YACZ,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAChC,OAAO;gBACL,QAAQ;gBACR,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC;gBAChC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;aAChC,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,MAAM;YACjB,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,YAAY;YACd,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,OAAO,EAAkB,CAAC;AAEhD,sEAAsE;AACtE,SAAgB,aAAa,CAA2B,QAAW;IACjE,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,QAAwB;IAC3C,OAAO,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="light" />
7
+ <!-- Filled in by the server at serve time with this process's session token.
8
+ Empty when the shell is opened without a server, in which case nothing
9
+ that mutates state will work — which is the intended outcome. -->
10
+ <meta name="a2h-token" content="" />
11
+ <title>A2H</title>
12
+ <link rel="stylesheet" href="/styles.css" />
13
+ </head>
14
+ <body>
15
+ <div class="app" id="app">
16
+ <button class="nav-toggle" id="navToggle" type="button" aria-label="Toggle navigation" aria-expanded="false">
17
+ <span class="nav-toggle-bar"></span>
18
+ <span class="nav-toggle-bar"></span>
19
+ <span class="nav-toggle-bar"></span>
20
+ </button>
21
+ <div class="scrim" id="scrim" hidden></div>
22
+
23
+ <aside class="sidebar" id="sidebar" aria-label="Navigation">
24
+ <div class="sidebar-inner">
25
+ <a class="brand" href="#/" id="brand">
26
+ <span class="brand-mark">A2H</span>
27
+ <span class="brand-sub">Agent&nbsp;to&nbsp;Human</span>
28
+ </a>
29
+ <nav class="nav" id="nav" aria-label="Sections"></nav>
30
+ <div class="sidebar-foot" id="sidebarFoot"></div>
31
+ </div>
32
+ </aside>
33
+
34
+ <main class="main" id="main">
35
+ <div class="content" id="content" aria-live="polite"></div>
36
+ </main>
37
+ </div>
38
+
39
+ <script type="module" src="/js/main.js"></script>
40
+ </body>
41
+ </html>
@@ -0,0 +1,205 @@
1
+ // The human's control surface.
2
+ //
3
+ // Design rules that come straight from design.md: actions are quiet buttons,
4
+ // confirmation happens inline rather than in a modal, and anything simulated
5
+ // says so plainly. A human should never be unsure whether their click left
6
+ // this machine.
7
+
8
+ import { el } from './dom.js';
9
+ import { emit } from './bus.js';
10
+ import { executeAction } from './api.js';
11
+ import { findAction, getActionResult, isPending, setActionResult, setPending } from './store.js';
12
+ import { statusTone } from './format.js';
13
+
14
+ const SIDE_EFFECT_LABEL = {
15
+ none: 'no side effect',
16
+ state: 'changes state here',
17
+ external: 'reaches outside A2H',
18
+ };
19
+
20
+ export function renderActions(ids, options = {}) {
21
+ const actions = ids.map((id) => findAction(id)).filter(Boolean);
22
+ if (actions.length === 0) return null;
23
+
24
+ const box = el('div', { class: 'actions' });
25
+ for (const action of actions) {
26
+ box.appendChild(actionRow(action, options));
27
+ }
28
+ return box;
29
+ }
30
+
31
+ function actionRow(action, options) {
32
+ const row = el('div', { class: 'action-row' });
33
+
34
+ // `compact` trims the descriptive chrome, nothing else. It used to hide the
35
+ // parameter inputs too, which made any action with required params
36
+ // impossible to run from a block: the button could only ever come back
37
+ // "missing required input". An action that asks for a value needs a field to
38
+ // put it in, wherever it is rendered.
39
+ if (!options.compact && (action.description || action.sideEffect !== 'none')) {
40
+ const meta = [];
41
+ if (action.sideEffect !== 'none') meta.push(SIDE_EFFECT_LABEL[action.sideEffect] || action.sideEffect);
42
+ if (action.simulated) meta.push('simulated');
43
+ row.appendChild(
44
+ el('div', { class: 'action-meta' }, [
45
+ action.description ? el('span', { class: 'action-desc', text: action.description }) : null,
46
+ meta.length ? el('span', { class: 'action-note', text: meta.join(' · ') }) : null,
47
+ ]),
48
+ );
49
+ }
50
+
51
+ const controls = el('div', { class: 'action-controls' });
52
+ const inputs = new Map();
53
+
54
+ if (action.params && action.params.length) {
55
+ const form = el('div', { class: 'action-params' });
56
+ for (const param of action.params) {
57
+ const input = paramInput(param, inputs);
58
+ form.appendChild(
59
+ el('label', { class: 'action-param' }, [
60
+ el('span', { class: 'action-param-label', text: param.label + (param.required ? ' *' : '') }),
61
+ input,
62
+ ]),
63
+ );
64
+ }
65
+ controls.appendChild(form);
66
+ }
67
+
68
+ const button = el('button', {
69
+ class: 'btn action-btn',
70
+ type: 'button',
71
+ disabled: !action.enabled || isPending(action.id),
72
+ text: isPending(action.id) ? 'Working…' : action.label,
73
+ });
74
+ controls.appendChild(button);
75
+
76
+ const confirmBox = el('div', { class: 'action-confirm', hidden: true });
77
+ const resultBox = el('div', { class: 'action-result' });
78
+ renderResult(resultBox, getActionResult(action.id));
79
+
80
+ button.addEventListener('click', () => {
81
+ if (action.confirm) {
82
+ confirmBox.hidden = false;
83
+ confirmBox.textContent = '';
84
+ confirmBox.appendChild(
85
+ el('span', { class: 'action-confirm-text',
86
+ text: `“${action.label}” ${SIDE_EFFECT_LABEL[action.sideEffect] || 'has a side effect'}. Continue?` }),
87
+ );
88
+ confirmBox.appendChild(
89
+ el('button', {
90
+ class: 'btn action-confirm-yes',
91
+ type: 'button',
92
+ text: 'Continue',
93
+ onclick: () => {
94
+ confirmBox.hidden = true;
95
+ run(action, inputs, button, resultBox);
96
+ },
97
+ }),
98
+ );
99
+ confirmBox.appendChild(
100
+ el('button', {
101
+ class: 'btn action-confirm-no',
102
+ type: 'button',
103
+ text: 'Cancel',
104
+ onclick: () => {
105
+ confirmBox.hidden = true;
106
+ },
107
+ }),
108
+ );
109
+ return;
110
+ }
111
+ run(action, inputs, button, resultBox);
112
+ });
113
+
114
+ row.appendChild(controls);
115
+ row.appendChild(confirmBox);
116
+ row.appendChild(resultBox);
117
+ return row;
118
+ }
119
+
120
+ function paramInput(param, inputs) {
121
+ const type = param.type || 'text';
122
+ if (type === 'textarea') {
123
+ const node = el('textarea', { class: 'input', rows: 3, placeholder: param.placeholder || '' });
124
+ if (param.default != null) node.value = String(param.default);
125
+ inputs.set(param.name, () => node.value);
126
+ return node;
127
+ }
128
+ if (type === 'select') {
129
+ const node = el('select', { class: 'input' });
130
+ for (const option of param.options || []) {
131
+ node.appendChild(el('option', { value: option, text: option }));
132
+ }
133
+ if (param.default != null) node.value = String(param.default);
134
+ inputs.set(param.name, () => node.value);
135
+ return node;
136
+ }
137
+ if (type === 'boolean') {
138
+ const node = el('input', { type: 'checkbox', class: 'checkbox' });
139
+ if (param.default === true) node.checked = true;
140
+ inputs.set(param.name, () => (node.checked ? 'true' : 'false'));
141
+ return node;
142
+ }
143
+ const node = el('input', { type: 'text', class: 'input', placeholder: param.placeholder || '' });
144
+ if (param.default != null) node.value = String(param.default);
145
+ inputs.set(param.name, () => node.value);
146
+ return node;
147
+ }
148
+
149
+ async function run(action, inputs, button, resultBox) {
150
+ const params = {};
151
+ for (const [name, read] of inputs) params[name] = read();
152
+
153
+ setPending(action.id, true);
154
+ button.disabled = true;
155
+ button.textContent = 'Working…';
156
+
157
+ let result;
158
+ try {
159
+ result = await executeAction({ id: action.id, params, confirm: true });
160
+ } catch (err) {
161
+ result = { ok: false, message: `Could not reach the server: ${err.message}`, simulated: false };
162
+ }
163
+
164
+ setPending(action.id, false);
165
+ setActionResult(action.id, result);
166
+ button.disabled = !action.enabled;
167
+ button.textContent = action.label;
168
+ renderResult(resultBox, result);
169
+
170
+ if (result.ok) {
171
+ // Task status and runs changed on the server — pull the new presentation.
172
+ emit('refresh');
173
+ }
174
+ }
175
+
176
+ function renderResult(box, result) {
177
+ box.textContent = '';
178
+ if (!result) return;
179
+ const tone = result.ok ? statusTone('succeeded') : statusTone('failed');
180
+ box.appendChild(
181
+ el('div', { class: `action-outcome tone-${tone}` }, [
182
+ el('span', { class: 'action-outcome-text', text: result.message || (result.ok ? 'Done.' : 'Failed.') }),
183
+ result.ok && result.simulated ? el('span', { class: 'action-sim', text: 'simulated' }) : null,
184
+ ]),
185
+ );
186
+ }
187
+
188
+ /** Compact audit trail, shown on the actions page. */
189
+ export function renderAudit(entries) {
190
+ if (!entries || entries.length === 0) {
191
+ return el('p', { class: 'empty', text: 'No actions have been taken in this session.' });
192
+ }
193
+ const list = el('div', { class: 'audit' });
194
+ for (const entry of entries) {
195
+ list.appendChild(
196
+ el('div', { class: `audit-row${entry.ok ? '' : ' is-failed'}` }, [
197
+ el('span', { class: 'audit-time', text: new Date(entry.at).toLocaleTimeString() }),
198
+ el('span', { class: 'audit-action', text: entry.actionId }),
199
+ el('span', { class: 'audit-executor', text: entry.executor }),
200
+ el('span', { class: 'audit-message', text: entry.message }),
201
+ ]),
202
+ );
203
+ }
204
+ return list;
205
+ }
@@ -0,0 +1,75 @@
1
+ // Server communication. The viewer talks to exactly four endpoints; keeping
2
+ // them in one file makes the surface easy to audit.
3
+
4
+ let presentationPromise = null;
5
+
6
+ export function loadPresentation(force) {
7
+ if (!force && presentationPromise) return presentationPromise;
8
+ presentationPromise = fetchJson('/api/presentation');
9
+ return presentationPromise;
10
+ }
11
+
12
+ export function invalidate() {
13
+ presentationPromise = null;
14
+ }
15
+
16
+ export function loadArtifact(path, mode) {
17
+ const suffix = mode === 'full' ? '&mode=full' : '';
18
+ return fetchJson(`/api/artifact?path=${encodeURIComponent(path)}${suffix}`);
19
+ }
20
+
21
+ /**
22
+ * Executes a declared action. The server owns the allowlist and the
23
+ * confirmation boundary; this function only carries the request.
24
+ *
25
+ * Mutating requests have to present the session token the server injected into
26
+ * this page. It is not a password and not a login — it exists so that another
27
+ * page in the same browser cannot forge a request to a localhost control API,
28
+ * which is exactly what makes it worth carrying on every call rather than
29
+ * fetching once and trusting the result.
30
+ */
31
+ export async function executeAction(payload) {
32
+ const res = await fetch('/api/action', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json', 'X-A2H-Token': sessionToken() },
35
+ body: JSON.stringify(payload),
36
+ });
37
+ let body;
38
+ try {
39
+ body = await res.json();
40
+ } catch {
41
+ body = { ok: false, error: 'bad_response', message: 'The server returned an unreadable response.' };
42
+ }
43
+ return body;
44
+ }
45
+
46
+ function sessionToken() {
47
+ const meta = document.querySelector('meta[name="a2h-token"]');
48
+ return meta ? meta.getAttribute('content') || '' : '';
49
+ }
50
+
51
+ /** Subscribes to workspace change / action events. Returns a close function. */
52
+ export function openEvents(handlers) {
53
+ let source;
54
+ try {
55
+ source = new EventSource('/api/events');
56
+ } catch {
57
+ return () => {};
58
+ }
59
+ source.addEventListener('change', () => handlers.onChange && handlers.onChange());
60
+ source.addEventListener('action', () => handlers.onAction && handlers.onAction());
61
+ source.onerror = () => {
62
+ /* EventSource reconnects on its own. */
63
+ };
64
+ return () => source.close();
65
+ }
66
+
67
+ async function fetchJson(url) {
68
+ const res = await fetch(url, { headers: { Accept: 'application/json' } });
69
+ if (!res.ok) {
70
+ const error = new Error(`${res.status}`);
71
+ error.status = res.status;
72
+ throw error;
73
+ }
74
+ return res.json();
75
+ }