@omnitype-code/adapter-sdk 2.0.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 (46) hide show
  1. package/README.md +163 -0
  2. package/dist/adapters/filesystem-only.d.ts +30 -0
  3. package/dist/adapters/filesystem-only.d.ts.map +1 -0
  4. package/dist/adapters/filesystem-only.js +115 -0
  5. package/dist/adapters/filesystem-only.js.map +1 -0
  6. package/dist/adapters/hook-poller.d.ts +40 -0
  7. package/dist/adapters/hook-poller.d.ts.map +1 -0
  8. package/dist/adapters/hook-poller.js +125 -0
  9. package/dist/adapters/hook-poller.js.map +1 -0
  10. package/dist/adapters/index.d.ts +34 -0
  11. package/dist/adapters/index.d.ts.map +1 -0
  12. package/dist/adapters/index.js +61 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/stdin-json-hook-tool.d.ts +87 -0
  15. package/dist/adapters/stdin-json-hook-tool.d.ts.map +1 -0
  16. package/dist/adapters/stdin-json-hook-tool.js +491 -0
  17. package/dist/adapters/stdin-json-hook-tool.js.map +1 -0
  18. package/dist/adapters/transcript-scavenger.d.ts +40 -0
  19. package/dist/adapters/transcript-scavenger.d.ts.map +1 -0
  20. package/dist/adapters/transcript-scavenger.js +569 -0
  21. package/dist/adapters/transcript-scavenger.js.map +1 -0
  22. package/dist/classifier.d.ts +132 -0
  23. package/dist/classifier.d.ts.map +1 -0
  24. package/dist/classifier.js +95 -0
  25. package/dist/classifier.js.map +1 -0
  26. package/dist/index.d.ts +8 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +8 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/loader.d.ts +51 -0
  31. package/dist/loader.d.ts.map +1 -0
  32. package/dist/loader.js +352 -0
  33. package/dist/loader.js.map +1 -0
  34. package/dist/manifest.d.ts +131 -0
  35. package/dist/manifest.d.ts.map +1 -0
  36. package/dist/manifest.js +12 -0
  37. package/dist/manifest.js.map +1 -0
  38. package/dist/normalize.d.ts +51 -0
  39. package/dist/normalize.d.ts.map +1 -0
  40. package/dist/normalize.js +122 -0
  41. package/dist/normalize.js.map +1 -0
  42. package/dist/tier.d.ts +12 -0
  43. package/dist/tier.d.ts.map +1 -0
  44. package/dist/tier.js +62 -0
  45. package/dist/tier.js.map +1 -0
  46. package/package.json +36 -0
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # @omnitype-code/adapter-sdk
2
+
3
+ The adapter layer for OmniType v2 deterministic attribution.
4
+
5
+ This SDK is for **AI tool authors** and **open-source contributors** who want to add OmniType attribution support to a coding tool (Cursor, Windsurf, Cline, a custom CLI, etc.).
6
+
7
+ If you are an AI tool **end user** looking to track attribution in your own workflow, install the VSCode extension instead.
8
+
9
+ ---
10
+
11
+ ## How the tier system works
12
+
13
+ Every file edit tracked by OmniType gets an attribution **tier**:
14
+
15
+ | Tier | Name | How it's produced | Confidence |
16
+ |---|---|---|---|
17
+ | **T0** | Deterministic | AI tool embeds `@omnitype-code/agent-sdk` directly | 100% |
18
+ | **T1** | Hook-inferred | `preToolUse` hook fires before each tool call | 85% |
19
+ | **T2** | Retrospective | Transcript scavenger reads session logs after the fact | 60% |
20
+ | **T3** | Filesystem-only | File watcher sees a change with no session context | 30% |
21
+
22
+ The goal is to get as much attribution as possible at T0 or T1. T3 spans have `origin='unknown'` — they are **never** assumed to be AI.
23
+
24
+ ---
25
+
26
+ ## Option A — Embed the Agent SDK (T0 path)
27
+
28
+ Use `@omnitype-code/agent-sdk` if your tool writes files directly (not via shell hooks).
29
+
30
+ ```bash
31
+ npm install @omnitype-code/agent-sdk
32
+ ```
33
+
34
+ ```typescript
35
+ import { OmniTypeAgent } from '@omnitype-code/agent-sdk';
36
+
37
+ // On session start (once per AI conversation)
38
+ const agent = await OmniTypeAgent.tryConnect({
39
+ workspace: '/path/to/git/repo',
40
+ sessionId: crypto.randomUUID(),
41
+ model: 'claude-sonnet-4-6',
42
+ tool: 'my-tool',
43
+ });
44
+
45
+ // Wrap every file write — produces T0 (deterministic) attribution
46
+ if (agent) {
47
+ await agent.withEdit('src/foo.ts', newContent, async (absPath, content) => {
48
+ await fs.writeFile(absPath, content, 'utf-8');
49
+ });
50
+ } else {
51
+ // Daemon not running — write normally, attribution will be T3
52
+ await fs.writeFile(absPath, newContent, 'utf-8');
53
+ }
54
+
55
+ // On session end
56
+ await agent?.end('tool-exit');
57
+ ```
58
+
59
+ `tryConnect` returns `null` if the daemon isn't running — your tool continues working normally and writes are tracked at T3 by the filesystem watcher.
60
+
61
+ ### Fine-grained API
62
+
63
+ ```typescript
64
+ // Manual txn control (for multi-file edits)
65
+ const txn = await agent.beginTxn(['src/a.ts', 'src/b.ts']);
66
+ await writeA();
67
+ await writeB();
68
+ await agent.commitTxn(txn, [
69
+ { path: 'src/a.ts', preHash, postHash, splices },
70
+ { path: 'src/b.ts', preHash, postHash, splices },
71
+ ]);
72
+
73
+ // Emit the prompt (privacy-safe — only hash stored by default)
74
+ await agent.emitPrompt(promptText, /* shareText= */ false);
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Option B — Write a Manifest (T1 path)
80
+
81
+ If your tool runs shell commands that can be intercepted via hooks, write a 30-line JSON manifest.
82
+
83
+ Create `.omnitype/adapters/my-tool/manifest.json`:
84
+
85
+ ```json
86
+ {
87
+ "id": "my-tool",
88
+ "version": "1.0.0",
89
+ "display_name": "My AI Tool",
90
+ "tool_binary": "my-tool",
91
+ "trust": { "trust_circle": 2 },
92
+ "declared_capabilities": [
93
+ "tool.identity",
94
+ "session.id",
95
+ "hook.preToolUse",
96
+ "operation.editOp.path_only"
97
+ ],
98
+ "hooks": {
99
+ "preToolUse": {
100
+ "install_path": "~/.config/my-tool/hooks/preToolUse.sh",
101
+ "one_liner": "echo '{\"type\":\"hook\",\"tool\":\"my-tool\",\"session\":\"$SESSION_ID\",\"file\":\"$FILE\"}' >> ${OMNITYPE_HOOKS_DIR}/event-$(date +%s%N).json"
102
+ }
103
+ },
104
+ "normalize": [
105
+ {
106
+ "match": { "tool_name": "write_file" },
107
+ "emit": "EditOp",
108
+ "path_from": "$.arguments.path",
109
+ "origin": "ai"
110
+ }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ Then run:
116
+ ```bash
117
+ omnitype-daemon install-hooks
118
+ ```
119
+
120
+ The daemon will install the hook and start receiving T1 events from your tool.
121
+
122
+ ### Trust circles
123
+
124
+ | Circle | Level | Who uses it |
125
+ |---|---|---|
126
+ | 1 | Verified partner | Claude Code, Cursor, Windsurf (manifests maintained by OmniType) |
127
+ | 2 | Community verified | Cline, Copilot (community-maintained, OmniType-reviewed) |
128
+ | 3 | Community | Unreviewed third-party manifests |
129
+
130
+ T0 is only achievable via the Agent SDK. Community (circle 3) adapters are capped at T1.
131
+
132
+ ---
133
+
134
+ ## The attribution guarantee
135
+
136
+ **OmniType v2 never assumes an unknown write is AI-generated.**
137
+
138
+ - `origin='unknown'` means: a file changed, but we don't know who wrote it.
139
+ - `origin='ai'` requires positive evidence: an active AI session txn, a signed Agent SDK event, or a hook payload.
140
+ - This is enforced in the SQLite materializer — there is no code path that promotes `'unknown'` to `'ai'`.
141
+
142
+ ---
143
+
144
+ ## Verifying your adapter
145
+
146
+ ```bash
147
+ # Check your adapter is installed and recognised
148
+ omnitype-daemon doctor
149
+
150
+ # See live attribution as you code
151
+ omnitype-daemon blame src/foo.ts
152
+
153
+ # Check tier breakdown for the workspace
154
+ omnitype-daemon health
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Filing issues
160
+
161
+ Open an issue at [github.com/omnitype-code/omnitype-v2](https://github.com/omnitype-code/omnitype-v2) with the label `adapter`.
162
+
163
+ Include the output of `omnitype-daemon doctor` and `omnitype-daemon health --json`.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * filesystem-only-adapter — always loaded, cannot be disabled.
3
+ *
4
+ * Watches files for changes that weren't recorded by any other adapter,
5
+ * computes a Myers diff, and emits UnattributedDelta with confidence < 1.0.
6
+ *
7
+ * Key invariants:
8
+ * - NEVER emits origin='ai'. Unknown writers are 'unknown'.
9
+ * - Always tier T3.
10
+ * - Only emits for files that have an existing FileOpened record (tracked).
11
+ * - Deduplicates against changes already recorded by higher-tier adapters
12
+ * (if post_hash matches materialized hash, skip).
13
+ */
14
+ import type { EmitFn } from '../loader.js';
15
+ export declare const ADAPTER_ID = "org.omnitype.adapters.filesystem-only";
16
+ export declare class FilesystemOnlyAdapter {
17
+ private watched;
18
+ private watchers;
19
+ private emit;
20
+ private workspace;
21
+ private actor;
22
+ constructor(emit: EmitFn, workspace: string, user: string, host: string);
23
+ /** Called by the materializer when a file is first opened/tracked */
24
+ trackFile(relPath: string, currentHash: string): void;
25
+ untrackFile(relPath: string): void;
26
+ private onFileChange;
27
+ private guessCandidates;
28
+ stopAll(): void;
29
+ }
30
+ //# sourceMappingURL=filesystem-only.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-only.d.ts","sourceRoot":"","sources":["../../src/adapters/filesystem-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,eAAO,MAAM,UAAU,0CAA0C,CAAC;AAQlE,qBAAa,qBAAqB;IAChC,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAQ;gBAET,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAYvE,qEAAqE;IACrE,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAarD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMlC,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,eAAe;IAMvB,OAAO,IAAI,IAAI;CAKhB"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * filesystem-only-adapter — always loaded, cannot be disabled.
3
+ *
4
+ * Watches files for changes that weren't recorded by any other adapter,
5
+ * computes a Myers diff, and emits UnattributedDelta with confidence < 1.0.
6
+ *
7
+ * Key invariants:
8
+ * - NEVER emits origin='ai'. Unknown writers are 'unknown'.
9
+ * - Always tier T3.
10
+ * - Only emits for files that have an existing FileOpened record (tracked).
11
+ * - Deduplicates against changes already recorded by higher-tier adapters
12
+ * (if post_hash matches materialized hash, skip).
13
+ */
14
+ import { watch, readFileSync, statSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { hashBytes } from '@omnitype-code/journal';
17
+ export const ADAPTER_ID = 'org.omnitype.adapters.filesystem-only';
18
+ export class FilesystemOnlyAdapter {
19
+ watched = new Map();
20
+ watchers = new Map();
21
+ emit;
22
+ workspace;
23
+ actor;
24
+ constructor(emit, workspace, user, host) {
25
+ this.emit = emit;
26
+ this.workspace = workspace;
27
+ this.actor = {
28
+ kind: 'unknown',
29
+ tool: 'unknown',
30
+ user,
31
+ host,
32
+ workspace,
33
+ };
34
+ }
35
+ /** Called by the materializer when a file is first opened/tracked */
36
+ trackFile(relPath, currentHash) {
37
+ const absPath = join(this.workspace, relPath);
38
+ this.watched.set(relPath, { path: relPath, absPath, lastHash: currentHash });
39
+ if (this.watchers.has(relPath))
40
+ return;
41
+ const watcher = watch(absPath, { persistent: false }, () => {
42
+ // Debounce 150ms
43
+ setTimeout(() => this.onFileChange(relPath), 150);
44
+ });
45
+ this.watchers.set(relPath, watcher);
46
+ }
47
+ untrackFile(relPath) {
48
+ this.watchers.get(relPath)?.close();
49
+ this.watchers.delete(relPath);
50
+ this.watched.delete(relPath);
51
+ }
52
+ onFileChange(relPath) {
53
+ const entry = this.watched.get(relPath);
54
+ if (!entry)
55
+ return;
56
+ let newContent;
57
+ try {
58
+ newContent = readFileSync(entry.absPath, 'utf-8');
59
+ }
60
+ catch {
61
+ return; // file deleted or unreadable
62
+ }
63
+ const newHash = hashBytes(Buffer.from(newContent, 'utf-8'));
64
+ if (newHash === entry.lastHash)
65
+ return; // no real change
66
+ const oldHash = entry.lastHash;
67
+ entry.lastHash = newHash;
68
+ const ops = computeSimpleDiff(entry.absPath, oldHash, newContent);
69
+ if (ops.length === 0)
70
+ return;
71
+ const body = {
72
+ type: 'UnattributedDelta',
73
+ path: relPath,
74
+ pre_hash: oldHash,
75
+ post_hash: newHash,
76
+ ops,
77
+ candidates: this.guessCandidates(relPath),
78
+ confidence: 0.0, // explicitly zero — we don't know who did this
79
+ };
80
+ this.emit(this.actor, body, 'T3', ADAPTER_ID);
81
+ }
82
+ guessCandidates(relPath) {
83
+ // In the future: query process-observation-adapter here
84
+ // For now: empty candidate list (honest unknown)
85
+ return [];
86
+ }
87
+ stopAll() {
88
+ for (const w of this.watchers.values())
89
+ w.close();
90
+ this.watchers.clear();
91
+ this.watched.clear();
92
+ }
93
+ }
94
+ // ─── Simple diff: whole-file replace represented as one splice ────────────────
95
+ // This is the v1 LCS downgraded to its honest role: T3, UnattributedDelta only.
96
+ // A real Myers diff is a future improvement; correctness of attribution doesn't
97
+ // depend on diff quality here since everything is already tagged T3/confidence=0.
98
+ function computeSimpleDiff(absPath, oldHash, newContent) {
99
+ // Represent as a full-file replacement splice.
100
+ // Byte-accurate diff can be added later; for now we model it as:
101
+ // delete everything, insert new content.
102
+ let oldLen = 0;
103
+ try {
104
+ oldLen = statSync(absPath).size;
105
+ }
106
+ catch { /* file might be new */ }
107
+ return [
108
+ {
109
+ offset: 0,
110
+ delete_len: oldLen,
111
+ insert_text: newContent,
112
+ },
113
+ ];
114
+ }
115
+ //# sourceMappingURL=filesystem-only.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-only.js","sourceRoot":"","sources":["../../src/adapters/filesystem-only.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * hook-poller — polls the .omnitype/hooks/ directory for events written
3
+ * by hook one-liners and converts them to journal events.
4
+ *
5
+ * Each tool's preToolUse hook writes a JSON file to the pipeDir.
6
+ * This poller drains those files every POLL_INTERVAL_MS and emits:
7
+ * - SessionStarted (if session_id is new)
8
+ * - ModelClaim (if model is present)
9
+ * - TxnBegin (for write-type tool calls)
10
+ * - The actual EditOp comes later from the VSCode discriminator or
11
+ * the filesystem watcher — NOT from the hook itself.
12
+ *
13
+ * This replaces the v1 sentinel approach.
14
+ * Key improvements over v1:
15
+ * - Events are consumed and deleted — no stale TTL races.
16
+ * - Session is tracked explicitly — no 30s window heuristics.
17
+ * - Model comes from actual hook payload — not terminal scraping.
18
+ * - process.cwd() issue is avoided: we know the workspace because
19
+ * the daemon already owns the workspace context.
20
+ */
21
+ import type { EmitFn } from '../loader.js';
22
+ export declare const ADAPTER_ID = "org.omnitype.adapters.hook-poller";
23
+ export declare class HookPoller {
24
+ private pipeDir;
25
+ private emit;
26
+ private workspace;
27
+ private user;
28
+ private host;
29
+ private timer;
30
+ private seenSessions;
31
+ private openTxns;
32
+ constructor(emit: EmitFn, workspace: string, user: string, host: string);
33
+ start(): void;
34
+ private poll;
35
+ private processHookEvent;
36
+ getOpenTxn(sessionId: string): string | undefined;
37
+ closeOpenTxn(sessionId: string): void;
38
+ stop(): void;
39
+ }
40
+ //# sourceMappingURL=hook-poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-poller.d.ts","sourceRoot":"","sources":["../../src/adapters/hook-poller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAQ3C,eAAO,MAAM,UAAU,sCAAsC,CAAC;AAI9D,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAqB;IAEzC,OAAO,CAAC,QAAQ,CAA6B;gBAEjC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAQvE,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,gBAAgB;IAgExB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrC,IAAI,IAAI,IAAI;CAMb"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * hook-poller — polls the .omnitype/hooks/ directory for events written
3
+ * by hook one-liners and converts them to journal events.
4
+ *
5
+ * Each tool's preToolUse hook writes a JSON file to the pipeDir.
6
+ * This poller drains those files every POLL_INTERVAL_MS and emits:
7
+ * - SessionStarted (if session_id is new)
8
+ * - ModelClaim (if model is present)
9
+ * - TxnBegin (for write-type tool calls)
10
+ * - The actual EditOp comes later from the VSCode discriminator or
11
+ * the filesystem watcher — NOT from the hook itself.
12
+ *
13
+ * This replaces the v1 sentinel approach.
14
+ * Key improvements over v1:
15
+ * - Events are consumed and deleted — no stale TTL races.
16
+ * - Session is tracked explicitly — no 30s window heuristics.
17
+ * - Model comes from actual hook payload — not terminal scraping.
18
+ * - process.cwd() issue is avoided: we know the workspace because
19
+ * the daemon already owns the workspace context.
20
+ */
21
+ import { join } from 'node:path';
22
+ import { createHash, randomBytes } from 'node:crypto';
23
+ import { drainHookEvents } from './stdin-json-hook-tool.js';
24
+ import { isWriteToolCall, hookPayloadToFilePath, hookPayloadToSessionEvent, } from '../normalize.js';
25
+ export const ADAPTER_ID = 'org.omnitype.adapters.hook-poller';
26
+ const POLL_INTERVAL_MS = 100;
27
+ const HOOK_TIER = 'T1'; // hook + workspace context = T1
28
+ export class HookPoller {
29
+ pipeDir;
30
+ emit;
31
+ workspace;
32
+ user;
33
+ host;
34
+ timer = null;
35
+ seenSessions = new Set();
36
+ // Maps session_id → open txn_id (for deduplication)
37
+ openTxns = new Map();
38
+ constructor(emit, workspace, user, host) {
39
+ this.pipeDir = join(workspace, '.omnitype', 'hooks');
40
+ this.emit = emit;
41
+ this.workspace = workspace;
42
+ this.user = user;
43
+ this.host = host;
44
+ }
45
+ start() {
46
+ this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
47
+ this.timer.unref();
48
+ }
49
+ poll() {
50
+ const events = drainHookEvents(this.pipeDir);
51
+ for (const event of events) {
52
+ this.processHookEvent(event);
53
+ }
54
+ }
55
+ processHookEvent(event) {
56
+ const { tool, payload, ts } = event;
57
+ const sessionInfo = hookPayloadToSessionEvent(payload, tool);
58
+ const filePath = hookPayloadToFilePath(payload);
59
+ // Antigravity uses toolCall.name; legacy tools use tool_name
60
+ const toolName = payload.tool_name ?? payload.toolCall?.name;
61
+ const actor = {
62
+ kind: 'ai',
63
+ tool,
64
+ model: payload.model ?? sessionInfo?.model ?? 'unknown',
65
+ session_id: sessionInfo?.session_id,
66
+ user: this.user,
67
+ host: this.host,
68
+ workspace: this.workspace,
69
+ };
70
+ // Emit SessionStarted (once per session_id)
71
+ if (sessionInfo?.session_id && !this.seenSessions.has(sessionInfo.session_id)) {
72
+ this.seenSessions.add(sessionInfo.session_id);
73
+ const dummyPromptHash = createHash('sha256').update(sessionInfo.session_id).digest('hex');
74
+ const sessionEvent = {
75
+ type: 'SessionStarted',
76
+ session_id: sessionInfo.session_id,
77
+ tool,
78
+ model: sessionInfo.model || 'unknown',
79
+ prompt_hash: dummyPromptHash,
80
+ prompt_bytes: 0,
81
+ };
82
+ this.emit(actor, sessionEvent, HOOK_TIER, ADAPTER_ID);
83
+ }
84
+ // Emit ModelClaim if model present
85
+ if (payload.model && sessionInfo?.session_id) {
86
+ const modelEvent = {
87
+ type: 'ModelClaim',
88
+ session_id: sessionInfo.session_id,
89
+ model: payload.model,
90
+ source: 'hook-stdin',
91
+ };
92
+ this.emit(actor, modelEvent, HOOK_TIER, ADAPTER_ID);
93
+ }
94
+ // Emit TxnBegin for write-type tool calls
95
+ if (isWriteToolCall(toolName) && sessionInfo?.session_id) {
96
+ const existingTxn = this.openTxns.get(sessionInfo.session_id);
97
+ if (!existingTxn) {
98
+ const txnId = randomBytes(16).toString('hex');
99
+ this.openTxns.set(sessionInfo.session_id, txnId);
100
+ const txnBegin = {
101
+ type: 'TxnBegin',
102
+ txn_id: txnId,
103
+ session_id: sessionInfo.session_id,
104
+ expected_files: filePath ? [filePath] : [],
105
+ };
106
+ this.emit(actor, txnBegin, HOOK_TIER, ADAPTER_ID);
107
+ }
108
+ }
109
+ // Note: TxnCommit is emitted by the VSCode extension or watcher
110
+ // when the actual file change is observed and linked to this txn.
111
+ }
112
+ getOpenTxn(sessionId) {
113
+ return this.openTxns.get(sessionId);
114
+ }
115
+ closeOpenTxn(sessionId) {
116
+ this.openTxns.delete(sessionId);
117
+ }
118
+ stop() {
119
+ if (this.timer) {
120
+ clearInterval(this.timer);
121
+ this.timer = null;
122
+ }
123
+ }
124
+ }
125
+ //# sourceMappingURL=hook-poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-poller.js","sourceRoot":"","sources":["../../src/adapters/hook-poller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Built-in adapter suite.
3
+ *
4
+ * Production guarantees:
5
+ * - Any individual adapter failing to start does NOT abort the daemon.
6
+ * - The suite always returns a usable handle, even when every adapter fails
7
+ * (the FilesystemOnlyAdapter is the always-on graceful-degradation path).
8
+ * - Each adapter's health is reported through `getHealth()`, so the daemon
9
+ * can expose it via `query_health` and the dashboard can show users
10
+ * which tools fell back to T3.
11
+ */
12
+ import { FilesystemOnlyAdapter } from './filesystem-only.js';
13
+ import { HookPoller } from './hook-poller.js';
14
+ import { TranscriptScavengerAdapter } from './transcript-scavenger.js';
15
+ import type { EmitFn } from '../loader.js';
16
+ export interface AdapterHealth {
17
+ id: string;
18
+ started: boolean;
19
+ error?: string;
20
+ }
21
+ export interface AdapterSuite {
22
+ filesystemOnly: FilesystemOnlyAdapter;
23
+ hookPoller: HookPoller | null;
24
+ transcriptScavenger: TranscriptScavengerAdapter | null;
25
+ getHealth(): AdapterHealth[];
26
+ stop(): void;
27
+ }
28
+ export declare function startBuiltinAdapters(emit: EmitFn, workspace: string, user: string, host: string): AdapterSuite;
29
+ export { FilesystemOnlyAdapter } from './filesystem-only.js';
30
+ export { HookPoller } from './hook-poller.js';
31
+ export { TranscriptScavengerAdapter } from './transcript-scavenger.js';
32
+ export { installHooks, TOOL_SPECS, buildHookOneliner } from './stdin-json-hook-tool.js';
33
+ export type { InstallResult, ToolHookSpec } from './stdin-json-hook-tool.js';
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,qBAAqB,CAAC;IACtC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,0BAA0B,GAAG,IAAI,CAAC;IACvD,SAAS,IAAI,aAAa,EAAE,CAAC;IAC7B,IAAI,IAAI,IAAI,CAAC;CACd;AAmBD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,YAAY,CA+Bd;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Built-in adapter suite.
3
+ *
4
+ * Production guarantees:
5
+ * - Any individual adapter failing to start does NOT abort the daemon.
6
+ * - The suite always returns a usable handle, even when every adapter fails
7
+ * (the FilesystemOnlyAdapter is the always-on graceful-degradation path).
8
+ * - Each adapter's health is reported through `getHealth()`, so the daemon
9
+ * can expose it via `query_health` and the dashboard can show users
10
+ * which tools fell back to T3.
11
+ */
12
+ import { FilesystemOnlyAdapter } from './filesystem-only.js';
13
+ import { HookPoller } from './hook-poller.js';
14
+ import { TranscriptScavengerAdapter } from './transcript-scavenger.js';
15
+ function safeStart(adapter, id, health) {
16
+ try {
17
+ adapter.start();
18
+ health.push({ id, started: true });
19
+ return adapter;
20
+ }
21
+ catch (err) {
22
+ const msg = err instanceof Error ? err.message : String(err);
23
+ console.error(`[omnitype-adapters] ${id} failed to start: ${msg}`);
24
+ health.push({ id, started: false, error: msg });
25
+ return null;
26
+ }
27
+ }
28
+ export function startBuiltinAdapters(emit, workspace, user, host) {
29
+ const health = [];
30
+ // FilesystemOnlyAdapter is always-on, started per-file via trackFile().
31
+ // It is the universal fallback: any unsupported tool's writes are caught here.
32
+ const filesystemOnly = new FilesystemOnlyAdapter(emit, workspace, user, host);
33
+ health.push({ id: 'org.omnitype.adapters.filesystem-only', started: true });
34
+ const hookPoller = safeStart(new HookPoller(emit, workspace, user, host), 'org.omnitype.adapters.hook-poller', health);
35
+ const transcriptScavenger = safeStart(new TranscriptScavengerAdapter(emit, workspace, user, host), 'org.omnitype.adapters.transcript-scavenger', health);
36
+ return {
37
+ filesystemOnly,
38
+ hookPoller,
39
+ transcriptScavenger,
40
+ getHealth: () => health.slice(),
41
+ stop() {
42
+ try {
43
+ filesystemOnly.stopAll();
44
+ }
45
+ catch { /* */ }
46
+ try {
47
+ hookPoller?.stop();
48
+ }
49
+ catch { /* */ }
50
+ try {
51
+ transcriptScavenger?.stop();
52
+ }
53
+ catch { /* */ }
54
+ },
55
+ };
56
+ }
57
+ export { FilesystemOnlyAdapter } from './filesystem-only.js';
58
+ export { HookPoller } from './hook-poller.js';
59
+ export { TranscriptScavengerAdapter } from './transcript-scavenger.js';
60
+ export { installHooks, TOOL_SPECS, buildHookOneliner } from './stdin-json-hook-tool.js';
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAiBvE,SAAS,SAAS,CAChB,OAAU,EACV,EAAU,EACV,MAAuB;IAEvB,IAAI,CAAC;QACH,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,qBAAqB,GAAG,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,SAAiB,EACjB,IAAY,EACZ,IAAY;IAEZ,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,wEAAwE;IACxE,+EAA+E;IAC/E,MAAM,cAAc,GAAG,IAAI,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9E,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,uCAAuC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5E,MAAM,UAAU,GAAG,SAAS,CAC1B,IAAI,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,EAC3C,mCAAmC,EACnC,MAAM,CACP,CAAC;IAEF,MAAM,mBAAmB,GAAG,SAAS,CACnC,IAAI,0BAA0B,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,EAC3D,4CAA4C,EAC5C,MAAM,CACP,CAAC;IAEF,OAAO;QACL,cAAc;QACd,UAAU;QACV,mBAAmB;QACnB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;QAC/B,IAAI;YACF,IAAI,CAAC;gBAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;YACjD,IAAI,CAAC;gBAAC,UAAU,EAAE,IAAI,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC;gBAAC,mBAAmB,EAAE,IAAI,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}