dory-to-dory-watcher 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dory-to-dory contributors
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,157 @@
1
+ # dory-to-dory Watcher
2
+
3
+ dory-to-dory Core의 SSE wake-up 신호를 기다렸다가, 실제 pending item이 있을 때만 로컬 Agent CLI를 한 번 실행하는 크로스 플랫폼 Watcher입니다. SSE에는 작업 본문이 포함되지 않으며, 연결 또는 재연결 때 `/v1/watcher/status`로 실제 상태를 다시 확인합니다.
4
+
5
+ Watcher는 Agent를 새로 구현하지 않습니다. 사용자가 이미 로그인하고 도구를 설정한 Codex CLI, Claude Code, Gemini CLI를 headless 모드로 실행하므로 그 Agent의 기존 도구와 권한 정책을 그대로 사용합니다. dory-to-dory MCP는 Watcher의 정확한 런타임 의존성으로 함께 설치됩니다. `custom` Adapter를 통해 다른 Agent 개발자가 실행 방식을 교체할 수 있습니다.
6
+
7
+ ## 동작 흐름
8
+
9
+ 1. Watcher가 Core의 `/v1/watcher/events` SSE에 연결합니다.
10
+ 2. wake-up 또는 재연결 시 `/v1/watcher/status`를 호출합니다.
11
+ 3. pending item이 있을 때만 선택한 Agent CLI를 실행합니다.
12
+ 4. Agent가 MCP의 `d2d_claim_next_item`으로 lease/fencing token을 받고 작업합니다.
13
+ 5. 완료 결과는 `d2d_submit_item_result`로 제출합니다.
14
+ 6. 사용자 확인이 필요하면 Agent가 구조화된 `ask_user` 결과를 제출하고 종료합니다. Core의 SES outbox가 일반 알림 이메일을 보냅니다.
15
+ 7. 사용자가 평소 Agent UI에서 답하면 `d2d_submit_user_response`가 답을 저장하고 item을 다시 pending으로 전환합니다. Watcher가 다시 Agent를 깨워 처리를 이어갑니다.
16
+
17
+ 이 전달은 at-least-once 방식입니다. 중복 wake-up은 정상이며, Core의 claim lease, fencing token, idempotency key가 중복 처리를 방지합니다.
18
+
19
+ ## 요구 사항
20
+
21
+ - Node.js 22 이상
22
+ - dory-to-dory Core에서 발급한 Agent API key
23
+ - 지원되는 Agent CLI 하나 이상
24
+ - Codex CLI
25
+ - Claude Code
26
+ - Gemini CLI
27
+ - Agent가 실행할 도구를 비대화식 환경에서 사용할 수 있도록 구성된 기존 권한 정책
28
+
29
+ ## 설치와 실행
30
+
31
+ 개발 중에는 다음과 같이 실행합니다.
32
+
33
+ ```sh
34
+ pnpm install
35
+ pnpm run build
36
+ node dist/cli.js doctor
37
+ node dist/cli.js run
38
+ ```
39
+
40
+ npm 게시 이후에는 전역 설치할 수 있습니다.
41
+
42
+ ```sh
43
+ npm install --global dory-to-dory-watcher@0.1.0
44
+ dory-watcher doctor
45
+ dory-watcher run
46
+ ```
47
+
48
+ 별도의 MCP npm 설치는 필요하지 않습니다. Watcher가 호환되는 정확한 MCP 버전을 함께 설치하며, Agent의 MCP 설정은 안정적인 `dory-watcher mcp` 명령을 실행하도록 등록해야 합니다.
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "dory-to-dory": {
54
+ "command": "dory-watcher",
55
+ "args": ["mcp"],
56
+ "env": {
57
+ "D2D_API_KEY_FILE": "/absolute/path/to/agent.key"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Agent 설정 형식은 제품마다 다르지만 command와 args의 의미는 같습니다. Watcher를 업데이트하면 함께 묶인 MCP도 업데이트되므로 Agent 설정에서 MCP 버전을 다시 입력할 필요가 없습니다. 설치 명령 자체는 Agent 설정을 몰래 변경하지 않으므로 최초 한 번은 위 launcher를 해당 Agent에 등록해야 합니다.
65
+
66
+ API key를 서비스 설정과 명령행에 반복해서 넣지 않도록 credential 파일 사용을 권장합니다.
67
+
68
+ ```sh
69
+ mkdir -p ~/.config/dory-to-dory
70
+ umask 077
71
+ read -s D2D_WATCHER_KEY
72
+ printf '%s\n' "$D2D_WATCHER_KEY" > ~/.config/dory-to-dory/agent.key
73
+ unset D2D_WATCHER_KEY
74
+ chmod 600 ~/.config/dory-to-dory/agent.key
75
+ export D2D_API_KEY_FILE="$HOME/.config/dory-to-dory/agent.key"
76
+ ```
77
+
78
+ Windows에서는 같은 파일의 ACL을 현재 사용자로 제한한 뒤 `D2D_API_KEY_FILE`에 절대 경로를 설정합니다.
79
+
80
+ ## Agent 선택
81
+
82
+ Codex가 기본값입니다.
83
+
84
+ ```sh
85
+ export D2D_AGENT_KIND=codex
86
+ export D2D_AGENT_WORKSPACE=/absolute/path/to/workspace
87
+ dory-watcher doctor
88
+ ```
89
+
90
+ Claude Code 또는 Gemini CLI는 Agent 종류만 바꿉니다.
91
+
92
+ ```sh
93
+ export D2D_AGENT_KIND=claude
94
+ # 또는
95
+ export D2D_AGENT_KIND=gemini
96
+ ```
97
+
98
+ 각 Adapter는 prompt를 프로세스 인자가 아니라 stdin으로 전달합니다. Codex는 `codex exec --json --ephemeral`, Claude는 print mode와 `stream-json`, Gemini는 headless `stream-json` 모드를 사용합니다. 각 CLI의 사용자 설정에 등록된 MCP와 도구가 로드됩니다.
99
+
100
+ Agent별 추가 인자는 JSON 문자열 배열로 전달할 수 있습니다. Watcher는 위험한 무승인 옵션을 기본으로 켜지 않습니다.
101
+
102
+ ```sh
103
+ export D2D_AGENT_ARGS_JSON='["--sandbox","workspace-write"]'
104
+ ```
105
+
106
+ 다른 Agent는 실행 파일과 인자를 직접 지정합니다. custom 프로그램은 고정 wake-up prompt를 stdin에서 읽고 종료 코드 `0`으로 성공을 알려야 합니다.
107
+
108
+ ```sh
109
+ export D2D_AGENT_KIND=custom
110
+ export D2D_AGENT_COMMAND=/absolute/path/to/my-agent-runner
111
+ export D2D_AGENT_ARGS_JSON='["--headless"]'
112
+ ```
113
+
114
+ ## 환경 변수
115
+
116
+ | 이름 | 필수 | 기본값 | 설명 |
117
+ |---|---:|---|---|
118
+ | `D2D_API_KEY` | 조건부 | - | Agent API key. 파일 방식과 동시에 사용하지 않음 |
119
+ | `D2D_API_KEY_FILE` | 조건부 | - | Agent API key가 한 줄로 저장된 파일 |
120
+ | `D2D_CORE_URL` | 아니요 | `https://dory-to-dory.dnotitia.ai` | 다른 Core 사용 시 재정의 |
121
+ | `D2D_AGENT_KIND` | 아니요 | `codex` | `codex`, `claude`, `gemini`, `custom` |
122
+ | `D2D_AGENT_COMMAND` | custom만 | Agent 종류 이름 | 실행 파일 이름 또는 절대 경로 |
123
+ | `D2D_AGENT_ARGS_JSON` | 아니요 | `[]` | Agent CLI에 추가할 문자열 인자 배열 |
124
+ | `D2D_AGENT_WORKSPACE` | 아니요 | 현재 디렉터리 | Agent 실행 작업공간 |
125
+ | `D2D_RUN_TIMEOUT_SECONDS` | 아니요 | `1200` | Agent 한 번 실행 제한 시간 |
126
+ | `D2D_REQUEST_TIMEOUT_SECONDS` | 아니요 | `15` | reconciliation API 제한 시간 |
127
+ | `D2D_MAX_ITEMS_PER_WAKE` | 아니요 | `10` | 한 wake cycle의 최대 처리 수 |
128
+ | `D2D_RECONNECT_MIN_SECONDS` | 아니요 | `1` | SSE 재연결 최소 backoff |
129
+ | `D2D_RECONNECT_MAX_SECONDS` | 아니요 | `60` | SSE 재연결 최대 backoff |
130
+ | `D2D_AGENT_FAILURE_BACKOFF_SECONDS` | 아니요 | `900` | Agent 실패·무진행 시 LLM 반복 호출을 막는 backoff |
131
+ | `D2D_STATE_FILE` | 아니요 | OS별 사용자 state 경로 | 마지막 SSE event ID 저장 위치 |
132
+
133
+ ## 백그라운드 실행
134
+
135
+ 정상 동작을 `dory-watcher doctor`와 `dory-watcher once`로 먼저 확인한 뒤 OS의 로그인 서비스로 `dory-watcher run`을 등록합니다.
136
+
137
+ - macOS: 사용자 LaunchAgent
138
+ - Linux: systemd user service
139
+ - Windows: Task Scheduler의 사용자 로그인 트리거
140
+
141
+ 서비스에는 key 값 대신 `D2D_API_KEY_FILE`과 Agent 종류, workspace를 전달하십시오. Agent CLI의 로그인 정보와 MCP 설정을 읽을 수 있도록 반드시 같은 OS 사용자 계정으로 실행해야 합니다.
142
+
143
+ ## 보안 및 비용
144
+
145
+ - SSE는 item ID, 요청 본문, 사용자 질문을 보내지 않는 wake-up 신호입니다.
146
+ - API key와 wake-up prompt를 명령행 인자로 넣지 않습니다.
147
+ - Watcher는 사용자의 Agent CLI와 동일한 도구 접근권한을 가집니다. 기존 Agent 권한 정책이 곧 Watcher 처리 한계입니다.
148
+ - pending item이 없으면 LLM을 호출하지 않습니다. 실제 item이 있으면 Agent CLI의 모델/API 요금이 발생할 수 있습니다.
149
+ - 이메일에는 `ask_user` 질문이나 coordination 본문을 넣지 않고 Agent를 열어 확인하라는 일반 안내만 보냅니다.
150
+
151
+ ## 개발
152
+
153
+ ```sh
154
+ pnpm run check
155
+ ```
156
+
157
+ 핵심 확장 지점은 `src/agent.ts`의 Adapter 호출 규약과 `src/watcher.ts`의 orchestration입니다. 새 Agent는 shell 문자열 조합 대신 인자 배열과 stdin을 사용하고, Core 상태를 최종 진실로 취급해야 합니다.
@@ -0,0 +1,17 @@
1
+ import type { AgentKind, WatcherConfig } from "./config.js";
2
+ export declare const WAKE_PROMPT = "This is a dory-to-dory background wake-up. Process at most one pending dory-to-dory item.\n\n1. Call the dory-to-dory MCP tool d2d_claim_next_item exactly once.\n2. If no item is pending, finish immediately without a user-facing message.\n3. If an item is returned, follow the attached natural-language policy and use the tools already configured for this Agent to perform only the permitted work.\n4. Submit the outcome with d2d_submit_item_result. Pass delivery.claim.id as claim_id. Reuse the policy version from the delivery when applicable.\n5. If user confirmation or missing information is required, do not invent an answer and do not try to prompt in this headless terminal. Submit decision ask_user with a concise structured user_input_request so Core can notify the user.\n6. If the reclaimed item includes an answered interaction, use that answer while continuing the task.\n\nDo not process ordinary unrelated work and do not include credentials in output.";
3
+ export interface AgentInvocation {
4
+ command: string;
5
+ args: string[];
6
+ promptOnStdin: string;
7
+ }
8
+ export interface AgentRunResult {
9
+ exitCode: number;
10
+ durationMs: number;
11
+ }
12
+ export declare class AgentRunError extends Error {
13
+ name: string;
14
+ }
15
+ export declare function buildInvocation(kind: AgentKind, command: string, extraArgs: string[]): AgentInvocation;
16
+ export declare function runAgent(config: WatcherConfig): Promise<AgentRunResult>;
17
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5D,eAAO,MAAM,WAAW,68BASyD,CAAC;AAElF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAc,SAAQ,KAAK;IAC7B,IAAI,SAAmB;CACjC;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EAAE,GAClB,eAAe,CAuBjB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CA0C7E"}
package/dist/agent.js ADDED
@@ -0,0 +1,86 @@
1
+ import { spawn } from "node:child_process";
2
+ export const WAKE_PROMPT = `This is a dory-to-dory background wake-up. Process at most one pending dory-to-dory item.
3
+
4
+ 1. Call the dory-to-dory MCP tool d2d_claim_next_item exactly once.
5
+ 2. If no item is pending, finish immediately without a user-facing message.
6
+ 3. If an item is returned, follow the attached natural-language policy and use the tools already configured for this Agent to perform only the permitted work.
7
+ 4. Submit the outcome with d2d_submit_item_result. Pass delivery.claim.id as claim_id. Reuse the policy version from the delivery when applicable.
8
+ 5. If user confirmation or missing information is required, do not invent an answer and do not try to prompt in this headless terminal. Submit decision ask_user with a concise structured user_input_request so Core can notify the user.
9
+ 6. If the reclaimed item includes an answered interaction, use that answer while continuing the task.
10
+
11
+ Do not process ordinary unrelated work and do not include credentials in output.`;
12
+ export class AgentRunError extends Error {
13
+ name = "AgentRunError";
14
+ }
15
+ export function buildInvocation(kind, command, extraArgs) {
16
+ if (kind === "codex") {
17
+ return {
18
+ command,
19
+ args: ["exec", "--json", "--ephemeral", "--skip-git-repo-check", ...extraArgs, "-"],
20
+ promptOnStdin: WAKE_PROMPT,
21
+ };
22
+ }
23
+ if (kind === "claude") {
24
+ return {
25
+ command,
26
+ args: ["-p", "--output-format", "stream-json", "--verbose", ...extraArgs],
27
+ promptOnStdin: WAKE_PROMPT,
28
+ };
29
+ }
30
+ if (kind === "gemini") {
31
+ return {
32
+ command,
33
+ args: ["--output-format", "stream-json", ...extraArgs],
34
+ promptOnStdin: WAKE_PROMPT,
35
+ };
36
+ }
37
+ return { command, args: extraArgs, promptOnStdin: WAKE_PROMPT };
38
+ }
39
+ export async function runAgent(config) {
40
+ const invocation = buildInvocation(config.agentKind, config.agentCommand, config.agentArgs);
41
+ const startedAt = Date.now();
42
+ return await new Promise((resolve, reject) => {
43
+ const child = spawn(invocation.command, invocation.args, {
44
+ cwd: config.workspace,
45
+ env: process.env,
46
+ shell: false,
47
+ windowsHide: true,
48
+ stdio: ["pipe", "pipe", "pipe"],
49
+ });
50
+ let outputBytes = 0;
51
+ const outputLimit = 1024 * 1024;
52
+ const consume = (chunk) => {
53
+ outputBytes += chunk.length;
54
+ if (outputBytes > outputLimit)
55
+ child.kill();
56
+ };
57
+ child.stdout.on("data", consume);
58
+ child.stderr.on("data", consume);
59
+ child.once("error", () => reject(new AgentRunError(`Could not start ${config.agentCommand}`)));
60
+ child.stdin.on("error", () => undefined);
61
+ let timedOut = false;
62
+ const timer = setTimeout(() => {
63
+ timedOut = true;
64
+ child.kill();
65
+ }, config.runTimeoutMs);
66
+ timer.unref();
67
+ child.once("close", (code) => {
68
+ clearTimeout(timer);
69
+ const durationMs = Date.now() - startedAt;
70
+ if (timedOut) {
71
+ reject(new AgentRunError(`${config.agentKind} Agent run timed out`));
72
+ }
73
+ else if (outputBytes > outputLimit) {
74
+ reject(new AgentRunError(`${config.agentKind} Agent produced excessive output`));
75
+ }
76
+ else if (code !== 0) {
77
+ reject(new AgentRunError(`${config.agentKind} Agent exited with code ${code ?? "unknown"}`));
78
+ }
79
+ else {
80
+ resolve({ exitCode: 0, durationMs });
81
+ }
82
+ });
83
+ child.stdin.end(invocation.promptOnStdin);
84
+ });
85
+ }
86
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;iFASsD,CAAC;AAalF,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,IAAI,GAAG,eAAe,CAAC;CACjC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAe,EACf,OAAe,EACf,SAAmB;IAEnB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO;YACL,OAAO;YACP,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,uBAAuB,EAAE,GAAG,SAAS,EAAE,GAAG,CAAC;YACnF,aAAa,EAAE,WAAW;SAC3B,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,OAAO;YACP,IAAI,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;YACzE,aAAa,EAAE,WAAW;SAC3B,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,OAAO;YACP,IAAI,EAAE,CAAC,iBAAiB,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC;YACtD,aAAa,EAAE,WAAW;SAC3B,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAqB;IAClD,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,MAAM,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;YACvD,GAAG,EAAE,MAAM,CAAC,SAAS;YACrB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,KAAK;YACZ,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE;YAChC,WAAW,IAAI,KAAK,CAAC,MAAM,CAAC;YAC5B,IAAI,WAAW,GAAG,WAAW;gBAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC9C,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,mBAAmB,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/F,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QACxB,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,aAAa,CAAC,GAAG,MAAM,CAAC,SAAS,sBAAsB,CAAC,CAAC,CAAC;YACvE,CAAC;iBAAM,IAAI,WAAW,GAAG,WAAW,EAAE,CAAC;gBACrC,MAAM,CAAC,IAAI,aAAa,CAAC,GAAG,MAAM,CAAC,SAAS,kCAAkC,CAAC,CAAC,CAAC;YACnF,CAAC;iBAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,IAAI,aAAa,CAAC,GAAG,MAAM,CAAC,SAAS,2BAA2B,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;YAC/F,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { serveDoryToDoryMcp } from "dory-to-dory-mcp";
3
+ import { loadConfig } from "./config.js";
4
+ import { doctor } from "./doctor.js";
5
+ import { Watcher } from "./watcher.js";
6
+ const HELP = `dory-watcher <command>
7
+
8
+ Commands:
9
+ run Reconcile once, then watch Core SSE events continuously (default)
10
+ once Reconcile and process available items once, then exit
11
+ mcp Run the bundled dory-to-dory MCP server over stdio
12
+ doctor Verify the credential, Core connection, workspace, and Agent CLI
13
+ help Show this help
14
+
15
+ Required configuration:
16
+ D2D_API_KEY or D2D_API_KEY_FILE
17
+
18
+ Agent selection:
19
+ D2D_AGENT_KIND=codex|claude|gemini|custom (default: codex)
20
+ D2D_AGENT_COMMAND=<executable> (required only for custom)
21
+ D2D_AGENT_ARGS_JSON='["arg1","arg2"]' (optional extra CLI arguments)
22
+ D2D_AGENT_WORKSPACE=/absolute/path (default: current directory)`;
23
+ async function main() {
24
+ const command = process.argv[2] ?? "run";
25
+ if (command === "help" || command === "--help" || command === "-h") {
26
+ console.log(HELP);
27
+ return;
28
+ }
29
+ if (command === "mcp") {
30
+ await serveDoryToDoryMcp();
31
+ return;
32
+ }
33
+ const config = loadConfig();
34
+ if (command === "doctor") {
35
+ for (const line of await doctor(config))
36
+ console.log(line);
37
+ return;
38
+ }
39
+ const watcher = new Watcher(config);
40
+ if (command === "once") {
41
+ await watcher.processAvailableWork();
42
+ return;
43
+ }
44
+ if (command !== "run")
45
+ throw new Error(`Unknown command: ${command}`);
46
+ const controller = new AbortController();
47
+ const stop = () => controller.abort();
48
+ process.once("SIGINT", stop);
49
+ process.once("SIGTERM", stop);
50
+ await watcher.run(controller.signal);
51
+ }
52
+ main().catch((error) => {
53
+ console.error(`[dory-watcher] ${error instanceof Error ? error.message : String(error)}`);
54
+ process.exitCode = 1;
55
+ });
56
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;0EAgB6D,CAAC;AAE3E,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IACzC,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACT,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,MAAM,kBAAkB,EAAE,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,MAAM,OAAO,CAAC,oBAAoB,EAAE,CAAC;QACrC,OAAO;IACT,CAAC;IACD,IAAI,OAAO,KAAK,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;IAEtE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9B,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC1F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ export type AgentKind = "codex" | "claude" | "gemini" | "custom";
2
+ export interface WatcherConfig {
3
+ coreUrl: URL;
4
+ apiKey: string;
5
+ agentKind: AgentKind;
6
+ agentCommand: string;
7
+ agentArgs: string[];
8
+ workspace: string;
9
+ runTimeoutMs: number;
10
+ requestTimeoutMs: number;
11
+ reconnectMinMs: number;
12
+ reconnectMaxMs: number;
13
+ agentFailureBackoffMs: number;
14
+ maxItemsPerWake: number;
15
+ stateFile: string;
16
+ }
17
+ export declare class ConfigError extends Error {
18
+ name: string;
19
+ }
20
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): WatcherConfig;
21
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAY,SAAQ,KAAK;IAC3B,IAAI,SAAiB;CAC/B;AAqED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,aAAa,CAwD9E"}
package/dist/config.js ADDED
@@ -0,0 +1,113 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ export class ConfigError extends Error {
5
+ name = "ConfigError";
6
+ }
7
+ const DEFAULT_CORE_URL = "https://dory-to-dory.dnotitia.ai";
8
+ const MAX_CREDENTIAL_FILE_BYTES = 4_096;
9
+ function positiveInteger(value, fallback, name) {
10
+ if (value === undefined || value.trim() === "")
11
+ return fallback;
12
+ const parsed = Number(value);
13
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
14
+ throw new ConfigError(`${name} must be a positive integer`);
15
+ }
16
+ return parsed;
17
+ }
18
+ function credential(env) {
19
+ const direct = env.D2D_API_KEY?.trim();
20
+ const filePath = env.D2D_API_KEY_FILE?.trim();
21
+ if (direct && filePath)
22
+ throw new ConfigError("Set only one of D2D_API_KEY or D2D_API_KEY_FILE");
23
+ if (direct)
24
+ return direct;
25
+ if (!filePath)
26
+ throw new ConfigError("D2D_API_KEY or D2D_API_KEY_FILE is required");
27
+ try {
28
+ const file = statSync(filePath);
29
+ if (!file.isFile())
30
+ throw new ConfigError("D2D_API_KEY_FILE must point to a regular file");
31
+ if (file.size > MAX_CREDENTIAL_FILE_BYTES) {
32
+ throw new ConfigError("D2D_API_KEY_FILE is unexpectedly large");
33
+ }
34
+ if (process.platform !== "win32" && (file.mode & 0o077) !== 0) {
35
+ throw new ConfigError("D2D_API_KEY_FILE must not be accessible by group or other users");
36
+ }
37
+ const value = readFileSync(filePath, "utf8").trim();
38
+ if (!value)
39
+ throw new ConfigError("D2D_API_KEY_FILE is empty");
40
+ return value;
41
+ }
42
+ catch (error) {
43
+ if (error instanceof ConfigError)
44
+ throw error;
45
+ throw new ConfigError(`Could not read D2D_API_KEY_FILE: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ }
48
+ function defaultStateFile(env) {
49
+ if (process.platform === "win32") {
50
+ return join(env.LOCALAPPDATA || join(homedir(), "AppData", "Local"), "dory-to-dory-watcher", "state.json");
51
+ }
52
+ if (process.platform === "darwin") {
53
+ return join(homedir(), "Library", "Application Support", "dory-to-dory-watcher", "state.json");
54
+ }
55
+ return join(env.XDG_STATE_HOME || join(homedir(), ".local", "state"), "dory-to-dory-watcher", "state.json");
56
+ }
57
+ function parseAgentArgs(value) {
58
+ if (!value?.trim())
59
+ return [];
60
+ try {
61
+ const parsed = JSON.parse(value);
62
+ if (!Array.isArray(parsed) || !parsed.every((entry) => typeof entry === "string")) {
63
+ throw new Error("expected a JSON array of strings");
64
+ }
65
+ return parsed;
66
+ }
67
+ catch (error) {
68
+ throw new ConfigError(`D2D_AGENT_ARGS_JSON is invalid: ${error instanceof Error ? error.message : String(error)}`);
69
+ }
70
+ }
71
+ export function loadConfig(env = process.env) {
72
+ const rawUrl = env.D2D_CORE_URL?.trim() || DEFAULT_CORE_URL;
73
+ let coreUrl;
74
+ try {
75
+ coreUrl = new URL(rawUrl.endsWith("/") ? rawUrl : `${rawUrl}/`);
76
+ }
77
+ catch {
78
+ throw new ConfigError("D2D_CORE_URL must be a valid URL");
79
+ }
80
+ if (!new Set(["http:", "https:"]).has(coreUrl.protocol)) {
81
+ throw new ConfigError("D2D_CORE_URL must use http or https");
82
+ }
83
+ const rawAgentKind = env.D2D_AGENT_KIND?.trim().toLowerCase() || "codex";
84
+ if (!new Set(["codex", "claude", "gemini", "custom"]).has(rawAgentKind)) {
85
+ throw new ConfigError("D2D_AGENT_KIND must be codex, claude, gemini, or custom");
86
+ }
87
+ const agentKind = rawAgentKind;
88
+ const defaultCommand = agentKind === "custom" ? "" : agentKind;
89
+ const agentCommand = env.D2D_AGENT_COMMAND?.trim() || defaultCommand;
90
+ if (!agentCommand)
91
+ throw new ConfigError("D2D_AGENT_COMMAND is required for a custom Agent");
92
+ const reconnectMinSeconds = positiveInteger(env.D2D_RECONNECT_MIN_SECONDS, 1, "D2D_RECONNECT_MIN_SECONDS");
93
+ const reconnectMaxSeconds = positiveInteger(env.D2D_RECONNECT_MAX_SECONDS, 60, "D2D_RECONNECT_MAX_SECONDS");
94
+ if (reconnectMaxSeconds < reconnectMinSeconds) {
95
+ throw new ConfigError("D2D_RECONNECT_MAX_SECONDS must be at least D2D_RECONNECT_MIN_SECONDS");
96
+ }
97
+ return {
98
+ coreUrl,
99
+ apiKey: credential(env),
100
+ agentKind,
101
+ agentCommand,
102
+ agentArgs: parseAgentArgs(env.D2D_AGENT_ARGS_JSON),
103
+ workspace: resolve(env.D2D_AGENT_WORKSPACE?.trim() || process.cwd()),
104
+ runTimeoutMs: positiveInteger(env.D2D_RUN_TIMEOUT_SECONDS, 1_200, "D2D_RUN_TIMEOUT_SECONDS") * 1_000,
105
+ requestTimeoutMs: positiveInteger(env.D2D_REQUEST_TIMEOUT_SECONDS, 15, "D2D_REQUEST_TIMEOUT_SECONDS") * 1_000,
106
+ reconnectMinMs: reconnectMinSeconds * 1_000,
107
+ reconnectMaxMs: reconnectMaxSeconds * 1_000,
108
+ agentFailureBackoffMs: positiveInteger(env.D2D_AGENT_FAILURE_BACKOFF_SECONDS, 900, "D2D_AGENT_FAILURE_BACKOFF_SECONDS") * 1_000,
109
+ maxItemsPerWake: positiveInteger(env.D2D_MAX_ITEMS_PER_WAKE, 10, "D2D_MAX_ITEMS_PER_WAKE"),
110
+ stateFile: resolve(env.D2D_STATE_FILE?.trim() || defaultStateFile(env)),
111
+ };
112
+ }
113
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { WatcherConfig } from "./config.js";
2
+ export interface WatcherStatus {
3
+ has_pending_items: boolean;
4
+ pending_count: number;
5
+ claimed_count: number;
6
+ waiting_for_user_count: number;
7
+ latest_event_id: number | null;
8
+ }
9
+ export declare class CoreError extends Error {
10
+ readonly status?: number | undefined;
11
+ name: string;
12
+ constructor(message: string, status?: number | undefined);
13
+ }
14
+ export declare class CoreClient {
15
+ private readonly config;
16
+ private readonly fetcher;
17
+ constructor(config: WatcherConfig, fetcher?: typeof fetch);
18
+ authorizationHeaders(): Headers;
19
+ eventsUrl(): URL;
20
+ status(): Promise<WatcherStatus>;
21
+ }
22
+ //# sourceMappingURL=core-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-client.d.ts","sourceRoot":"","sources":["../src/core-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,qBAAa,SAAU,SAAQ,KAAK;IAKhC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAJjB,IAAI,SAAe;gBAG1B,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA;CAI3B;AAED,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,OAAO,KAAa;IAGhD,oBAAoB,IAAI,OAAO;IAQ/B,SAAS,IAAI,GAAG;IAIV,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;CA0BvC"}
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export class CoreError extends Error {
3
+ status;
4
+ name = "CoreError";
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ }
9
+ }
10
+ export class CoreClient {
11
+ config;
12
+ fetcher;
13
+ constructor(config, fetcher = fetch) {
14
+ this.config = config;
15
+ this.fetcher = fetcher;
16
+ }
17
+ authorizationHeaders() {
18
+ return new Headers({
19
+ Accept: "application/json",
20
+ Authorization: `Bearer ${this.config.apiKey}`,
21
+ "X-Request-ID": randomUUID(),
22
+ });
23
+ }
24
+ eventsUrl() {
25
+ return new URL("v1/watcher/events", this.config.coreUrl);
26
+ }
27
+ async status() {
28
+ const controller = new AbortController();
29
+ const timer = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
30
+ try {
31
+ const response = await this.fetcher(new URL("v1/watcher/status", this.config.coreUrl), {
32
+ headers: this.authorizationHeaders(),
33
+ signal: controller.signal,
34
+ });
35
+ const text = await response.text();
36
+ const body = text ? JSON.parse(text) : undefined;
37
+ if (!response.ok) {
38
+ const detail = body && typeof body === "object" && "detail" in body && typeof body.detail === "string"
39
+ ? body.detail
40
+ : `HTTP ${response.status}`;
41
+ throw new CoreError(`dory-to-dory Core status failed: ${detail}`, response.status);
42
+ }
43
+ return body;
44
+ }
45
+ catch (error) {
46
+ if (error instanceof CoreError)
47
+ throw error;
48
+ const timeout = error instanceof Error && error.name === "AbortError";
49
+ throw new CoreError(timeout ? "dory-to-dory Core status timed out" : "Could not reach dory-to-dory Core");
50
+ }
51
+ finally {
52
+ clearTimeout(timer);
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=core-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-client.js","sourceRoot":"","sources":["../src/core-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAYzC,MAAM,OAAO,SAAU,SAAQ,KAAK;IAKvB;IAJF,IAAI,GAAG,WAAW,CAAC;IAE5B,YACE,OAAe,EACN,MAAe;QAExB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,WAAM,GAAN,MAAM,CAAS;IAG1B,CAAC;CACF;AAED,MAAM,OAAO,UAAU;IAEF;IACA;IAFnB,YACmB,MAAqB,EACrB,UAAwB,KAAK;QAD7B,WAAM,GAAN,MAAM,CAAe;QACrB,YAAO,GAAP,OAAO,CAAsB;IAC7C,CAAC;IAEJ,oBAAoB;QAClB,OAAO,IAAI,OAAO,CAAC;YACjB,MAAM,EAAE,kBAAkB;YAC1B,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAC7C,cAAc,EAAE,UAAU,EAAE;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,SAAS;QACP,OAAO,IAAI,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACjF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;gBACrF,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE;gBACpC,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,GAAY,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC1D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,MAAM,GACV,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;oBACrF,CAAC,CAAC,IAAI,CAAC,MAAM;oBACb,CAAC,CAAC,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,IAAI,SAAS,CAAC,oCAAoC,MAAM,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrF,CAAC;YACD,OAAO,IAAqB,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS;gBAAE,MAAM,KAAK,CAAC;YAC5C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;YACtE,MAAM,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC;QAC5G,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import type { WatcherConfig } from "./config.js";
2
+ export declare function doctor(config: WatcherConfig): Promise<string[]>;
3
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyBjD,wBAAsB,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWrE"}
package/dist/doctor.js ADDED
@@ -0,0 +1,42 @@
1
+ import { spawn } from "node:child_process";
2
+ import { stat } from "node:fs/promises";
3
+ import { CoreClient } from "./core-client.js";
4
+ async function commandVersion(command) {
5
+ return await new Promise((resolve, reject) => {
6
+ const child = spawn(command, ["--version"], {
7
+ shell: false,
8
+ windowsHide: true,
9
+ stdio: ["ignore", "pipe", "pipe"],
10
+ });
11
+ let output = "";
12
+ child.stdout.on("data", (chunk) => {
13
+ if (output.length < 2_000)
14
+ output += chunk.toString("utf8");
15
+ });
16
+ child.stderr.on("data", (chunk) => {
17
+ if (output.length < 2_000)
18
+ output += chunk.toString("utf8");
19
+ });
20
+ child.once("error", () => reject(new Error(`Could not start ${command}`)));
21
+ child.once("close", (code) => {
22
+ if (code === 0)
23
+ resolve(output.trim().split("\n")[0] || "version not reported");
24
+ else
25
+ reject(new Error(`${command} --version exited with code ${code ?? "unknown"}`));
26
+ });
27
+ });
28
+ }
29
+ export async function doctor(config) {
30
+ const workspace = await stat(config.workspace);
31
+ if (!workspace.isDirectory())
32
+ throw new Error("D2D_AGENT_WORKSPACE must be a directory");
33
+ const version = await commandVersion(config.agentCommand);
34
+ const status = await new CoreClient(config).status();
35
+ return [
36
+ `Core: connected (${config.coreUrl.origin})`,
37
+ `Agent: ${config.agentKind} (${version})`,
38
+ `Workspace: ${config.workspace}`,
39
+ `Work state: pending=${status.pending_count}, claimed=${status.claimed_count}, waiting_for_user=${status.waiting_for_user_count}`,
40
+ ];
41
+ }
42
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGxC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,KAAK,UAAU,cAAc,CAAC,OAAe;IAC3C,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE;YAC1C,KAAK,EAAE,KAAK;YACZ,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,sBAAsB,CAAC,CAAC;;gBAC3E,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,+BAA+B,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAqB;IAChD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACzF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IACrD,OAAO;QACL,oBAAoB,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG;QAC5C,UAAU,MAAM,CAAC,SAAS,KAAK,OAAO,GAAG;QACzC,cAAc,MAAM,CAAC,SAAS,EAAE;QAChC,uBAAuB,MAAM,CAAC,aAAa,aAAa,MAAM,CAAC,aAAa,sBAAsB,MAAM,CAAC,sBAAsB,EAAE;KAClI,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { buildInvocation, runAgent, WAKE_PROMPT, AgentRunError } from "./agent.js";
2
+ export { loadConfig, ConfigError, type AgentKind, type WatcherConfig } from "./config.js";
3
+ export { CoreClient, CoreError, type WatcherStatus } from "./core-client.js";
4
+ export { connectWatcherEvents, parseSseStream, type WatcherEvent } from "./sse.js";
5
+ export { AgentNoProgressError, Watcher, type WatcherDependencies } from "./watcher.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { buildInvocation, runAgent, WAKE_PROMPT, AgentRunError } from "./agent.js";
2
+ export { loadConfig, ConfigError } from "./config.js";
3
+ export { CoreClient, CoreError } from "./core-client.js";
4
+ export { connectWatcherEvents, parseSseStream } from "./sse.js";
5
+ export { AgentNoProgressError, Watcher } from "./watcher.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAsC,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAsB,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAqB,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC"}
package/dist/sse.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { CoreClient } from "./core-client.js";
2
+ export interface WatcherEvent {
3
+ event: string;
4
+ data: string;
5
+ id?: number;
6
+ }
7
+ export declare function parseSseStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<WatcherEvent>;
8
+ export declare function connectWatcherEvents(core: CoreClient, lastEventId?: number, signal?: AbortSignal, fetcher?: typeof fetch): AsyncGenerator<WatcherEvent>;
9
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAoBD,wBAAuB,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,YAAY,CAAC,CAsBtG;AAED,wBAAuB,oBAAoB,CACzC,IAAI,EAAE,UAAU,EAChB,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,OAAO,KAAa,GAC5B,cAAc,CAAC,YAAY,CAAC,CAY9B"}
package/dist/sse.js ADDED
@@ -0,0 +1,67 @@
1
+ function parseEvent(block) {
2
+ let event = "message";
3
+ let id;
4
+ const data = [];
5
+ for (const line of block.split("\n")) {
6
+ if (!line || line.startsWith(":"))
7
+ continue;
8
+ const separator = line.indexOf(":");
9
+ const field = separator < 0 ? line : line.slice(0, separator);
10
+ let value = separator < 0 ? "" : line.slice(separator + 1);
11
+ if (value.startsWith(" "))
12
+ value = value.slice(1);
13
+ if (field === "event")
14
+ event = value;
15
+ if (field === "data")
16
+ data.push(value);
17
+ if (field === "id" && /^\d+$/.test(value))
18
+ id = Number(value);
19
+ }
20
+ if (data.length === 0 && id === undefined)
21
+ return undefined;
22
+ return id === undefined ? { event, data: data.join("\n") } : { event, data: data.join("\n"), id };
23
+ }
24
+ export async function* parseSseStream(stream) {
25
+ const reader = stream.getReader();
26
+ const decoder = new TextDecoder();
27
+ let buffer = "";
28
+ try {
29
+ while (true) {
30
+ const { done, value } = await reader.read();
31
+ buffer += decoder.decode(value, { stream: !done }).replaceAll("\r\n", "\n").replaceAll("\r", "\n");
32
+ let boundary = buffer.indexOf("\n\n");
33
+ while (boundary >= 0) {
34
+ const parsed = parseEvent(buffer.slice(0, boundary));
35
+ buffer = buffer.slice(boundary + 2);
36
+ if (parsed)
37
+ yield parsed;
38
+ boundary = buffer.indexOf("\n\n");
39
+ }
40
+ if (done)
41
+ break;
42
+ }
43
+ const parsed = parseEvent(buffer);
44
+ if (parsed)
45
+ yield parsed;
46
+ }
47
+ finally {
48
+ reader.releaseLock();
49
+ }
50
+ }
51
+ export async function* connectWatcherEvents(core, lastEventId, signal, fetcher = fetch) {
52
+ const headers = core.authorizationHeaders();
53
+ headers.set("Accept", "text/event-stream");
54
+ if (lastEventId !== undefined)
55
+ headers.set("Last-Event-ID", String(lastEventId));
56
+ const init = { headers };
57
+ if (signal !== undefined)
58
+ init.signal = signal;
59
+ const response = await fetcher(core.eventsUrl(), init);
60
+ if (!response.ok) {
61
+ throw new Error(`dory-to-dory watcher stream failed with HTTP ${response.status}`);
62
+ }
63
+ if (!response.body)
64
+ throw new Error("dory-to-dory watcher stream did not include a response body");
65
+ yield* parseSseStream(response.body);
66
+ }
67
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function readLastEventId(path: string): Promise<number | undefined>;
2
+ export declare function writeLastEventId(path: string, eventId: number): Promise<void>;
3
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAOA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoB/E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnF"}
package/dist/state.js ADDED
@@ -0,0 +1,31 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export async function readLastEventId(path) {
4
+ try {
5
+ const parsed = JSON.parse(await readFile(path, "utf8"));
6
+ if (parsed &&
7
+ typeof parsed === "object" &&
8
+ "last_event_id" in parsed &&
9
+ typeof parsed.last_event_id === "number" &&
10
+ Number.isSafeInteger(parsed.last_event_id) &&
11
+ parsed.last_event_id >= 0) {
12
+ return parsed.last_event_id;
13
+ }
14
+ return undefined;
15
+ }
16
+ catch (error) {
17
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
18
+ return undefined;
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+ export async function writeLastEventId(path, eventId) {
24
+ const directory = dirname(path);
25
+ await mkdir(directory, { recursive: true, mode: 0o700 });
26
+ const temporary = `${path}.${process.pid}.tmp`;
27
+ const payload = { last_event_id: eventId };
28
+ await writeFile(temporary, `${JSON.stringify(payload)}\n`, { encoding: "utf8", mode: 0o600 });
29
+ await rename(temporary, path);
30
+ }
31
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,IACE,MAAM;YACN,OAAO,MAAM,KAAK,QAAQ;YAC1B,eAAe,IAAI,MAAM;YACzB,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ;YACxC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC;YAC1C,MAAM,CAAC,aAAa,IAAI,CAAC,EACzB,CAAC;YACD,OAAO,MAAM,CAAC,aAAa,CAAC;QAC9B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,OAAe;IAClE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAC/C,MAAM,OAAO,GAAc,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;IACtD,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { WatcherConfig } from "./config.js";
2
+ import { CoreClient } from "./core-client.js";
3
+ import { type AgentRunResult } from "./agent.js";
4
+ import { connectWatcherEvents } from "./sse.js";
5
+ export interface WatcherDependencies {
6
+ core?: CoreClient;
7
+ invokeAgent?: (config: WatcherConfig) => Promise<AgentRunResult>;
8
+ connectEvents?: typeof connectWatcherEvents;
9
+ sleep?: (milliseconds: number) => Promise<void>;
10
+ log?: (message: string) => void;
11
+ }
12
+ export declare class AgentNoProgressError extends Error {
13
+ name: string;
14
+ }
15
+ export declare class Watcher {
16
+ private readonly config;
17
+ private readonly core;
18
+ private readonly invokeAgent;
19
+ private readonly connectEvents;
20
+ private readonly sleep;
21
+ private readonly log;
22
+ constructor(config: WatcherConfig, dependencies?: WatcherDependencies);
23
+ processAvailableWork(maxItems?: number): Promise<number>;
24
+ run(signal?: AbortSignal): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAGhD,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACjE,aAAa,CAAC,EAAE,OAAO,oBAAoB,CAAC;IAC5C,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IACpC,IAAI,SAA0B;CACxC;AAKD,qBAAa,OAAO;IAQhB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqD;IACjF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA8B;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;IAChE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;gBAG7B,MAAM,EAAE,aAAa,EACtC,YAAY,GAAE,mBAAwB;IASlC,oBAAoB,CAAC,QAAQ,SAA8B,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB7E,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B/C"}
@@ -0,0 +1,82 @@
1
+ import { CoreClient } from "./core-client.js";
2
+ import { AgentRunError, runAgent } from "./agent.js";
3
+ import { connectWatcherEvents } from "./sse.js";
4
+ import { readLastEventId, writeLastEventId } from "./state.js";
5
+ export class AgentNoProgressError extends Error {
6
+ name = "AgentNoProgressError";
7
+ }
8
+ const defaultSleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
9
+ export class Watcher {
10
+ config;
11
+ core;
12
+ invokeAgent;
13
+ connectEvents;
14
+ sleep;
15
+ log;
16
+ constructor(config, dependencies = {}) {
17
+ this.config = config;
18
+ this.core = dependencies.core ?? new CoreClient(config);
19
+ this.invokeAgent = dependencies.invokeAgent ?? runAgent;
20
+ this.connectEvents = dependencies.connectEvents ?? connectWatcherEvents;
21
+ this.sleep = dependencies.sleep ?? defaultSleep;
22
+ this.log = dependencies.log ?? ((message) => console.error(`[dory-watcher] ${message}`));
23
+ }
24
+ async processAvailableWork(maxItems = this.config.maxItemsPerWake) {
25
+ let processed = 0;
26
+ while (processed < maxItems) {
27
+ const before = await this.core.status();
28
+ if (!before.has_pending_items)
29
+ return processed;
30
+ await this.invokeAgent(this.config);
31
+ processed += 1;
32
+ const after = await this.core.status();
33
+ if (after.waiting_for_user_count > before.waiting_for_user_count) {
34
+ this.log("An item is waiting for user input; Core notification delivery will handle it.");
35
+ }
36
+ if (!after.has_pending_items)
37
+ return processed;
38
+ if (after.pending_count >= before.pending_count &&
39
+ after.claimed_count === before.claimed_count &&
40
+ after.waiting_for_user_count === before.waiting_for_user_count) {
41
+ throw new AgentNoProgressError("Agent exited without changing dory-to-dory work state");
42
+ }
43
+ }
44
+ this.log(`Stopped this wake cycle after ${maxItems} item(s); later events will reconcile remaining work.`);
45
+ return processed;
46
+ }
47
+ async run(signal) {
48
+ let lastEventId = await readLastEventId(this.config.stateFile);
49
+ let reconnectDelay = this.config.reconnectMinMs;
50
+ while (!signal?.aborted) {
51
+ try {
52
+ await this.processAvailableWork();
53
+ for await (const event of this.connectEvents(this.core, lastEventId, signal)) {
54
+ reconnectDelay = this.config.reconnectMinMs;
55
+ if (event.id !== undefined) {
56
+ lastEventId = event.id;
57
+ await writeLastEventId(this.config.stateFile, event.id);
58
+ }
59
+ if (event.event === "work_available" || event.event === "reconcile") {
60
+ await this.processAvailableWork();
61
+ }
62
+ if (signal?.aborted)
63
+ return;
64
+ }
65
+ if (!signal?.aborted)
66
+ throw new Error("Watcher event stream closed");
67
+ }
68
+ catch (error) {
69
+ if (signal?.aborted)
70
+ return;
71
+ this.log(error instanceof Error ? error.message : String(error));
72
+ const agentFailure = error instanceof AgentRunError || error instanceof AgentNoProgressError;
73
+ const baseDelay = agentFailure ? this.config.agentFailureBackoffMs : reconnectDelay;
74
+ const jitter = Math.floor(Math.random() * Math.max(1, baseDelay / 4));
75
+ await this.sleep(baseDelay + jitter);
76
+ if (!agentFailure)
77
+ reconnectDelay = Math.min(reconnectDelay * 2, this.config.reconnectMaxMs);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "dory-to-dory-watcher",
3
+ "version": "0.1.0",
4
+ "description": "Cross-platform SSE watcher that wakes a configured local Agent CLI for dory-to-dory work",
5
+ "type": "module",
6
+ "bin": {
7
+ "dory-watcher": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "dory-to-dory",
22
+ "agent",
23
+ "watcher",
24
+ "sse",
25
+ "codex",
26
+ "claude",
27
+ "gemini"
28
+ ],
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "dory-to-dory-mcp": "0.1.2"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^24.0.0",
41
+ "tsx": "^4.20.0",
42
+ "typescript": "^5.9.0",
43
+ "vitest": "^3.2.0"
44
+ },
45
+ "scripts": {
46
+ "build": "node scripts/clean.mjs && tsc -p tsconfig.json",
47
+ "check": "npm run build && npm run test",
48
+ "clean": "node scripts/clean.mjs",
49
+ "dev": "tsx src/cli.ts",
50
+ "test": "vitest run"
51
+ }
52
+ }