@appliqation/agent-core 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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/dist/appq/index.d.ts +3 -0
  4. package/dist/appq/index.d.ts.map +1 -0
  5. package/dist/appq/index.js +3 -0
  6. package/dist/appq/index.js.map +1 -0
  7. package/dist/appq/mcpClient.d.ts +52 -0
  8. package/dist/appq/mcpClient.d.ts.map +1 -0
  9. package/dist/appq/mcpClient.js +79 -0
  10. package/dist/appq/mcpClient.js.map +1 -0
  11. package/dist/appq/scenarioResolvers.d.ts +60 -0
  12. package/dist/appq/scenarioResolvers.d.ts.map +1 -0
  13. package/dist/appq/scenarioResolvers.js +114 -0
  14. package/dist/appq/scenarioResolvers.js.map +1 -0
  15. package/dist/audit/index.d.ts +2 -0
  16. package/dist/audit/index.d.ts.map +1 -0
  17. package/dist/audit/index.js +2 -0
  18. package/dist/audit/index.js.map +1 -0
  19. package/dist/audit/sink.d.ts +69 -0
  20. package/dist/audit/sink.d.ts.map +1 -0
  21. package/dist/audit/sink.js +93 -0
  22. package/dist/audit/sink.js.map +1 -0
  23. package/dist/config/helpers.d.ts +3 -0
  24. package/dist/config/helpers.d.ts.map +1 -0
  25. package/dist/config/helpers.js +16 -0
  26. package/dist/config/helpers.js.map +1 -0
  27. package/dist/config/index.d.ts +2 -0
  28. package/dist/config/index.d.ts.map +1 -0
  29. package/dist/config/index.js +2 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/engine/budget.d.ts +18 -0
  32. package/dist/engine/budget.d.ts.map +1 -0
  33. package/dist/engine/budget.js +31 -0
  34. package/dist/engine/budget.js.map +1 -0
  35. package/dist/engine/index.d.ts +4 -0
  36. package/dist/engine/index.d.ts.map +1 -0
  37. package/dist/engine/index.js +4 -0
  38. package/dist/engine/index.js.map +1 -0
  39. package/dist/engine/loop.d.ts +20 -0
  40. package/dist/engine/loop.d.ts.map +1 -0
  41. package/dist/engine/loop.js +60 -0
  42. package/dist/engine/loop.js.map +1 -0
  43. package/dist/engine/workflowRunner.d.ts +27 -0
  44. package/dist/engine/workflowRunner.d.ts.map +1 -0
  45. package/dist/engine/workflowRunner.js +31 -0
  46. package/dist/engine/workflowRunner.js.map +1 -0
  47. package/dist/evidence/capture.d.ts +53 -0
  48. package/dist/evidence/capture.d.ts.map +1 -0
  49. package/dist/evidence/capture.js +80 -0
  50. package/dist/evidence/capture.js.map +1 -0
  51. package/dist/evidence/index.d.ts +2 -0
  52. package/dist/evidence/index.d.ts.map +1 -0
  53. package/dist/evidence/index.js +2 -0
  54. package/dist/evidence/index.js.map +1 -0
  55. package/dist/index.d.ts +9 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +9 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/providers/anthropic.d.ts +4 -0
  60. package/dist/providers/anthropic.d.ts.map +1 -0
  61. package/dist/providers/anthropic.js +113 -0
  62. package/dist/providers/anthropic.js.map +1 -0
  63. package/dist/providers/index.d.ts +3 -0
  64. package/dist/providers/index.d.ts.map +1 -0
  65. package/dist/providers/index.js +3 -0
  66. package/dist/providers/index.js.map +1 -0
  67. package/dist/providers/openai.d.ts +4 -0
  68. package/dist/providers/openai.d.ts.map +1 -0
  69. package/dist/providers/openai.js +87 -0
  70. package/dist/providers/openai.js.map +1 -0
  71. package/dist/tools/apiTools.d.ts +21 -0
  72. package/dist/tools/apiTools.d.ts.map +1 -0
  73. package/dist/tools/apiTools.js +82 -0
  74. package/dist/tools/apiTools.js.map +1 -0
  75. package/dist/tools/authState.d.ts +21 -0
  76. package/dist/tools/authState.d.ts.map +1 -0
  77. package/dist/tools/authState.js +29 -0
  78. package/dist/tools/authState.js.map +1 -0
  79. package/dist/tools/browserTools.d.ts +35 -0
  80. package/dist/tools/browserTools.d.ts.map +1 -0
  81. package/dist/tools/browserTools.js +313 -0
  82. package/dist/tools/browserTools.js.map +1 -0
  83. package/dist/tools/destructiveActionGate.d.ts +10 -0
  84. package/dist/tools/destructiveActionGate.d.ts.map +1 -0
  85. package/dist/tools/destructiveActionGate.js +40 -0
  86. package/dist/tools/destructiveActionGate.js.map +1 -0
  87. package/dist/tools/gatedDispatcher.d.ts +15 -0
  88. package/dist/tools/gatedDispatcher.d.ts.map +1 -0
  89. package/dist/tools/gatedDispatcher.js +39 -0
  90. package/dist/tools/gatedDispatcher.js.map +1 -0
  91. package/dist/tools/index.d.ts +8 -0
  92. package/dist/tools/index.d.ts.map +1 -0
  93. package/dist/tools/index.js +8 -0
  94. package/dist/tools/index.js.map +1 -0
  95. package/dist/tools/projectContext.d.ts +4 -0
  96. package/dist/tools/projectContext.d.ts.map +1 -0
  97. package/dist/tools/projectContext.js +37 -0
  98. package/dist/tools/projectContext.js.map +1 -0
  99. package/dist/tools/roleInference.d.ts +65 -0
  100. package/dist/tools/roleInference.d.ts.map +1 -0
  101. package/dist/tools/roleInference.js +128 -0
  102. package/dist/tools/roleInference.js.map +1 -0
  103. package/dist/types.d.ts +78 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +3 -0
  106. package/dist/types.js.map +1 -0
  107. package/package.json +74 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Appliqation Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @appliqation/agent-core
