@nema-project/nema 0.0.0-stage → 0.1.4

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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunks/chunk-HEIQINX3.js +825 -0
  3. package/dist/chunks/chunk-WT3XUFWU.js +86 -0
  4. package/dist/claude/adapter.d.ts +19 -0
  5. package/dist/claude/adapter.js +258 -0
  6. package/dist/claude/events.d.ts +29 -0
  7. package/dist/claude/events.js +75 -0
  8. package/dist/claude/launch.d.ts +22 -0
  9. package/dist/claude/launch.js +80 -0
  10. package/dist/claude/token.d.ts +9 -0
  11. package/dist/claude/token.js +87 -0
  12. package/dist/codex/adapter.d.ts +17 -0
  13. package/dist/codex/adapter.js +151 -0
  14. package/dist/codex/events.d.ts +16 -0
  15. package/dist/codex/events.js +47 -0
  16. package/dist/codex/isolation.d.ts +33 -0
  17. package/dist/codex/isolation.js +115 -0
  18. package/dist/config.d.ts +29 -0
  19. package/dist/config.js +75 -0
  20. package/dist/configCommands.d.ts +13 -0
  21. package/dist/configCommands.js +69 -0
  22. package/dist/daemon/cleanup.d.ts +5 -0
  23. package/dist/daemon/cleanup.js +49 -0
  24. package/dist/daemon/connection.d.ts +22 -0
  25. package/dist/daemon/connection.js +166 -0
  26. package/dist/daemon/rpc.d.ts +14 -0
  27. package/dist/daemon/rpc.js +20 -0
  28. package/dist/daemon/runner.d.ts +34 -0
  29. package/dist/daemon/runner.js +94 -0
  30. package/dist/daemon/setup.d.ts +39 -0
  31. package/dist/daemon/setup.js +95 -0
  32. package/dist/direct/adapter.d.ts +30 -0
  33. package/dist/direct/adapter.js +190 -0
  34. package/dist/direct/authCommands.d.ts +54 -0
  35. package/dist/direct/authCommands.js +268 -0
  36. package/dist/direct/authStore.d.ts +120 -0
  37. package/dist/direct/authStore.js +351 -0
  38. package/dist/direct/byok.d.ts +27 -0
  39. package/dist/direct/byok.js +67 -0
  40. package/dist/direct/codexLogin.d.ts +34 -0
  41. package/dist/direct/codexLogin.js +93 -0
  42. package/dist/direct/jwt.d.ts +4 -0
  43. package/dist/direct/jwt.js +22 -0
  44. package/dist/direct/lock.d.ts +16 -0
  45. package/dist/direct/lock.js +134 -0
  46. package/dist/direct/loop.d.ts +33 -0
  47. package/dist/direct/loop.js +110 -0
  48. package/dist/direct/siwc.d.ts +63 -0
  49. package/dist/direct/siwc.js +403 -0
  50. package/dist/direct/tools.d.ts +17 -0
  51. package/dist/direct/tools.js +104 -0
  52. package/dist/direct/transport.d.ts +177 -0
  53. package/dist/direct/transport.js +437 -0
  54. package/dist/doctor.d.ts +28 -0
  55. package/dist/doctor.js +178 -0
  56. package/dist/extensions/handlers.d.ts +2 -0
  57. package/dist/extensions/handlers.js +10 -0
  58. package/dist/extensions/store.d.ts +11 -0
  59. package/dist/extensions/store.js +132 -0
  60. package/dist/github/client.d.ts +47 -0
  61. package/dist/github/client.js +383 -0
  62. package/dist/github/gh.d.ts +56 -0
  63. package/dist/github/gh.js +385 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +144 -0
  66. package/dist/localSettings.d.ts +8 -0
  67. package/dist/localSettings.js +26 -0
  68. package/dist/mcp/server.d.ts +5 -0
  69. package/dist/mcp/server.js +83 -0
  70. package/dist/ownedProcess.d.ts +39 -0
  71. package/dist/ownedProcess.js +229 -0
  72. package/dist/platform.d.ts +10 -0
  73. package/dist/platform.js +60 -0
  74. package/dist/report/commands.d.ts +41 -0
  75. package/dist/report/commands.js +159 -0
  76. package/dist/report/desktop.d.ts +3 -0
  77. package/dist/report/desktop.js +123 -0
  78. package/dist/report/git.d.ts +5 -0
  79. package/dist/report/git.js +42 -0
  80. package/dist/report/install.d.ts +22 -0
  81. package/dist/report/install.js +183 -0
  82. package/dist/report/send.d.ts +35 -0
  83. package/dist/report/send.js +208 -0
  84. package/dist/report/token.d.ts +5 -0
  85. package/dist/report/token.js +86 -0
  86. package/dist/repos/git.d.ts +48 -0
  87. package/dist/repos/git.js +370 -0
  88. package/dist/repos/graphify.d.ts +32 -0
  89. package/dist/repos/graphify.js +292 -0
  90. package/dist/repos/handlers.d.ts +20 -0
  91. package/dist/repos/handlers.js +255 -0
  92. package/dist/repos/indexState.d.ts +10 -0
  93. package/dist/repos/indexState.js +45 -0
  94. package/dist/repos/pickDirectory.d.ts +11 -0
  95. package/dist/repos/pickDirectory.js +140 -0
  96. package/dist/repos/reportCheck.d.ts +13 -0
  97. package/dist/repos/reportCheck.js +268 -0
  98. package/dist/repos/watcher.d.ts +64 -0
  99. package/dist/repos/watcher.js +202 -0
  100. package/dist/room/client.d.ts +26 -0
  101. package/dist/room/client.js +76 -0
  102. package/dist/secrets/file.d.ts +12 -0
  103. package/dist/secrets/file.js +89 -0
  104. package/dist/secrets/github.d.ts +10 -0
  105. package/dist/secrets/github.js +29 -0
  106. package/dist/secrets/permissions.d.ts +7 -0
  107. package/dist/secrets/permissions.js +86 -0
  108. package/dist/systemGit.d.ts +14 -0
  109. package/dist/systemGit.js +75 -0
  110. package/dist/wp/commands.d.ts +15 -0
  111. package/dist/wp/commands.js +103 -0
  112. package/package.json +26 -4
  113. package/src/report/SKILL.md +21 -0
  114. package/README.md +0 -3
