@moxxy/sdk 0.32.0 → 0.34.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 +1 -1
- package/dist/audit-export.d.ts +51 -0
- package/dist/audit-export.d.ts.map +1 -0
- package/dist/audit-export.js +4 -0
- package/dist/audit-export.js.map +1 -0
- package/dist/audit.d.ts +86 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +35 -0
- package/dist/audit.js.map +1 -0
- package/dist/define.d.ts +2 -0
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +1 -0
- package/dist/define.js.map +1 -1
- package/dist/egress.d.ts +109 -0
- package/dist/egress.d.ts.map +1 -0
- package/dist/egress.js +228 -0
- package/dist/egress.js.map +1 -0
- package/dist/events.d.ts +18 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/fs-utils.d.ts +48 -0
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +114 -1
- package/dist/fs-utils.js.map +1 -1
- package/dist/hooks.d.ts +8 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/mode/react-loop.d.ts.map +1 -1
- package/dist/mode/react-loop.js +10 -1
- package/dist/mode/react-loop.js.map +1 -1
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/principal-os.d.ts +14 -0
- package/dist/principal-os.d.ts.map +1 -0
- package/dist/principal-os.js +48 -0
- package/dist/principal-os.js.map +1 -0
- package/dist/principal.d.ts +54 -0
- package/dist/principal.d.ts.map +1 -0
- package/dist/principal.js +18 -0
- package/dist/principal.js.map +1 -0
- package/dist/redact.d.ts +36 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +88 -0
- package/dist/redact.js.map +1 -0
- package/dist/schemas.d.ts +18 -18
- package/dist/secret-provider.d.ts +60 -0
- package/dist/secret-provider.d.ts.map +1 -0
- package/dist/secret-provider.js +59 -0
- package/dist/secret-provider.js.map +1 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -1
- package/dist/server.js.map +1 -1
- package/dist/session-like.d.ts +3 -0
- package/dist/session-like.d.ts.map +1 -1
- package/dist/signature.d.ts +21 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +32 -0
- package/dist/signature.js.map +1 -0
- package/dist/tool-icon.d.ts +22 -0
- package/dist/tool-icon.d.ts.map +1 -0
- package/dist/tool-icon.js +44 -0
- package/dist/tool-icon.js.map +1 -0
- package/dist/tool.d.ts +8 -0
- package/dist/tool.d.ts.map +1 -1
- package/package.json +9 -2
- package/src/audit-export.ts +57 -0
- package/src/audit.ts +137 -0
- package/src/cross-process-lock.test.ts +23 -14
- package/src/define.ts +3 -0
- package/src/egress.test.ts +238 -0
- package/src/egress.ts +274 -0
- package/src/events.ts +18 -0
- package/src/fs-utils.test.ts +98 -2
- package/src/fs-utils.ts +120 -1
- package/src/hooks.ts +8 -0
- package/src/host-credential.test.ts +39 -0
- package/src/index.ts +49 -0
- package/src/mode/react-loop.ts +10 -1
- package/src/plugin.ts +10 -0
- package/src/principal-os.ts +48 -0
- package/src/principal.test.ts +47 -0
- package/src/principal.ts +60 -0
- package/src/redact.test.ts +91 -0
- package/src/redact.ts +91 -0
- package/src/secret-provider.ts +84 -0
- package/src/server.ts +17 -0
- package/src/session-like.ts +3 -0
- package/src/signature.ts +34 -0
- package/src/tool-icon.test.ts +46 -0
- package/src/tool-icon.ts +46 -0
- package/src/tool.ts +8 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Ed25519 detached-signature verification over exact bytes.
|
|
4
|
+
*
|
|
5
|
+
* Lives in the SDK rather than beside its first caller because two different
|
|
6
|
+
* things now depend on it, and one of them is policy. Policy has to bind on a
|
|
7
|
+
* machine with no plugins installed at all, so the verifier it uses cannot sit
|
|
8
|
+
* inside a plugin: a control that a user can disable by uninstalling something
|
|
9
|
+
* is not a control.
|
|
10
|
+
*
|
|
11
|
+
* Verifies over the bytes as received, never over a re-serialisation of the
|
|
12
|
+
* parsed object. Canonicalising first would let two different documents share a
|
|
13
|
+
* signature whenever the parser and the serialiser disagree about anything,
|
|
14
|
+
* which is how signature-verification bypasses usually happen.
|
|
15
|
+
*
|
|
16
|
+
* Returns false rather than throwing on a malformed key or signature: to a
|
|
17
|
+
* caller "this did not verify" and "this could not be parsed" warrant the same
|
|
18
|
+
* response, and a throw here would tempt a try/catch that swallows both into a
|
|
19
|
+
* success path.
|
|
20
|
+
*/
|
|
21
|
+
export function verifyEd25519(bytes, signatureB64, publicKeyPem) {
|
|
22
|
+
if (!publicKeyPem || !signatureB64)
|
|
23
|
+
return false;
|
|
24
|
+
try {
|
|
25
|
+
const key = createPublicKey(publicKeyPem);
|
|
26
|
+
return cryptoVerify(null, Buffer.from(bytes), key, Buffer.from(signatureB64, 'base64'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAiB,EACjB,YAAoB,EACpB,YAAoB;IAEpB,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;QAC1C,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tool looks like, as a semantic name rather than a glyph.
|
|
3
|
+
*
|
|
4
|
+
* A CLOSED vocabulary on purpose. Surfaces render wildly differently: the
|
|
5
|
+
* desktop draws SVG, the TUI prints a character, a future web surface might do
|
|
6
|
+
* something else again. If this were a free string, a plugin could name an icon
|
|
7
|
+
* no surface owns and every surface would have to guess, so in practice
|
|
8
|
+
* everything would fall back and the field would be decorative. A fixed set
|
|
9
|
+
* means each surface maps it exhaustively and a plugin's choice actually
|
|
10
|
+
* survives to the screen.
|
|
11
|
+
*
|
|
12
|
+
* The names describe WHAT THE TOOL TOUCHES, not what it looks like, so a
|
|
13
|
+
* surface is free to change its artwork without every plugin becoming wrong.
|
|
14
|
+
*
|
|
15
|
+
* Adding a member is a breaking change for surfaces (their maps stop being
|
|
16
|
+
* exhaustive and stop compiling), which is the intended cost: a new icon should
|
|
17
|
+
* be a deliberate decision across surfaces, not a silent fallback to a wrench.
|
|
18
|
+
*/
|
|
19
|
+
export declare const TOOL_ICONS: readonly ["file", "folder", "search", "edit", "diff", "terminal", "globe", "chat", "workflow", "agent", "settings", "lock", "plug", "mic", "speaker", "smartphone", "workspace", "clipboard", "spark", "wrench"];
|
|
20
|
+
export type ToolIcon = (typeof TOOL_ICONS)[number];
|
|
21
|
+
export declare function isToolIcon(value: unknown): value is ToolIcon;
|
|
22
|
+
//# sourceMappingURL=tool-icon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-icon.d.ts","sourceRoot":"","sources":["../src/tool-icon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,kNAqBb,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tool looks like, as a semantic name rather than a glyph.
|
|
3
|
+
*
|
|
4
|
+
* A CLOSED vocabulary on purpose. Surfaces render wildly differently: the
|
|
5
|
+
* desktop draws SVG, the TUI prints a character, a future web surface might do
|
|
6
|
+
* something else again. If this were a free string, a plugin could name an icon
|
|
7
|
+
* no surface owns and every surface would have to guess, so in practice
|
|
8
|
+
* everything would fall back and the field would be decorative. A fixed set
|
|
9
|
+
* means each surface maps it exhaustively and a plugin's choice actually
|
|
10
|
+
* survives to the screen.
|
|
11
|
+
*
|
|
12
|
+
* The names describe WHAT THE TOOL TOUCHES, not what it looks like, so a
|
|
13
|
+
* surface is free to change its artwork without every plugin becoming wrong.
|
|
14
|
+
*
|
|
15
|
+
* Adding a member is a breaking change for surfaces (their maps stop being
|
|
16
|
+
* exhaustive and stop compiling), which is the intended cost: a new icon should
|
|
17
|
+
* be a deliberate decision across surfaces, not a silent fallback to a wrench.
|
|
18
|
+
*/
|
|
19
|
+
export const TOOL_ICONS = [
|
|
20
|
+
'file',
|
|
21
|
+
'folder',
|
|
22
|
+
'search',
|
|
23
|
+
'edit',
|
|
24
|
+
'diff',
|
|
25
|
+
'terminal',
|
|
26
|
+
'globe',
|
|
27
|
+
'chat',
|
|
28
|
+
'workflow',
|
|
29
|
+
'agent',
|
|
30
|
+
'settings',
|
|
31
|
+
'lock',
|
|
32
|
+
'plug',
|
|
33
|
+
'mic',
|
|
34
|
+
'speaker',
|
|
35
|
+
'smartphone',
|
|
36
|
+
'workspace',
|
|
37
|
+
'clipboard',
|
|
38
|
+
'spark',
|
|
39
|
+
'wrench',
|
|
40
|
+
];
|
|
41
|
+
export function isToolIcon(value) {
|
|
42
|
+
return typeof value === 'string' && TOOL_ICONS.includes(value);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=tool-icon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-icon.js","sourceRoot":"","sources":["../src/tool-icon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,MAAM;IACN,UAAU;IACV,OAAO;IACP,MAAM;IACN,UAAU;IACV,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;IACN,KAAK;IACL,SAAS;IACT,YAAY;IACZ,WAAW;IACX,WAAW;IACX,OAAO;IACP,QAAQ;CACA,CAAC;AAIX,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,UAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC5F,CAAC"}
|
package/dist/tool.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
import type { EventLogReader } from './log.js';
|
|
3
|
+
import type { ToolIcon } from './tool-icon.js';
|
|
3
4
|
import type { PermissionRule } from './permission.js';
|
|
4
5
|
import type { SessionId, ToolCallId, TurnId } from './ids.js';
|
|
5
6
|
import type { SubagentSpawner } from './subagent.js';
|
|
@@ -182,6 +183,13 @@ export interface ToolDef {
|
|
|
182
183
|
readonly handler: (input: unknown, ctx: ToolContext) => Promise<unknown> | unknown;
|
|
183
184
|
/** Opt-in presentation hint. See `ToolCompactPresentation`. */
|
|
184
185
|
readonly compact?: ToolCompactPresentation;
|
|
186
|
+
/**
|
|
187
|
+
* What this tool touches, so surfaces can show it. Without this a surface can
|
|
188
|
+
* only guess from the NAME, which works for the handful of built-ins it was
|
|
189
|
+
* written against and gives every plugin-contributed tool the same generic
|
|
190
|
+
* fallback. See {@link ToolIcon}.
|
|
191
|
+
*/
|
|
192
|
+
readonly icon?: ToolIcon;
|
|
185
193
|
/**
|
|
186
194
|
* Optional capability declaration. Advisory unless the user enables
|
|
187
195
|
* `@moxxy/plugin-security`, at which point the active `Isolator`
|
package/dist/tool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Typed public surface for the moxxy framework: event types, define* factories, lifecycle hook signatures.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
@@ -67,15 +67,22 @@
|
|
|
67
67
|
"access": "public"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"zod": "^3.24.0"
|
|
70
|
+
"zod": "^3.24.0",
|
|
71
|
+
"undici": "^6.21.0"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"typescript": "^5.7.3",
|
|
75
|
+
"undici": "^6.21.0",
|
|
74
76
|
"vitest": "^2.1.8",
|
|
75
77
|
"zod": "^3.24.0",
|
|
76
78
|
"@moxxy/tsconfig": "0.0.0",
|
|
77
79
|
"@moxxy/vitest-preset": "0.0.0"
|
|
78
80
|
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"undici": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
79
86
|
"scripts": {
|
|
80
87
|
"build": "tsc -p tsconfig.json",
|
|
81
88
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AuditRecord } from './audit.js';
|
|
2
|
+
import type { Principal } from './principal.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shipping the local audit trail somewhere central.
|
|
6
|
+
*
|
|
7
|
+
* Separate from `AuditSinkDef` on purpose. A sink is the WRITE path: it is
|
|
8
|
+
* handed each record as it happens. An exporter is a READER of the trail that
|
|
9
|
+
* has already been durably written, driven by a checkpoint.
|
|
10
|
+
*
|
|
11
|
+
* That split is what makes shipping survivable. A network sink has to decide,
|
|
12
|
+
* while a turn is running, what to do when the collector is down: block the
|
|
13
|
+
* turn, drop the record, or buffer it in memory and lose it on exit. An
|
|
14
|
+
* exporter has no such dilemma, because the record is already on disk in a
|
|
15
|
+
* hash-chained file. A failed export is retried from the checkpoint, and the
|
|
16
|
+
* worst case is that central visibility lags, never that the record is gone.
|
|
17
|
+
*/
|
|
18
|
+
export interface AuditExportResource {
|
|
19
|
+
/** Which machine. The whole point of central collection is telling them apart. */
|
|
20
|
+
readonly host: string;
|
|
21
|
+
readonly cliVersion: string;
|
|
22
|
+
/** Whoever the surface could establish was acting; absent on an unattributed host. */
|
|
23
|
+
readonly principal?: Principal;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AuditExportBatch {
|
|
27
|
+
readonly records: ReadonlyArray<AuditRecord>;
|
|
28
|
+
readonly resource: AuditExportResource;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AuditExportContext {
|
|
32
|
+
/** Destination, as configured. Exporter-defined; an endpoint URL for OTLP. */
|
|
33
|
+
readonly endpoint: string;
|
|
34
|
+
/** Exporter-specific settings from `audit.export`, already placeholder-resolved. */
|
|
35
|
+
readonly settings: Readonly<Record<string, unknown>>;
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AuditExporterDef {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Ship one batch.
|
|
44
|
+
*
|
|
45
|
+
* THE CONTRACT: resolving means the destination has DURABLY accepted these
|
|
46
|
+
* records, because the checkpoint advances on resolve and those records are
|
|
47
|
+
* never offered again. An exporter that resolves on "request sent" turns a
|
|
48
|
+
* collector-side rejection into silent data loss. When in doubt, throw:
|
|
49
|
+
* re-sending a batch is cheap and records carry a hash the collector can
|
|
50
|
+
* deduplicate on, whereas a skipped batch is unrecoverable.
|
|
51
|
+
*/
|
|
52
|
+
send(batch: AuditExportBatch, ctx: AuditExportContext): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function defineAuditExporter(def: AuditExporterDef): AuditExporterDef {
|
|
56
|
+
return def;
|
|
57
|
+
}
|
package/src/audit.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { MoxxyEvent, MoxxyEventType } from './events.js';
|
|
2
|
+
import type { SessionId, TurnId } from './ids.js';
|
|
3
|
+
import type { Principal } from './principal.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The audit trail: a tamper-evident record of what the agent did, who it acted
|
|
7
|
+
* for, and what was allowed or denied, in a form that can LEAVE the machine.
|
|
8
|
+
*
|
|
9
|
+
* Distinct from the event log, and the distinction is the whole point. The
|
|
10
|
+
* event log is the conversation: complete, replayable, local, and full of
|
|
11
|
+
* payloads nobody wants shipped to a SIEM. An audit record is the receipt:
|
|
12
|
+
* bounded, redacted, hash-chained, and self-attributing, so one line is
|
|
13
|
+
* meaningful on its own without replaying a session to interpret it.
|
|
14
|
+
*
|
|
15
|
+
* A swappable block like every other, mirroring `EventStoreDef`: core seeds a
|
|
16
|
+
* protected local floor, plugins register alternatives (syslog, OTel, S3,
|
|
17
|
+
* webhook), and the operator activates one by name.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** What kind of thing happened. Coarser than `MoxxyEventType` on purpose: an
|
|
21
|
+
* auditor reasons about categories of action, not about the loop's internals. */
|
|
22
|
+
export type AuditAction =
|
|
23
|
+
/** The security-relevant config in force, recorded once per session. Without
|
|
24
|
+
* it a trail says what was done but not what the rules were at the time,
|
|
25
|
+
* which is the first question asked when reviewing a past run. */
|
|
26
|
+
| 'policy'
|
|
27
|
+
| 'prompt'
|
|
28
|
+
| 'tool.request'
|
|
29
|
+
| 'tool.approved'
|
|
30
|
+
| 'tool.denied'
|
|
31
|
+
| 'tool.result'
|
|
32
|
+
| 'skill.invoked'
|
|
33
|
+
| 'skill.created'
|
|
34
|
+
| 'plugin.registered'
|
|
35
|
+
| 'plugin.unregistered'
|
|
36
|
+
| 'abort'
|
|
37
|
+
/** Token counts for one provider call. Metadata only, never content, which
|
|
38
|
+
* is what makes cost attribution auditable without the trail carrying the
|
|
39
|
+
* conversation. */
|
|
40
|
+
| 'usage'
|
|
41
|
+
| 'error'
|
|
42
|
+
| 'other';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* One record. Every field is either metadata or already-redacted, so the whole
|
|
46
|
+
* object is safe to forward.
|
|
47
|
+
*/
|
|
48
|
+
export interface AuditRecord {
|
|
49
|
+
/** Position in this sink's chain. Contiguous from 0; a gap is evidence. */
|
|
50
|
+
readonly seq: number;
|
|
51
|
+
readonly ts: number;
|
|
52
|
+
readonly sessionId: SessionId;
|
|
53
|
+
readonly turnId: TurnId;
|
|
54
|
+
readonly action: AuditAction;
|
|
55
|
+
/** The underlying event type, kept so a record can be traced back. */
|
|
56
|
+
readonly eventType: MoxxyEventType;
|
|
57
|
+
/**
|
|
58
|
+
* Who it was done for. Absent means unattributed, which is itself worth
|
|
59
|
+
* auditing: it says the surface could not establish an identity.
|
|
60
|
+
*/
|
|
61
|
+
readonly actor?: Principal;
|
|
62
|
+
/** Bounded, redacted, action-specific detail. Never raw payloads. */
|
|
63
|
+
readonly detail?: Readonly<Record<string, unknown>>;
|
|
64
|
+
/** Hash of the previous record, or null for the first. */
|
|
65
|
+
readonly prevHash: string | null;
|
|
66
|
+
/** Hash over this record's own fields plus {@link prevHash}. */
|
|
67
|
+
readonly hash: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A record before the chain fields are computed. */
|
|
71
|
+
export type UnchainedAuditRecord = Omit<AuditRecord, 'seq' | 'prevHash' | 'hash'>;
|
|
72
|
+
|
|
73
|
+
export interface AuditSinkScope {
|
|
74
|
+
readonly sessionId: SessionId;
|
|
75
|
+
readonly cwd: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AuditSinkSession {
|
|
79
|
+
/**
|
|
80
|
+
* Append one record.
|
|
81
|
+
*
|
|
82
|
+
* Takes an UNCHAINED record: sequencing and any tamper-evidence are the
|
|
83
|
+
* sink's own concern, because each sink has its own ordering. The local file
|
|
84
|
+
* chains by day; a syslog sink has no chain at all; a remote service assigns
|
|
85
|
+
* its own sequence. Handing every sink a pre-sealed `seq`/`hash` would be a
|
|
86
|
+
* lie for all but one of them.
|
|
87
|
+
*
|
|
88
|
+
* MUST NOT throw: a failing audit sink degrades loudly, it never takes down
|
|
89
|
+
* the turn it is recording. Report failure by returning false.
|
|
90
|
+
*/
|
|
91
|
+
write(record: UnchainedAuditRecord): Promise<boolean>;
|
|
92
|
+
/** Flush anything buffered. Called on session close. */
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AuditSinkDef {
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly description?: string;
|
|
99
|
+
open(scope: AuditSinkScope): AuditSinkSession;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Freeze an audit-sink spec, mirroring the other `defineX` factories. */
|
|
103
|
+
export function defineAuditSink(def: AuditSinkDef): AuditSinkDef {
|
|
104
|
+
return Object.freeze({ ...def });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Which event types produce an audit record, and under what category.
|
|
109
|
+
*
|
|
110
|
+
* Everything absent from this map is conversation, not audit: assistant text,
|
|
111
|
+
* reasoning, provider request/response, compaction, elision, mode iterations.
|
|
112
|
+
* They belong in the event log and would only bloat a trail an auditor has to
|
|
113
|
+
* read. Tool and permission traffic is what actually gets reviewed, so it is
|
|
114
|
+
* named precisely.
|
|
115
|
+
*
|
|
116
|
+
* An exhaustive map rather than a `default:` so adding an event type is a
|
|
117
|
+
* deliberate decision about whether it is auditable, not a silent 'other'.
|
|
118
|
+
*/
|
|
119
|
+
const AUDITED: Partial<Record<MoxxyEventType, AuditAction>> = {
|
|
120
|
+
user_prompt: 'prompt',
|
|
121
|
+
tool_call_requested: 'tool.request',
|
|
122
|
+
tool_call_approved: 'tool.approved',
|
|
123
|
+
tool_call_denied: 'tool.denied',
|
|
124
|
+
tool_result: 'tool.result',
|
|
125
|
+
skill_invoked: 'skill.invoked',
|
|
126
|
+
skill_created: 'skill.created',
|
|
127
|
+
plugin_registered: 'plugin.registered',
|
|
128
|
+
plugin_unregistered: 'plugin.unregistered',
|
|
129
|
+
abort: 'abort',
|
|
130
|
+
provider_response: 'usage',
|
|
131
|
+
error: 'error',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** The audit category for an event, or null when it is conversation only. */
|
|
135
|
+
export function auditActionOf(event: MoxxyEvent): AuditAction | null {
|
|
136
|
+
return AUDITED[event.type] ?? null;
|
|
137
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { mkdtemp, rm, readdir } from 'node:fs/promises';
|
|
1
|
+
import { mkdtemp, rm, readdir, utimes } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
3
|
+
import path, { join } from 'node:path';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
5
|
import { CrossProcessFireLock } from './cross-process-lock.js';
|
|
6
6
|
|
|
7
|
-
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
|
|
8
|
-
|
|
9
7
|
describe('CrossProcessFireLock', () => {
|
|
10
8
|
let dir: string;
|
|
11
9
|
beforeEach(async () => {
|
|
@@ -40,23 +38,34 @@ describe('CrossProcessFireLock', () => {
|
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
it('reclaims a stale marker once its TTL elapses (crashed holder)', async () => {
|
|
43
|
-
const
|
|
41
|
+
const ttlMs = 1_000;
|
|
42
|
+
const lock = new CrossProcessFireLock({ dir, ttlMs });
|
|
44
43
|
expect(await lock.claim('k@1')).toBe(true);
|
|
45
44
|
expect(await lock.claim('k@1')).toBe(false);
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
45
|
+
// The previous holder "crashed": advance the CLOCK past the TTL rather than
|
|
46
|
+
// sleeping, so the test asserts the staleness rule and not the accuracy of
|
|
47
|
+
// a timer on a loaded machine.
|
|
48
|
+
expect(await lock.claim('k@1', Date.now() + ttlMs * 5)).toBe(true);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
// Age is set on the FILES and the clock is passed in, rather than produced by
|
|
52
|
+
// sleeping. Expiry is `now - mtime > ttlMs`, so a sleep-based version races
|
|
53
|
+
// the scheduler: any pause between claiming the fresh marker and sweeping
|
|
54
|
+
// that exceeds the TTL expires it too, and a short TTL makes that pause
|
|
55
|
+
// trivial to hit on a loaded machine.
|
|
51
56
|
it('sweep removes expired markers and leaves fresh ones', async () => {
|
|
52
|
-
const
|
|
57
|
+
const ttlMs = 1_000;
|
|
58
|
+
const lock = new CrossProcessFireLock({ dir, ttlMs });
|
|
53
59
|
await lock.claim('old@1');
|
|
54
|
-
await sleep(40);
|
|
55
60
|
await lock.claim('fresh@1');
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
61
|
+
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const stale = new Date(now - ttlMs * 5);
|
|
64
|
+
await utimes(join(dir, 'old@1.lock'), stale, stale);
|
|
65
|
+
await utimes(join(dir, 'fresh@1.lock'), new Date(now), new Date(now));
|
|
66
|
+
|
|
67
|
+
expect(await lock.sweep(now)).toBe(1);
|
|
68
|
+
expect(await readdir(dir)).toEqual(['fresh@1.lock']);
|
|
60
69
|
});
|
|
61
70
|
|
|
62
71
|
it('sanitizes unsafe key characters into a single marker file', async () => {
|
package/src/define.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { PermissionRule } from './permission.js';
|
|
|
9
9
|
import type { Plugin, PluginSpec } from './plugin.js';
|
|
10
10
|
import type { ProviderDef } from './provider.js';
|
|
11
11
|
import type { SkillDef, SkillFrontmatter } from './skill.js';
|
|
12
|
+
import type { ToolIcon } from './tool-icon.js';
|
|
12
13
|
import type { HostedTool, ToolCompactPresentation, ToolContext, ToolDef } from './tool.js';
|
|
13
14
|
import type { ToolIsolationSpec } from './isolation.js';
|
|
14
15
|
import type { TranscriberDef } from './transcriber.js';
|
|
@@ -46,6 +47,7 @@ export function defineTool<S extends z.ZodTypeAny, O = unknown>(spec: {
|
|
|
46
47
|
permission?: PermissionRule;
|
|
47
48
|
handler: (input: z.output<S>, ctx: ToolContext) => Promise<O> | O;
|
|
48
49
|
compact?: ToolCompactPresentation;
|
|
50
|
+
icon?: ToolIcon;
|
|
49
51
|
isolation?: ToolIsolationSpec;
|
|
50
52
|
}): ToolDef {
|
|
51
53
|
return Object.freeze({
|
|
@@ -58,6 +60,7 @@ export function defineTool<S extends z.ZodTypeAny, O = unknown>(spec: {
|
|
|
58
60
|
permission: spec.permission,
|
|
59
61
|
handler: spec.handler as (input: unknown, ctx: ToolContext) => Promise<unknown> | unknown,
|
|
60
62
|
compact: spec.compact,
|
|
63
|
+
icon: spec.icon,
|
|
61
64
|
isolation: spec.isolation,
|
|
62
65
|
});
|
|
63
66
|
}
|