2
+
3
+ **The shared engine every standalone Appliqation agent is built from.** Not an app on its own — no CLI, no `.env` — just the generic, workflow-agnostic pieces that [`appliqation-autotest`](https://github.com/appliqation/appliqation-autotest), [`appliqation-scriptgen`](https://github.com/appliqation/appliqation-scriptgen), and [`appliqation-defect-fix`](https://github.com/appliqation/appliqation-defect-fix) each build on:
4
+
5
+ - **`/engine`** — the think→act→observe tool-calling loop, budget tracking (calls/pages/time/turns), and a workflow runner that fetches a named prompt (from Appliqation's MCP server or a local file) and runs it through that loop.
6
+ - **`/appq`** — a JSON-RPC client for Appliqation's MCP API, plus scenario/test-set/run resolvers that always derive `project_id`/`url` from a source of truth (a scenario ID, a TC UUID) rather than accepting a second, possibly-diverging input.
7
+ - **`/providers`** — Anthropic and OpenAI adapters implementing one common interface, including prompt caching and image-handling for both wire formats.
8
+ - **`/tools`** — a Playwright-backed browser-tool palette, the tool-allowlist gating mechanism every agent's own safety boundary is built on, a destructive-action click gate, auth-session resolution, and per-test-case role inference.
9
+ - **`/evidence`** — screenshot/console/network/accessibility-snapshot capture with cursor-based delta reads.
10
+
11
+ ## Design principle: mechanism shared, domain knowledge local
12
+
13
+ Every module here is either genuinely universal, or an explicit injection point so a consuming agent can supply its own domain knowledge without this package needing to know about it. Two examples:
14
+
15
+ - **Tool-dispatch gating** (`tools/gatedDispatcher.ts`) enforces "only call tools in this allowlist" — but the allowlist *itself* (which tools an executor vs. validator vs. any other stage may touch) is each consuming agent's own knowledge, kept in that agent's own code, never here.
16
+ - **Browser tools** (`tools/browserTools.ts`) take optional `{onBeforeClick, screenshotSink}` hooks instead of hardcoding a destructive-action policy or an upload destination — a consuming agent with no write access, or a different safety posture, supplies its own.
17
+
18
+ ## Installation
19
+
20
+ Not yet published to npm — consumed via a `file:` dependency by every agent in this family until it is:
21
+
22
+ ```json
23
+ "dependencies": {
24
+ "@appliqation/agent-core": "file:../appliqation-agent-core"
25
+ }
26
+ ```
27
+
28
+ ## Development
29
+
30
+ ```bash
31
+ npm run build
32
+ npm run typecheck
33
+ npm test
34
+ ```
35
+
36
+ See `CLAUDE.md` for a map of this repo if you're working in it with an AI coding assistant.
37
+
38
+ ## License
39
+
40
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,3 @@
1
+ export { createMcpClient, type McpClient, type McpClientOptions, type ToolCallOutcome, type McpTextContent, type McpMessage } from './mcpClient.js';
2
+ export { resolveRun, scenarioIdFromTcUuid, resolveScenarioId, fetchScenarioInfo, fetchTestSetInfo, resolveUrl } from './scenarioResolvers.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/appq/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACpJ,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createMcpClient } from './mcpClient.js';
2
+ export { resolveRun, scenarioIdFromTcUuid, resolveScenarioId, fetchScenarioInfo, fetchTestSetInfo, resolveUrl } from './scenarioResolvers.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/appq/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqG,MAAM,gBAAgB,CAAC;AACpJ,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,52 @@
1
+ export interface McpClientOptions {
2
+ origin: string;
3
+ apiKey: string;
4
+ }
5
+ export interface McpTextContent {
6
+ type: 'text';
7
+ text: string;
8
+ }
9
+ export interface McpMessage {
10
+ role: string;
11
+ content: McpTextContent | {
12
+ type: string;
13
+ [k: string]: unknown;
14
+ };
15
+ }
16
+ export interface ToolCallOutcome {
17
+ ok: boolean;
18
+ text: string;
19
+ raw?: unknown;
20
+ }
21
+ export interface McpClient {
22
+ /** Fetches a named appq workflow's prose via the standard MCP `prompts/get` method. */
23
+ fetchPrompt(name: string, args?: Record<string, unknown>): Promise<string>;
24
+ /**
25
+ * Fetches a named workflow via the `start_workflow` tool instead of
26
+ * `prompts/get`. Functionally identical content (both terminate in
27
+ * McpPromptRegistry::buildMessages() server-side) — this path additionally
28
+ * carries the directive prefix appq prepends for non-slash-command clients.
29
+ * Prefer fetchPrompt() unless you specifically want that directive framing.
30
+ */
31
+ startWorkflow(name: string, args?: Record<string, unknown>): Promise<string>;
32
+ /** Generic `tools/call` — the one entry point every appq MCP tool goes through. */
33
+ callTool(name: string, args: Record<string, unknown>): Promise<ToolCallOutcome>;
34
+ listTools(): Promise<Array<{
35
+ name: string;
36
+ description: string;
37
+ inputSchema: unknown;
38
+ }>>;
39
+ /**
40
+ * Claims a locally-captured screenshot for attachment (create_defect's
41
+ * screenshot_upload_id, or submit_execution_evidence's), via appq's
42
+ * two-step upload flow: POST the blob, get an upload_id back.
43
+ *
44
+ * Content-Type MUST be one appq's endpoint whitelists (image/png, jpeg,
45
+ * gif, webp) — it validates both the header and the actual magic bytes
46
+ * against it and rejects anything else with 415. application/octet-stream
47
+ * (a very natural first guess for "raw binary") fails this every time.
48
+ */
49
+ uploadScreenshot(pngBuffer: Buffer, label: string): Promise<string>;
50
+ }
51
+ export declare function createMcpClient(opts: McpClientOptions): McpClient;
52
+ //# sourceMappingURL=mcpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpClient.d.ts","sourceRoot":"","sources":["../../src/appq/mcpClient.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAClE;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,uFAAuF;IACvF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E;;;;;;OAMG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAChF,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAC;IACzF;;;;;;;;;OASG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAkFjE"}
@@ -0,0 +1,79 @@
1
+ // JSON-RPC client for appq's MCP server (POST /api/appq/mcp). A factory, not
2
+ // a module-level singleton — each consuming agent has its own .env/config
3
+ // shape, so origin/apiKey are supplied by the caller rather than read from a
4
+ // shared global.
5
+ export function createMcpClient(opts) {
6
+ let requestId = 0;
7
+ async function rpc(method, params) {
8
+ const id = ++requestId;
9
+ const res = await fetch(`${opts.origin}/api/appq/mcp`, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ 'X-API-Key': opts.apiKey,
14
+ },
15
+ body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
16
+ });
17
+ if (!res.ok) {
18
+ throw new Error(`appq MCP HTTP ${res.status}: ${await res.text()}`);
19
+ }
20
+ const body = (await res.json());
21
+ if (body.error) {
22
+ throw new Error(`appq MCP error ${body.error.code}: ${body.error.message}`);
23
+ }
24
+ if (body.result === undefined) {
25
+ throw new Error('appq MCP response had no result and no error');
26
+ }
27
+ return body.result;
28
+ }
29
+ const client = {
30
+ async fetchPrompt(name, args = {}) {
31
+ const result = await rpc('prompts/get', { name, arguments: args });
32
+ const text = result.messages
33
+ .map((m) => (m.content.type === 'text' ? m.content.text : ''))
34
+ .filter(Boolean)
35
+ .join('\n\n');
36
+ if (!text)
37
+ throw new Error(`appq prompt "${name}" returned no text content`);
38
+ return text;
39
+ },
40
+ async startWorkflow(name, args = {}) {
41
+ const result = await client.callTool('start_workflow', { name, arguments: args });
42
+ if (!result.ok)
43
+ throw new Error(`start_workflow "${name}" failed: ${result.text}`);
44
+ return result.text;
45
+ },
46
+ async callTool(name, args) {
47
+ const result = await rpc('tools/call', {
48
+ name,
49
+ arguments: args,
50
+ });
51
+ const text = result.content
52
+ .map((c) => (c.type === 'text' ? c.text : ''))
53
+ .filter(Boolean)
54
+ .join('\n');
55
+ return { ok: !result.isError, text, raw: result };
56
+ },
57
+ async listTools() {
58
+ const result = await rpc('tools/list', {});
59
+ return result.tools;
60
+ },
61
+ async uploadScreenshot(pngBuffer, label) {
62
+ const res = await fetch(`${opts.origin}/api/appq/mcp/upload-screenshot`, {
63
+ method: 'POST',
64
+ headers: {
65
+ 'X-API-Key': opts.apiKey,
66
+ 'Content-Type': 'image/png',
67
+ 'X-Screenshot-Label': label,
68
+ },
69
+ body: new Uint8Array(pngBuffer),
70
+ });
71
+ if (!res.ok)
72
+ throw new Error(`screenshot upload failed: HTTP ${res.status}`);
73
+ const body = (await res.json());
74
+ return body.upload_id;
75
+ },
76
+ };
77
+ return client;
78
+ }
79
+ //# sourceMappingURL=mcpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpClient.js","sourceRoot":"","sources":["../../src/appq/mcpClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { McpClient } from './mcpClient.js';
2
+ import type { TcInfo } from '../tools/roleInference.js';
3
+ export declare function resolveRun(client: McpClient, opts: {
4
+ runId?: string;
5
+ scenarioId?: string;
6
+ projectId?: string;
7
+ environment?: string;
8
+ }): Promise<string>;
9
+ /** A TC UUID is always "{scenario_id}-{uuid4}" — appq's own tools parse it the same way (e.g. CreateDefectTool). */
10
+ export declare function scenarioIdFromTcUuid(tcUuid: string): number;
11
+ /**
12
+ * scenario_id is never accepted as a separate input alongside a TC UUID —
13
+ * it's mathematically embedded in the UUID, so a caller-supplied value
14
+ * could only ever be a stale/typo'd duplicate, never a legitimate override.
15
+ * In whole-scenario mode there's nothing to derive it from, so scenarioId is
16
+ * the one genuinely required primary input there.
17
+ */
18
+ export declare function resolveScenarioId(opts: {
19
+ scenarioId?: string;
20
+ testCaseUuid?: string;
21
+ }): number;
22
+ /**
23
+ * project_id is always derived, never accepted as a separate input — a
24
+ * scenario belongs to exactly one project, so a caller-supplied value that
25
+ * diverges from the real one can only be wrong. get_scenario needs only
26
+ * scenario_id; its response always includes "Project ID: N" plus each TC's
27
+ * name/UUID/tag — fetched once and reused for role inference too, rather
28
+ * than a second call for the same data.
29
+ */
30
+ export declare function fetchScenarioInfo(client: McpClient, scenarioId: number): Promise<{
31
+ projectId: number;
32
+ tcs: TcInfo[];
33
+ }>;
34
+ /**
35
+ * project_id is always derived here too, same reasoning as
36
+ * fetchScenarioInfo() — get_test_set already returns "Project ID: N" since
37
+ * a test set belongs to exactly one project, even though its test cases
38
+ * can span multiple *scenarios* (that's the whole point of a test set —
39
+ * a curated cross-scenario collection, e.g. a regression/smoke/sanity
40
+ * suite for CI). Each returned TcInfo's own scenario_id is NOT included as
41
+ * a separate field — derive it per-TC via scenarioIdFromTcUuid() when
42
+ * needed, the same single source of truth every other TC-scoped
43
+ * resolution in this package already trusts, rather than a second parsed
44
+ * value that could quietly disagree with it.
45
+ */
46
+ export declare function fetchTestSetInfo(client: McpClient, testSetId: number): Promise<{
47
+ projectId: number;
48
+ tcs: TcInfo[];
49
+ }>;
50
+ /**
51
+ * url is always derived from an environment name, never accepted as a
52
+ * separate input. Unlike project_id (which create_run itself validates
53
+ * against the scenario and rejects on mismatch), there's no server-side
54
+ * check that a caller-supplied URL actually matches the named environment —
55
+ * a diverging value would silently test the wrong target while the run gets
56
+ * recorded against a different environment, with nothing to catch it.
57
+ * get_project_settings already stores a URL per named environment.
58
+ */
59
+ export declare function resolveUrl(client: McpClient, environment: string, projectId: number): Promise<string>;
60
+ //# sourceMappingURL=scenarioResolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarioResolvers.d.ts","sourceRoot":"","sources":["../../src/appq/scenarioResolvers.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAGxD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,oHAAoH;AACpH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAI9F;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAO5H;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAO1H;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW3G"}
@@ -0,0 +1,114 @@
1
+ // Scenario/project/run/url resolution against appq's MCP tools. Pure
2
+ // appq-response-shape parsing, no agent-specific meaning — every consuming
3
+ // agent needs the identical scenario/project/url derivation, including the
4
+ // "never accept project_id/url as separate, possibly-diverging inputs"
5
+ // invariant: a scenario belongs to exactly one project, and there's no
6
+ // server-side check that a caller-supplied URL actually matches a named
7
+ // environment — a diverging value would silently test the wrong target. Take
8
+ // an McpClient explicitly rather than importing a global one, matching
9
+ // mcpClient.ts's factory-not-singleton design.
10
+ import { parseScenarioTcList, parseTestSetTcList } from '../tools/roleInference.js';
11
+ export async function resolveRun(client, opts) {
12
+ if (opts.runId)
13
+ return opts.runId;
14
+ if (!opts.scenarioId || !opts.projectId) {
15
+ throw new Error('--scenario-id and --project-id are required to create a run (or pass --run-id to reuse one).');
16
+ }
17
+ const created = await client.callTool('update_run_results', {
18
+ action: 'create_run',
19
+ scenario_id: Number(opts.scenarioId),
20
+ project_id: Number(opts.projectId),
21
+ ...(opts.environment ? { environment: opts.environment } : {}),
22
+ });
23
+ if (!created.ok)
24
+ throw new Error(`Failed to create run: ${created.text}`);
25
+ const parsed = JSON.parse(created.text);
26
+ console.error(`[setup] created run ${parsed.run_id}`);
27
+ return parsed.run_id;
28
+ }
29
+ /** A TC UUID is always "{scenario_id}-{uuid4}" — appq's own tools parse it the same way (e.g. CreateDefectTool). */
30
+ export function scenarioIdFromTcUuid(tcUuid) {
31
+ const prefix = tcUuid.split('-', 1)[0];
32
+ const id = Number(prefix);
33
+ if (!Number.isFinite(id) || id <= 0) {
34
+ throw new Error(`Could not derive a scenario ID from test case UUID "${tcUuid}" — expected "{scenario_id}-{uuid4}".`);
35
+ }
36
+ return id;
37
+ }
38
+ /**
39
+ * scenario_id is never accepted as a separate input alongside a TC UUID —
40
+ * it's mathematically embedded in the UUID, so a caller-supplied value
41
+ * could only ever be a stale/typo'd duplicate, never a legitimate override.
42
+ * In whole-scenario mode there's nothing to derive it from, so scenarioId is
43
+ * the one genuinely required primary input there.
44
+ */
45
+ export function resolveScenarioId(opts) {
46
+ if (opts.testCaseUuid)
47
+ return scenarioIdFromTcUuid(opts.testCaseUuid);
48
+ if (opts.scenarioId)
49
+ return Number(opts.scenarioId);
50
+ throw new Error('--scenario-id is required in whole-scenario mode (no --test-case-uuid given).');
51
+ }
52
+ /**
53
+ * project_id is always derived, never accepted as a separate input — a
54
+ * scenario belongs to exactly one project, so a caller-supplied value that
55
+ * diverges from the real one can only be wrong. get_scenario needs only
56
+ * scenario_id; its response always includes "Project ID: N" plus each TC's
57
+ * name/UUID/tag — fetched once and reused for role inference too, rather
58
+ * than a second call for the same data.
59
+ */
60
+ export async function fetchScenarioInfo(client, scenarioId) {
61
+ const result = await client.callTool('get_scenario', { scenario_id: scenarioId });
62
+ if (!result.ok)
63
+ throw new Error(`get_scenario failed while resolving scenario ${scenarioId}: ${result.text}`);
64
+ const match = result.text.match(/Project ID:\s*(\d+)/);
65
+ if (!match)
66
+ throw new Error(`Could not find a project ID in get_scenario's response for scenario ${scenarioId}.`);
67
+ console.error(`[setup] project ${match[1]} (scenario ${scenarioId})`);
68
+ return { projectId: Number(match[1]), tcs: parseScenarioTcList(result.text) };
69
+ }
70
+ /**
71
+ * project_id is always derived here too, same reasoning as
72
+ * fetchScenarioInfo() — get_test_set already returns "Project ID: N" since
73
+ * a test set belongs to exactly one project, even though its test cases
74
+ * can span multiple *scenarios* (that's the whole point of a test set —
75
+ * a curated cross-scenario collection, e.g. a regression/smoke/sanity
76
+ * suite for CI). Each returned TcInfo's own scenario_id is NOT included as
77
+ * a separate field — derive it per-TC via scenarioIdFromTcUuid() when
78
+ * needed, the same single source of truth every other TC-scoped
79
+ * resolution in this package already trusts, rather than a second parsed
80
+ * value that could quietly disagree with it.
81
+ */
82
+ export async function fetchTestSetInfo(client, testSetId) {
83
+ const result = await client.callTool('get_test_set', { testset_id: testSetId });
84
+ if (!result.ok)
85
+ throw new Error(`get_test_set failed while resolving test set ${testSetId}: ${result.text}`);
86
+ const match = result.text.match(/Project ID:\s*(\d+)/);
87
+ if (!match)
88
+ throw new Error(`Could not find a project ID in get_test_set's response for test set ${testSetId}.`);
89
+ console.error(`[setup] project ${match[1]} (test set ${testSetId})`);
90
+ return { projectId: Number(match[1]), tcs: parseTestSetTcList(result.text) };
91
+ }
92
+ /**
93
+ * url is always derived from an environment name, never accepted as a
94
+ * separate input. Unlike project_id (which create_run itself validates
95
+ * against the scenario and rejects on mismatch), there's no server-side
96
+ * check that a caller-supplied URL actually matches the named environment —
97
+ * a diverging value would silently test the wrong target while the run gets
98
+ * recorded against a different environment, with nothing to catch it.
99
+ * get_project_settings already stores a URL per named environment.
100
+ */
101
+ export async function resolveUrl(client, environment, projectId) {
102
+ const result = await client.callTool('get_project_settings', { project_id: projectId });
103
+ if (!result.ok)
104
+ throw new Error(`get_project_settings failed while resolving the URL for environment "${environment}": ${result.text}`);
105
+ const settings = JSON.parse(result.text);
106
+ const env = (settings.environments ?? []).find((e) => e.name === environment);
107
+ if (!env) {
108
+ const available = (settings.environments ?? []).map((e) => e.name).join(', ') || '(none configured)';
109
+ throw new Error(`No environment named "${environment}" on project ${projectId}. Available: ${available}`);
110
+ }
111
+ console.error(`[setup] url ${env.url} (environment "${environment}")`);
112
+ return env.url;
113
+ }
114
+ //# sourceMappingURL=scenarioResolvers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarioResolvers.js","sourceRoot":"","sources":["../../src/appq/scenarioResolvers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { type AuditRecord, type AuditSink, type AuditEnv, noopAuditSink, createJsonlAuditSink, createMongoAuditSink, resolveAuditSink, safeRecord, createUsageAccumulator, } from './sink.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,sBAAsB,GACvB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { noopAuditSink, createJsonlAuditSink, createMongoAuditSink, resolveAuditSink, safeRecord, createUsageAccumulator, } from './sink.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,sBAAsB,GACvB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,69 @@
1
+ export interface AuditRecord {
2
+ /** e.g. 'appliqation-autotest', 'appliqation-autopilot'. */
3
+ agent: string;
4
+ /** e.g. 'judge', 'generate', 'fix', 'explore', 'raise', 'run'. */
5
+ subcommand: string;
6
+ startedAt: number;
7
+ endedAt: number;
8
+ durationMillis: number;
9
+ /** Absent for agents with no LLM loop at all (appliqation-pr-raise). */
10
+ model?: string;
11
+ usage?: {
12
+ inputTokens: number;
13
+ outputTokens: number;
14
+ cacheWriteTokens?: number;
15
+ cacheReadTokens?: number;
16
+ };
17
+ turns?: number;
18
+ budgetExceeded?: boolean;
19
+ exitCode: number;
20
+ /** The CLI's own already-built --json summary object, verbatim — no second schema to maintain. */
21
+ outcome: Record<string, unknown>;
22
+ }
23
+ export interface AuditSink {
24
+ record(entry: AuditRecord): Promise<void>;
25
+ }
26
+ export declare const noopAuditSink: AuditSink;
27
+ export declare function createJsonlAuditSink(opts: {
28
+ filePath: string;
29
+ }): AuditSink;
30
+ export declare function createMongoAuditSink(opts: {
31
+ uri: string;
32
+ dbName: string;
33
+ collection: string;
34
+ }): AuditSink;
35
+ /**
36
+ * The one function CLIs should actually call — never `sink.record()` directly.
37
+ * An audit write is observability, not part of the task it's describing: a
38
+ * failure here (a down Mongo instance, a bad path) must never affect the real
39
+ * run's exit code or output, so it's caught and logged as a warning, not
40
+ * propagated. Same reasoning as browserTools.ts's screenshot-staging failure
41
+ * already being non-fatal.
42
+ */
43
+ export declare function safeRecord(sink: AuditSink, entry: AuditRecord): Promise<void>;
44
+ export interface AuditEnv {
45
+ auditMongoUri?: string;
46
+ auditMongoDb?: string;
47
+ auditMongoCollection?: string;
48
+ auditJsonlPath?: string;
49
+ }
50
+ /**
51
+ * Precedence mirrors resolveProvider()'s own shape: Mongo (if fully
52
+ * configured) > JSONL > noop. Nothing writes anywhere unless explicitly
53
+ * configured — same BYO-credential/optional posture as every LLM key and
54
+ * resolveApiAuth() elsewhere in this family.
55
+ */
56
+ export declare function resolveAuditSink(env: AuditEnv): AuditSink;
57
+ interface UsageDelta {
58
+ inputTokens: number;
59
+ outputTokens: number;
60
+ cacheWriteTokens?: number;
61
+ cacheReadTokens?: number;
62
+ }
63
+ /** Sums usage across however many 'usage' onEvent callbacks a run emits into one invocation-level total. */
64
+ export declare function createUsageAccumulator(): {
65
+ onUsage(u: UsageDelta): void;
66
+ totals(): AuditRecord["usage"];
67
+ };
68
+ export {};
69
+ //# sourceMappingURL=sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/audit/sink.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,eAAO,MAAM,aAAa,EAAE,SAI3B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM1E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAqBzG;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnF;AAED,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,CASzD;AAED,UAAU,UAAU;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,4GAA4G;AAC5G,wBAAgB,sBAAsB;eAOvB,UAAU,GAAG,IAAI;cAMlB,WAAW,CAAC,OAAO,CAAC;EAIjC"}
@@ -0,0 +1,93 @@
1
+ // Observability, not a knowledge channel: an AuditRecord is a record of what
2
+ // an invocation did, for a human to read later (a dashboard, an ad-hoc query)
3
+ // — never something fed back into a future agent's own decision-making, which
4
+ // is exactly what keeps this distinct from the write-trust boundary
5
+ // enrich_project_context's action=write sits behind (see tools/projectContext.ts).
6
+ // A write here failing must never affect the real task it's describing — see
7
+ // safeRecord() below, the one function every CLI should actually call.
8
+ import { MongoClient } from 'mongodb';
9
+ import { appendFile } from 'node:fs/promises';
10
+ export const noopAuditSink = {
11
+ async record() {
12
+ // Intentionally does nothing — the default when no sink is configured.
13
+ },
14
+ };
15
+ export function createJsonlAuditSink(opts) {
16
+ return {
17
+ async record(entry) {
18
+ await appendFile(opts.filePath, JSON.stringify(entry) + '\n', 'utf8');
19
+ },
20
+ };
21
+ }
22
+ export function createMongoAuditSink(opts) {
23
+ let collectionPromise;
24
+ function getCollection() {
25
+ // Lazy-connect-once: the client is created and connected on the first
26
+ // record() call, then reused — a CLI invocation that never calls
27
+ // record() (e.g. audit unconfigured elsewhere in the same process)
28
+ // never opens a connection at all.
29
+ if (!collectionPromise) {
30
+ const client = new MongoClient(opts.uri);
31
+ collectionPromise = client.connect().then(() => client.db(opts.dbName).collection(opts.collection));
32
+ }
33
+ return collectionPromise;
34
+ }
35
+ return {
36
+ async record(entry) {
37
+ const collection = await getCollection();
38
+ await collection.insertOne(entry);
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * The one function CLIs should actually call — never `sink.record()` directly.
44
+ * An audit write is observability, not part of the task it's describing: a
45
+ * failure here (a down Mongo instance, a bad path) must never affect the real
46
+ * run's exit code or output, so it's caught and logged as a warning, not
47
+ * propagated. Same reasoning as browserTools.ts's screenshot-staging failure
48
+ * already being non-fatal.
49
+ */
50
+ export async function safeRecord(sink, entry) {
51
+ try {
52
+ await sink.record(entry);
53
+ }
54
+ catch (err) {
55
+ console.error(`[audit] failed to record: ${err.message}`);
56
+ }
57
+ }
58
+ /**
59
+ * Precedence mirrors resolveProvider()'s own shape: Mongo (if fully
60
+ * configured) > JSONL > noop. Nothing writes anywhere unless explicitly
61
+ * configured — same BYO-credential/optional posture as every LLM key and
62
+ * resolveApiAuth() elsewhere in this family.
63
+ */
64
+ export function resolveAuditSink(env) {
65
+ if (env.auditMongoUri) {
66
+ if (!env.auditMongoDb || !env.auditMongoCollection) {
67
+ throw new Error('AUDIT_MONGO_URI is set but AUDIT_MONGO_DB/AUDIT_MONGO_COLLECTION are missing — no collection-name fallback.');
68
+ }
69
+ return createMongoAuditSink({ uri: env.auditMongoUri, dbName: env.auditMongoDb, collection: env.auditMongoCollection });
70
+ }
71
+ if (env.auditJsonlPath)
72
+ return createJsonlAuditSink({ filePath: env.auditJsonlPath });
73
+ return noopAuditSink;
74
+ }
75
+ /** Sums usage across however many 'usage' onEvent callbacks a run emits into one invocation-level total. */
76
+ export function createUsageAccumulator() {
77
+ let inputTokens = 0;
78
+ let outputTokens = 0;
79
+ let cacheWriteTokens = 0;
80
+ let cacheReadTokens = 0;
81
+ return {
82
+ onUsage(u) {
83
+ inputTokens += u.inputTokens;
84
+ outputTokens += u.outputTokens;
85
+ cacheWriteTokens += u.cacheWriteTokens ?? 0;
86
+ cacheReadTokens += u.cacheReadTokens ?? 0;
87
+ },
88
+ totals() {
89
+ return { inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
90
+ },
91
+ };
92
+ }
93
+ //# sourceMappingURL=sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.js","sourceRoot":"","sources":["../../src/audit/sink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function required(name: string): string;
2
+ export declare function optional(name: string): string | undefined;
3
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/config/helpers.ts"],"names":[],"mappings":"AAOA,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzD"}
@@ -0,0 +1,16 @@
1
+ // Two pure primitives, shared because every consuming agent needs exactly
2
+ // this env-var-reading pattern. Deliberately NOT a shared config schema/
3
+ // singleton — each agent's .env shape is genuinely different, and a schema
4
+ // DSL flexible enough to cover more than one shape today is speculative
5
+ // machinery with no current payoff. Each agent keeps its own frozen config
6
+ // object built from these.
7
+ export function required(name) {
8
+ const value = process.env[name];
9
+ if (!value)
10
+ throw new Error(`Missing required env var: ${name}`);
11
+ return value;
12
+ }
13
+ export function optional(name) {
14
+ return process.env[name] || undefined;
15
+ }
16
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/config/helpers.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,2EAA2E;AAC3E,2BAA2B;AAE3B,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACjE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;AACxC,CAAC"}