@@ -0,0 +1,166 @@
1
+ import {
2
+ parseHubToDaemon
3
+ } from "../chunks/chunk-HEIQINX3.js";
4
+
5
+ // daemon/connection.js
6
+ import WebSocket from "ws";
7
+ import { cliVersion } from "../direct/siwc.js";
8
+ import { reportTokenHash } from "../report/token.js";
9
+ import { reconcileWorkspaceDirs, removeWorkspaceDir } from "./cleanup.js";
10
+ var VERSION_SKEW_ERROR = "hub \u4E0E nema CLI \u7248\u672C\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u540C\u65F6\u66F4\u65B0\uFF08\u91CD\u65B0\u6784\u5EFA compose \u5E76\u91CD\u542F nema daemon\uFF09";
11
+ function skewGuidance(message) {
12
+ if (!message.includes("\u7248\u672C\u4E0D\u4E00\u81F4"))
13
+ return null;
14
+ const match = /@nema-project\/nema@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(message);
15
+ if (match)
16
+ return `run: npm i -g @nema-project/nema@${match[1]}`;
17
+ return "run: npm i -g @nema-project/nema@<hub \u7248\u672C>\n\u8BF7\u5148\u67E5\u770B\u6B63\u5728\u8FD0\u884C\u7684 hub \u7684\u7248\u672C";
18
+ }
19
+ function connectDaemon(opts) {
20
+ const reconnectMs = opts.reconnectMs ?? 3e3;
21
+ const url = opts.hubUrl.replace(/^http/, "ws").replace(/\/+$/, "") + "/daemon";
22
+ let ws = null;
23
+ let timer = null;
24
+ let closed = false;
25
+ let stop = false;
26
+ let closePromise;
27
+ const rpcInflight = /* @__PURE__ */ new Set();
28
+ const open = () => {
29
+ if (closed)
30
+ return;
31
+ const sock = new WebSocket(url);
32
+ ws = sock;
33
+ const send = (m) => {
34
+ if (closed)
35
+ return false;
36
+ if (sock.readyState !== WebSocket.OPEN)
37
+ return false;
38
+ try {
39
+ sock.send(JSON.stringify(m));
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ };
45
+ sock.on("open", () => {
46
+ if (closed)
47
+ return;
48
+ opts.bindSend?.(send);
49
+ let hash = null;
50
+ try {
51
+ const tokenHome = opts.reportTokenHome ?? opts.nemaHome;
52
+ hash = tokenHome ? reportTokenHash(tokenHome) : null;
53
+ } catch (e) {
54
+ console.error(`[nema] report token not sent: ${e instanceof Error ? e.message : String(e)}`);
55
+ }
56
+ send({
57
+ type: "hello",
58
+ token: opts.token,
59
+ version: opts.version ?? cliVersion(),
60
+ adapters: opts.runner.adapterNames,
61
+ ...hash ? { reportTokenHash: hash } : {}
62
+ });
63
+ });
64
+ sock.on("message", (d) => {
65
+ if (closed)
66
+ return;
67
+ let m;
68
+ let rawMsg;
69
+ try {
70
+ rawMsg = JSON.parse(String(d));
71
+ m = parseHubToDaemon(rawMsg);
72
+ } catch (e) {
73
+ const r = rawMsg;
74
+ if (r && typeof r === "object" && r.type === "task.assign" && typeof r.taskId === "string") {
75
+ console.error(`[nema] cannot parse task.assign ${r.taskId}: ${e instanceof Error ? e.message : String(e)}`);
76
+ send({ type: "task.finished", taskId: r.taskId, ok: false, error: VERSION_SKEW_ERROR });
77
+ } else {
78
+ console.error("[nema] ignoring unparseable hub message:", e instanceof Error ? e.message : String(e));
79
+ }
80
+ return;
81
+ }
82
+ const nemaHome = opts.nemaHome;
83
+ if (m.type === "error") {
84
+ console.error(`[nema] hub: ${m.message}`);
85
+ const guidance = skewGuidance(m.message);
86
+ if (guidance) {
87
+ console.error(guidance);
88
+ stop = true;
89
+ }
90
+ } else if (m.type === "welcome") {
91
+ console.log(`[nema] daemon connected to ${opts.hubUrl}`);
92
+ const ids = m.workspaceIds;
93
+ if (nemaHome && ids) {
94
+ void opts.runner.exclusive(async () => {
95
+ try {
96
+ const removed = await reconcileWorkspaceDirs(nemaHome, ids);
97
+ if (removed.length > 0)
98
+ console.log(`[nema] removed isolation dirs of deleted workspaces: ${removed.join(", ")}`);
99
+ } catch (e) {
100
+ console.error("[nema] isolation dir cleanup failed", e);
101
+ }
102
+ });
103
+ }
104
+ } else if (m.type === "workspace.deleted") {
105
+ const id = m.workspaceId;
106
+ if (nemaHome) {
107
+ void opts.runner.exclusive(async () => {
108
+ try {
109
+ await removeWorkspaceDir(nemaHome, id);
110
+ } catch (e) {
111
+ console.error(`[nema] failed to remove isolation dir of ${id}`, e);
112
+ }
113
+ });
114
+ }
115
+ } else if (m.type === "task.assign") {
116
+ const taskId = m.taskId;
117
+ opts.runner.enqueue(m, (event) => send({ type: "task.event", taskId, event }), (r) => send(r.ok ? { type: "task.finished", taskId, ok: true, reply: r.reply } : { type: "task.finished", taskId, ok: false, error: r.error }));
118
+ } else if (m.type === "rpc.request" && opts.rpc) {
119
+ const router = opts.rpc;
120
+ const request = router.handle(m).then((res) => {
121
+ if (!closed)
122
+ send(res);
123
+ });
124
+ rpcInflight.add(request);
125
+ void request.then(() => rpcInflight.delete(request), () => rpcInflight.delete(request));
126
+ }
127
+ });
128
+ sock.on("error", () => {
129
+ });
130
+ sock.on("close", () => {
131
+ if (ws === sock) {
132
+ ws = null;
133
+ opts.bindSend?.(null);
134
+ }
135
+ if (!closed && !stop)
136
+ timer = setTimeout(open, reconnectMs);
137
+ });
138
+ };
139
+ open();
140
+ return {
141
+ close() {
142
+ if (closePromise)
143
+ return closePromise;
144
+ closed = true;
145
+ if (timer)
146
+ clearTimeout(timer);
147
+ opts.bindSend?.(null);
148
+ const socket = ws;
149
+ const socketClosed = !socket || socket.readyState === WebSocket.CLOSED ? Promise.resolve() : new Promise((resolve) => {
150
+ socket.once("close", () => resolve());
151
+ try {
152
+ socket.terminate();
153
+ } catch {
154
+ resolve();
155
+ }
156
+ });
157
+ closePromise = Promise.all([...rpcInflight, socketClosed]).then(() => {
158
+ });
159
+ return closePromise;
160
+ }
161
+ };
162
+ }
163
+ export {
164
+ VERSION_SKEW_ERROR,
165
+ connectDaemon
166
+ };
@@ -0,0 +1,14 @@
1
+ import type { DaemonToHub, HubToDaemon } from "@nema/shared";
2
+ export type RpcHandler = (params: unknown) => Promise<unknown>;
3
+ type RpcRequest = Extract<HubToDaemon, {
4
+ type: "rpc.request";
5
+ }>;
6
+ type RpcResponse = Extract<DaemonToHub, {
7
+ type: "rpc.response";
8
+ }>;
9
+ export declare class RpcRouter {
10
+ private readonly handlers;
11
+ register(method: string, h: RpcHandler): void;
12
+ handle(req: RpcRequest): Promise<RpcResponse>;
13
+ }
14
+ export {};
@@ -0,0 +1,20 @@
1
+ // daemon/rpc.js
2
+ var RpcRouter = class {
3
+ handlers = /* @__PURE__ */ new Map();
4
+ register(method, h) {
5
+ this.handlers.set(method, h);
6
+ }
7
+ async handle(req) {
8
+ const handler = this.handlers.get(req.method);
9
+ if (!handler)
10
+ return { type: "rpc.response", id: req.id, ok: false, error: `\u672A\u77E5\u65B9\u6CD5 ${req.method}` };
11
+ try {
12
+ return { type: "rpc.response", id: req.id, ok: true, result: await handler(req.params) };
13
+ } catch (e) {
14
+ return { type: "rpc.response", id: req.id, ok: false, error: e instanceof Error ? e.message : String(e) };
15
+ }
16
+ }
17
+ };
18
+ export {
19
+ RpcRouter
20
+ };
@@ -0,0 +1,34 @@
1
+ import type { TaskAssign, TaskEvent } from "@nema/shared";
2
+ import { type NemaConfig } from "../config.js";
3
+ export type AgentResult = {
4
+ ok: true;
5
+ reply: string;
6
+ } | {
7
+ ok: false;
8
+ error: string;
9
+ };
10
+ export interface AgentAdapter {
11
+ name: string;
12
+ run(task: TaskAssign, onEvent: (e: TaskEvent) => void, signal?: AbortSignal): Promise<AgentResult>;
13
+ }
14
+ /** "auto" -> the configured adapter. "codex" and "codex-cli" are the retired CLI names; both run codex-direct. */
15
+ export declare function resolveAdapterName(requested: string, config: NemaConfig): string;
16
+ /** Runs tasks one at a time, globally, in enqueue order. */
17
+ export declare class Runner {
18
+ private readonly adapters;
19
+ private readonly getConfig;
20
+ private chain;
21
+ private stopped;
22
+ private stopping?;
23
+ private activeController?;
24
+ constructor(adapters: AgentAdapter[], getConfig?: () => NemaConfig);
25
+ get adapterNames(): string[];
26
+ /**
27
+ * Runs `job` in the same queue as tasks, so it never overlaps a running agent (e.g. removing a
28
+ * workspace's isolation dir while Codex is still using it). Failures are swallowed.
29
+ */
30
+ exclusive(job: () => Promise<void>): Promise<void>;
31
+ enqueue(task: TaskAssign, onEvent: (e: TaskEvent) => void, onDone: (r: AgentResult) => void): void;
32
+ /** Permanently stop accepting work and wait until active adapter cleanup has finished. */
33
+ stop(): Promise<void>;
34
+ }
@@ -0,0 +1,94 @@
1
+ // daemon/runner.js
2
+ import { configErrorMessage, DEFAULT_CONFIG } from "../config.js";
3
+ function resolveAdapterName(requested, config) {
4
+ if (requested === "auto")
5
+ return config.adapter;
6
+ if (requested === "codex" || requested === "codex-cli")
7
+ return "codex-direct";
8
+ return requested;
9
+ }
10
+ var Runner = class {
11
+ adapters;
12
+ getConfig;
13
+ chain = Promise.resolve();
14
+ stopped = false;
15
+ stopping;
16
+ activeController;
17
+ constructor(adapters, getConfig = () => ({ ...DEFAULT_CONFIG })) {
18
+ this.adapters = adapters;
19
+ this.getConfig = getConfig;
20
+ }
21
+ get adapterNames() {
22
+ return this.adapters.map((a) => a.name);
23
+ }
24
+ /**
25
+ * Runs `job` in the same queue as tasks, so it never overlaps a running agent (e.g. removing a
26
+ * workspace's isolation dir while Codex is still using it). Failures are swallowed.
27
+ */
28
+ exclusive(job) {
29
+ if (this.stopped)
30
+ return this.stopping ?? Promise.resolve();
31
+ const run = this.chain.then(async () => {
32
+ if (!this.stopped)
33
+ await job();
34
+ }).catch(() => {
35
+ });
36
+ this.chain = run;
37
+ return run;
38
+ }
39
+ enqueue(task, onEvent, onDone) {
40
+ if (this.stopped)
41
+ return;
42
+ this.chain = this.chain.then(async () => {
43
+ if (this.stopped)
44
+ return;
45
+ let result;
46
+ let name;
47
+ try {
48
+ name = resolveAdapterName(task.adapter, this.getConfig());
49
+ } catch (e) {
50
+ result = { ok: false, error: configErrorMessage(e) };
51
+ }
52
+ if (name !== void 0) {
53
+ const adapter = this.adapters.find((a) => a.name === name);
54
+ if (!adapter) {
55
+ result = { ok: false, error: `adapter ${name} \u4E0D\u53EF\u7528` };
56
+ } else {
57
+ const controller = new AbortController();
58
+ this.activeController = controller;
59
+ try {
60
+ result = await adapter.run(task, (event) => {
61
+ if (!this.stopped)
62
+ onEvent(event);
63
+ }, controller.signal);
64
+ } catch (e) {
65
+ result = { ok: false, error: e instanceof Error ? e.message : String(e) };
66
+ } finally {
67
+ if (this.activeController === controller)
68
+ this.activeController = void 0;
69
+ }
70
+ }
71
+ }
72
+ if (this.stopped)
73
+ return;
74
+ try {
75
+ onDone(result);
76
+ } catch {
77
+ }
78
+ });
79
+ }
80
+ /** Permanently stop accepting work and wait until active adapter cleanup has finished. */
81
+ stop() {
82
+ if (this.stopping)
83
+ return this.stopping;
84
+ this.stopped = true;
85
+ this.activeController?.abort(new Error("runner stopped"));
86
+ this.stopping = this.chain.then(() => {
87
+ });
88
+ return this.stopping;
89
+ }
90
+ };
91
+ export {
92
+ Runner,
93
+ resolveAdapterName
94
+ };
@@ -0,0 +1,39 @@
1
+ import { type NemaConfig } from "../config.js";
2
+ import { type GithubReader } from "../github/gh.js";
3
+ import type { GraphifyOpts } from "../repos/graphify.js";
4
+ import { RepoWatcher } from "../repos/watcher.js";
5
+ import { Runner } from "./runner.js";
6
+ import type { OwnedHelperScope } from "@nema/shared/ownedHelper";
7
+ export type RunnerSetup = {
8
+ nemaHome: string;
9
+ userCodexHome: string;
10
+ version: string;
11
+ getConfig?: () => NemaConfig;
12
+ cliEntry: string;
13
+ execPath?: string;
14
+ ownedHelpers?: OwnedHelperScope;
15
+ };
16
+ /** The daemon's runner. Codex stays on the direct adapter; Claude Code is selected only when config.adapter is claude-code. */
17
+ export declare function createRunner(o: RunnerSetup): Runner;
18
+ export type DaemonStart = {
19
+ hubUrl: string;
20
+ token: string;
21
+ nemaHome: string;
22
+ userCodexHome: string;
23
+ version: string;
24
+ getConfig?: () => NemaConfig;
25
+ cliEntry: string;
26
+ execPath?: string;
27
+ /** For tests: production passes the packaged `tools/graphify` binary, not a PATH lookup. */
28
+ graphify?: GraphifyOpts;
29
+ /** Explicit provider injection for tests or callers; production uses the signed-in `gh` CLI. */
30
+ github?: GithubReader;
31
+ /** Where the report token the hub should accept lives; defaults to `nemaHome`. */
32
+ reportTokenHome?: string;
33
+ ownedHelpers?: OwnedHelperScope;
34
+ };
35
+ /** Connects the daemon and answers repo RPC plus `plugin.call` and `plugins.status`. */
36
+ export declare function startDaemon(o: DaemonStart): {
37
+ close(): Promise<void>;
38
+ watcher: RepoWatcher;
39
+ };
@@ -0,0 +1,95 @@
1
+ // daemon/setup.js
2
+ import { ClaudeCodeAdapter } from "../claude/adapter.js";
3
+ import { loadConfig } from "../config.js";
4
+ import { CodexDirectAdapter } from "../direct/adapter.js";
5
+ import { GithubGh } from "../github/gh.js";
6
+ import { registerExtensionHandlers } from "../extensions/handlers.js";
7
+ import { registerRepoHandlers } from "../repos/handlers.js";
8
+ import { parseReposSync, RepoWatcher } from "../repos/watcher.js";
9
+ import { connectDaemon } from "./connection.js";
10
+ import { RpcRouter } from "./rpc.js";
11
+ import { Runner } from "./runner.js";
12
+ function createRunner(o) {
13
+ if (!o.cliEntry)
14
+ throw new Error("cliEntry is required");
15
+ const direct = new CodexDirectAdapter({ nemaHome: o.nemaHome, version: o.version, codexHome: o.userCodexHome });
16
+ const claude = new ClaudeCodeAdapter({ nemaHome: o.nemaHome, cliEntry: o.cliEntry, execPath: o.execPath ?? process.execPath, ownedHelpers: o.ownedHelpers });
17
+ return new Runner([direct, claude], o.getConfig ?? (() => loadConfig(o.nemaHome)));
18
+ }
19
+ function startDaemon(o) {
20
+ const runner = createRunner(o);
21
+ const rpc = new RpcRouter();
22
+ const graphify = { ...o.graphify, ownedHelpers: o.ownedHelpers };
23
+ const github = o.github ?? new GithubGh({ ownedHelpers: o.ownedHelpers });
24
+ registerExtensionHandlers(rpc, o.nemaHome);
25
+ let sendMessage = null;
26
+ const watcher = new RepoWatcher({
27
+ nemaHome: o.nemaHome,
28
+ graphify,
29
+ onMoved: (move) => {
30
+ if (!sendMessage)
31
+ return false;
32
+ return sendMessage({ type: "repo.moved", repoId: move.repoId, mainRef: move.mainRef, from: move.from, to: move.to });
33
+ },
34
+ onFetch: (report) => {
35
+ const message = {
36
+ type: "repo.fetched",
37
+ repoId: report.repoId,
38
+ ok: report.ok,
39
+ at: report.at
40
+ };
41
+ if (report.error !== void 0)
42
+ message.error = report.error;
43
+ sendMessage?.(message);
44
+ }
45
+ });
46
+ registerRepoHandlers(rpc, {
47
+ nemaHome: o.nemaHome,
48
+ graphify,
49
+ github,
50
+ ownedHelpers: o.ownedHelpers,
51
+ prepareRepo: (repoId, taskId) => watcher.prepare(repoId, taskId)
52
+ });
53
+ rpc.register("repos.sync", async (params) => {
54
+ watcher.setRepos(parseReposSync(params));
55
+ return { ok: true };
56
+ });
57
+ rpc.register("repo.tick", async (params) => {
58
+ const repoId = params?.repoId;
59
+ if (typeof repoId !== "string" || repoId === "")
60
+ throw new Error("invalid repo params");
61
+ void watcher.tickRepo(repoId).catch((error) => {
62
+ console.error("[nema] repo tick failed", error);
63
+ });
64
+ return { ok: true };
65
+ });
66
+ watcher.start();
67
+ const connection = connectDaemon({
68
+ hubUrl: o.hubUrl,
69
+ token: o.token,
70
+ runner,
71
+ nemaHome: o.nemaHome,
72
+ ...o.reportTokenHome ? { reportTokenHome: o.reportTokenHome } : {},
73
+ version: o.version,
74
+ rpc,
75
+ bindSend: (send) => {
76
+ sendMessage = send;
77
+ }
78
+ });
79
+ let closing;
80
+ return {
81
+ watcher,
82
+ close() {
83
+ if (closing)
84
+ return closing;
85
+ const tasks = [runner.stop(), watcher.stop(), connection.close(), github.close()];
86
+ closing = Promise.all(tasks).then(() => {
87
+ });
88
+ return closing;
89
+ }
90
+ };
91
+ }
92
+ export {
93
+ createRunner,
94
+ startDaemon
95
+ };
@@ -0,0 +1,30 @@
1
+ import type { TaskAssign, TaskEvent } from "@nema/shared";
2
+ import type { AgentAdapter, AgentResult } from "../daemon/runner.js";
3
+ import { type Refresher } from "./authStore.js";
4
+ import { type TransportProfile } from "./transport.js";
5
+ export declare const NOT_LOGGED_IN = "\u672A\u767B\u5F55\u76F4\u8FDE Codex\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C nema auth login";
6
+ export declare const RUN_TIMEOUT_MESSAGE = "Codex \u76F4\u8FDE\u8FD0\u884C\u8D85\u65F6";
7
+ export declare const DIRECT_USER_TEXT = "\u8BF7\u6309\u4E0A\u9762\u7684\u6307\u4EE4\u5904\u7406\u672C\u6B21\u7528\u6237\u6D88\u606F\uFF0C\u5E76\u53EA\u901A\u8FC7\u63D0\u4F9B\u7684\u51FD\u6570\u8BFB\u5199\u9879\u76EE\u77E5\u8BC6\u3002";
8
+ export declare const FALLBACK_MODEL = "gpt-5.5";
9
+ export type CodexDirectAdapterOptions = {
10
+ nemaHome: string;
11
+ version: string;
12
+ profile?: TransportProfile;
13
+ timeoutMs?: number;
14
+ now?: () => number;
15
+ /** Token refresher (defaults to SIWC). Unused when authMode is compat or byok. */
16
+ refresher?: Refresher;
17
+ /** Codex CLI home whose auth.json compat mode reads. Defaults to $CODEX_HOME or ~/.codex. */
18
+ codexHome?: string;
19
+ };
20
+ /** Local time as `YYYY-MM-DD HH:mm`. */
21
+ export declare function formatLocalTime(ms: number): string;
22
+ export declare class CodexDirectAdapter implements AgentAdapter {
23
+ private readonly opts;
24
+ readonly name = "codex-direct";
25
+ constructor(opts: CodexDirectAdapterOptions);
26
+ run(task: TaskAssign, onEvent: (e: TaskEvent) => void, ownerSignal?: AbortSignal): Promise<AgentResult>;
27
+ private execute;
28
+ /** First listed model; the fallback only when the list is unavailable (API error) or empty. */
29
+ private defaultModel;
30
+ }
@@ -0,0 +1,190 @@
1
+ // direct/adapter.js
2
+ import { randomUUID } from "node:crypto";
3
+ import { configErrorMessage, DEFAULT_BYOK_BASE_URL, loadConfig } from "../config.js";
4
+ import { RoomClient } from "../room/client.js";
5
+ import { AuthStore, ReloginRequiredError, UNUSABLE_FILE_MESSAGE } from "./authStore.js";
6
+ import { ByokTokenSource } from "./byok.js";
7
+ import { CodexFileTokenSource, defaultCodexHome } from "./codexLogin.js";
8
+ import { DEFAULT_LIMITS, runToolLoop } from "./loop.js";
9
+ import { siwcRefresher } from "./siwc.js";
10
+ import { roomTools } from "./tools.js";
11
+ import { BYOK_PROFILE, ByokRejectedError, COMPAT_PROFILE, listModels, ModelApiError, ModelQuotaError, SIWC_PROFILE, streamResponse } from "./transport.js";
12
+ var NOT_LOGGED_IN = "\u672A\u767B\u5F55\u76F4\u8FDE Codex\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C nema auth login";
13
+ var RUN_TIMEOUT_MESSAGE = "Codex \u76F4\u8FDE\u8FD0\u884C\u8D85\u65F6";
14
+ var DIRECT_USER_TEXT = "\u8BF7\u6309\u4E0A\u9762\u7684\u6307\u4EE4\u5904\u7406\u672C\u6B21\u7528\u6237\u6D88\u606F\uFF0C\u5E76\u53EA\u901A\u8FC7\u63D0\u4F9B\u7684\u51FD\u6570\u8BFB\u5199\u9879\u76EE\u77E5\u8BC6\u3002";
15
+ var FALLBACK_MODEL = "gpt-5.5";
16
+ var DEFAULT_TIMEOUT_MS = 6e5;
17
+ var PROGRESS_INTERVAL_MS = 500;
18
+ var PROGRESS_MAX_CHARS = 2e3;
19
+ var RunTimeoutError = class extends Error {
20
+ constructor() {
21
+ super(RUN_TIMEOUT_MESSAGE);
22
+ this.name = "RunTimeoutError";
23
+ }
24
+ };
25
+ var pad = (n) => String(n).padStart(2, "0");
26
+ function formatLocalTime(ms) {
27
+ const d = new Date(ms);
28
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
29
+ }
30
+ function errorMessage(e) {
31
+ if (e instanceof RunTimeoutError || e instanceof ReloginRequiredError || e instanceof ByokRejectedError)
32
+ return e.message;
33
+ if (e instanceof ModelQuotaError) {
34
+ if (e.code === "subscription_sharing_usage_unavailable")
35
+ return "\u8BA2\u9605\u989D\u5EA6\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
36
+ if (e.code === "usage_not_included")
37
+ return "\u5F53\u524D\u8BA2\u9605\u4E0D\u5305\u542B\u6B64\u7528\u91CF";
38
+ return "\u8BA2\u9605\u989D\u5EA6\u5DF2\u7528\u5B8C" + (e.resetsAt !== void 0 ? `\uFF0C\u9884\u8BA1 ${formatLocalTime(e.resetsAt)} \u6062\u590D` : "");
39
+ }
40
+ if (e instanceof ModelApiError)
41
+ return "\u6A21\u578B\u63A5\u53E3\u9519\u8BEF\uFF1A" + e.message;
42
+ return e instanceof Error ? e.message : String(e);
43
+ }
44
+ var CodexDirectAdapter = class {
45
+ opts;
46
+ name = "codex-direct";
47
+ constructor(opts) {
48
+ this.opts = opts;
49
+ }
50
+ async run(task, onEvent, ownerSignal) {
51
+ if (ownerSignal?.aborted)
52
+ return { ok: false, error: "Codex \u76F4\u8FDE\u8FD0\u884C\u5DF2\u4E2D\u6B62" };
53
+ let config;
54
+ try {
55
+ config = loadConfig(this.opts.nemaHome);
56
+ } catch (e) {
57
+ return { ok: false, error: configErrorMessage(e) };
58
+ }
59
+ let source;
60
+ let profile;
61
+ let configuredModel;
62
+ if (config.authMode === "compat") {
63
+ source = new CodexFileTokenSource(this.opts.codexHome ?? defaultCodexHome(), this.opts.now);
64
+ profile = this.opts.profile ?? COMPAT_PROFILE;
65
+ try {
66
+ await source.getAccessToken();
67
+ } catch (e) {
68
+ return { ok: false, error: errorMessage(e) };
69
+ }
70
+ if (ownerSignal?.aborted)
71
+ return { ok: false, error: "Codex \u76F4\u8FDE\u8FD0\u884C\u5DF2\u4E2D\u6B62" };
72
+ } else if (config.authMode === "byok") {
73
+ if (!config.model)
74
+ return { ok: false, error: "BYOK \u9700\u8981\u6307\u5B9A\u6A21\u578B\uFF0C\u8BF7\u8FD0\u884C nema config set model <\u540D\u79F0>" };
75
+ source = new ByokTokenSource(this.opts.nemaHome);
76
+ profile = { ...BYOK_PROFILE, baseUrl: config.byokBaseUrl ?? DEFAULT_BYOK_BASE_URL };
77
+ configuredModel = config.model;
78
+ try {
79
+ await source.getAccessToken();
80
+ } catch (e) {
81
+ return { ok: false, error: errorMessage(e) };
82
+ }
83
+ if (ownerSignal?.aborted)
84
+ return { ok: false, error: "Codex \u76F4\u8FDE\u8FD0\u884C\u5DF2\u4E2D\u6B62" };
85
+ } else {
86
+ const store = new AuthStore(this.opts.nemaHome, this.opts.refresher ?? siwcRefresher);
87
+ const { auth, reason } = store.inspect();
88
+ if (!auth)
89
+ return { ok: false, error: reason === "unusable" ? UNUSABLE_FILE_MESSAGE : NOT_LOGGED_IN };
90
+ source = store;
91
+ profile = this.opts.profile ?? SIWC_PROFILE;
92
+ }
93
+ const ac = new AbortController();
94
+ const abortFromOwner = () => ac.abort(ownerSignal?.reason ?? new Error("run stopped"));
95
+ ownerSignal?.addEventListener("abort", abortFromOwner, { once: true });
96
+ if (ownerSignal?.aborted)
97
+ abortFromOwner();
98
+ const timeout = new Promise((_, reject) => {
99
+ ac.signal.addEventListener("abort", () => reject(ac.signal.reason), { once: true });
100
+ });
101
+ timeout.catch(() => {
102
+ });
103
+ const timer = setTimeout(() => ac.abort(new RunTimeoutError()), task.limits?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
104
+ const execution = this.execute(task, onEvent, source, profile, ac.signal, configuredModel);
105
+ try {
106
+ return await Promise.race([execution, timeout]);
107
+ } catch (e) {
108
+ return { ok: false, error: errorMessage(ac.signal.reason instanceof RunTimeoutError ? ac.signal.reason : e) };
109
+ } finally {
110
+ clearTimeout(timer);
111
+ if (!ac.signal.aborted)
112
+ ac.abort(new Error("run finished"));
113
+ ownerSignal?.removeEventListener("abort", abortFromOwner);
114
+ if (ownerSignal)
115
+ await execution.catch(() => {
116
+ });
117
+ }
118
+ }
119
+ async execute(task, rawOnEvent, source, profile, signal, configuredModel) {
120
+ const onEvent = (e) => {
121
+ if (!signal.aborted)
122
+ rawOnEvent(e);
123
+ };
124
+ let config;
125
+ try {
126
+ config = loadConfig(this.opts.nemaHome);
127
+ } catch (e) {
128
+ throw new Error(configErrorMessage(e), { cause: e });
129
+ }
130
+ const ctx = { auth: source, profile, version: this.opts.version, signal };
131
+ const model = configuredModel ?? config.model ?? await this.defaultModel(ctx);
132
+ const room = new RoomClient(task.hubUrl, task.runToken, signal);
133
+ const sessionId = randomUUID();
134
+ const now = this.opts.now ?? Date.now;
135
+ const tools = roomTools(task.pluginTools);
136
+ const { reply } = await runToolLoop({
137
+ instructions: task.prompt,
138
+ userText: DIRECT_USER_TEXT,
139
+ room,
140
+ pluginTools: task.pluginTools,
141
+ signal,
142
+ ...task.limits ? { limits: { ...DEFAULT_LIMITS, maxModelCalls: task.limits.maxModelCalls, maxToolCalls: task.limits.maxToolCalls } } : {},
143
+ onProgress: (text) => onEvent({ type: "tool_call", text }),
144
+ call: async (input) => {
145
+ let text = "";
146
+ let sent = "";
147
+ let lastAt = -Infinity;
148
+ const flush = () => {
149
+ if (text === sent)
150
+ return;
151
+ sent = text;
152
+ lastAt = now();
153
+ onEvent({ type: "agent_message", text: text.slice(-PROGRESS_MAX_CHARS) });
154
+ };
155
+ try {
156
+ return await streamResponse({ model, instructions: task.prompt, input, tools, reasoning: { effort: config.reasoningEffort, summary: "auto" }, sessionId }, {
157
+ ...ctx,
158
+ onTextDelta: (d) => {
159
+ text += d;
160
+ if (now() - lastAt >= PROGRESS_INTERVAL_MS)
161
+ flush();
162
+ }
163
+ });
164
+ } finally {
165
+ if (!signal.aborted)
166
+ flush();
167
+ }
168
+ }
169
+ });
170
+ return { ok: true, reply };
171
+ }
172
+ /** First listed model; the fallback only when the list is unavailable (API error) or empty. */
173
+ async defaultModel(ctx) {
174
+ try {
175
+ return (await listModels(ctx))[0] ?? FALLBACK_MODEL;
176
+ } catch (e) {
177
+ if (e instanceof ModelApiError)
178
+ return FALLBACK_MODEL;
179
+ throw e;
180
+ }
181
+ }
182
+ };
183
+ export {
184
+ CodexDirectAdapter,
185
+ DIRECT_USER_TEXT,
186
+ FALLBACK_MODEL,
187
+ NOT_LOGGED_IN,
188
+ RUN_TIMEOUT_MESSAGE,
189
+ formatLocalTime
190
+ };