@nema-project/nema 0.0.0-stage → 0.1.3

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,134 @@
1
+ // direct/lock.js
2
+ import { randomUUID } from "node:crypto";
3
+ import { closeSync, constants, linkSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync } from "node:fs";
4
+ var LockTimeoutError = class extends Error {
5
+ lockPath;
6
+ timeoutMs;
7
+ constructor(lockPath, timeoutMs) {
8
+ super(`Timed out after ${timeoutMs} ms waiting for lock ${lockPath}`);
9
+ this.lockPath = lockPath;
10
+ this.timeoutMs = timeoutMs;
11
+ this.name = "LockTimeoutError";
12
+ }
13
+ };
14
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
15
+ function pidAlive(pid) {
16
+ try {
17
+ process.kill(pid, 0);
18
+ return true;
19
+ } catch (e) {
20
+ return e.code !== "ESRCH";
21
+ }
22
+ }
23
+ function readRaw(path) {
24
+ try {
25
+ return readFileSync(path, "utf8");
26
+ } catch (e) {
27
+ if (e.code === "ENOENT")
28
+ return null;
29
+ throw e;
30
+ }
31
+ }
32
+ function parseHolder(raw) {
33
+ try {
34
+ const v = JSON.parse(raw);
35
+ if (Number.isInteger(v.pid) && v.pid > 0 && typeof v.at === "number")
36
+ return v;
37
+ } catch {
38
+ }
39
+ return null;
40
+ }
41
+ function isStale(path, raw, staleMs) {
42
+ const h = parseHolder(raw);
43
+ if (h)
44
+ return !pidAlive(h.pid);
45
+ try {
46
+ return Date.now() - statSync(path).mtimeMs > staleMs;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ function takeOver(path, staleRaw) {
52
+ const tomb = `${path}.stale-${process.pid}-${randomUUID()}`;
53
+ try {
54
+ renameSync(path, tomb);
55
+ } catch (e) {
56
+ if (e.code === "ENOENT")
57
+ return;
58
+ throw e;
59
+ }
60
+ try {
61
+ if (readRaw(tomb) !== staleRaw) {
62
+ try {
63
+ linkSync(tomb, path);
64
+ } catch {
65
+ }
66
+ }
67
+ } finally {
68
+ try {
69
+ unlinkSync(tomb);
70
+ } catch {
71
+ }
72
+ }
73
+ }
74
+ async function withFileLock(lockPath, fn, opts = {}) {
75
+ const timeoutMs = opts.timeoutMs ?? 15e3;
76
+ const pollMs = opts.pollMs ?? 50;
77
+ const staleMs = opts.staleMs ?? 3e4;
78
+ const token = randomUUID();
79
+ const deadline = Date.now() + timeoutMs;
80
+ for (; ; ) {
81
+ try {
82
+ const fd = openSync(lockPath, "wx", 384);
83
+ try {
84
+ writeSync(fd, JSON.stringify({ pid: process.pid, at: Date.now(), token }));
85
+ } finally {
86
+ closeSync(fd);
87
+ }
88
+ break;
89
+ } catch (e) {
90
+ if (e.code !== "EEXIST")
91
+ throw e;
92
+ }
93
+ const raw = readRaw(lockPath);
94
+ if (raw === null)
95
+ continue;
96
+ if (isStale(lockPath, raw, staleMs)) {
97
+ takeOver(lockPath, raw);
98
+ continue;
99
+ }
100
+ if (Date.now() >= deadline)
101
+ throw new LockTimeoutError(lockPath, timeoutMs);
102
+ await sleep(pollMs);
103
+ }
104
+ const heartbeat = setInterval(() => {
105
+ try {
106
+ const raw = readRaw(lockPath);
107
+ if (raw === null || parseHolder(raw)?.token !== token)
108
+ return;
109
+ const fd = openSync(lockPath, constants.O_WRONLY | constants.O_TRUNC | constants.O_NOFOLLOW);
110
+ try {
111
+ writeSync(fd, JSON.stringify({ pid: process.pid, at: Date.now(), token }));
112
+ } finally {
113
+ closeSync(fd);
114
+ }
115
+ } catch {
116
+ }
117
+ }, Math.max(1, Math.floor(staleMs / 3)));
118
+ heartbeat.unref();
119
+ try {
120
+ return await fn();
121
+ } finally {
122
+ clearInterval(heartbeat);
123
+ try {
124
+ const raw = readRaw(lockPath);
125
+ if (raw !== null && parseHolder(raw)?.token === token)
126
+ unlinkSync(lockPath);
127
+ } catch {
128
+ }
129
+ }
130
+ }
131
+ export {
132
+ LockTimeoutError,
133
+ withFileLock
134
+ };
@@ -0,0 +1,33 @@
1
+ import type { PluginToolSpec } from "@nema/shared";
2
+ import type { RoomClient } from "../room/client.js";
3
+ import { type RoomApi } from "./tools.js";
4
+ import type { CollectedResponse, InputItem } from "./transport.js";
5
+ export type LoopLimits = {
6
+ maxModelCalls: number;
7
+ maxToolCalls: number;
8
+ maxUnknownTools: number;
9
+ maxContinuations: number;
10
+ };
11
+ export declare const DEFAULT_LIMITS: LoopLimits;
12
+ export declare const CONTINUE_TEXT = "\u8BF7\u7EE7\u7EED\u3002";
13
+ export type ToolLoopOptions = {
14
+ /** Sent as the request `instructions` by `call`; kept here so callers pass the hub prompt in one place. */
15
+ instructions: string;
16
+ userText: string;
17
+ call: (input: InputItem[]) => Promise<CollectedResponse>;
18
+ room: RoomClient | RoomApi;
19
+ /** Plugin functions from `task.assign`. Routed through `dispatchTool`, so they count toward the tool limit. */
20
+ pluginTools?: PluginToolSpec[];
21
+ /** Short progress notes (e.g. which Room function is being called). */
22
+ onProgress: (text: string) => void;
23
+ limits?: LoopLimits;
24
+ /** Once aborted (timeout / run end) the loop stops and emits no further progress. */
25
+ signal?: AbortSignal;
26
+ };
27
+ /**
28
+ * Runs the model until it answers without function calls. Returns the final assistant text (all text of the
29
+ * final answer, joined across continuations). Throws an Error with a user-facing Chinese message on any limit.
30
+ */
31
+ export declare function runToolLoop(opts: ToolLoopOptions): Promise<{
32
+ reply: string;
33
+ }>;
@@ -0,0 +1,110 @@
1
+ // direct/loop.js
2
+ import { createHash } from "node:crypto";
3
+ import { dispatchTool } from "./tools.js";
4
+ import { isFunctionCall, isMessage } from "./transport.js";
5
+ var DEFAULT_LIMITS = { maxModelCalls: 24, maxToolCalls: 60, maxUnknownTools: 5, maxContinuations: 3 };
6
+ var CONTINUE_TEXT = "\u8BF7\u7EE7\u7EED\u3002";
7
+ var MAX_CALL_ID = 64;
8
+ var userMessage = (text) => ({ type: "message", role: "user", content: [{ type: "input_text", text }] });
9
+ var sha32 = (s) => createHash("sha256").update(s).digest("hex").slice(0, 32);
10
+ var CallIds = class {
11
+ used = /* @__PURE__ */ new Set();
12
+ assign(raw) {
13
+ let base = typeof raw === "string" && raw !== "" ? raw : "call_missing";
14
+ if (base.length > MAX_CALL_ID)
15
+ base = `call_${sha32(base)}`;
16
+ let id = base;
17
+ for (let n = 1; this.used.has(id); n++) {
18
+ const suffix = `_dup${n}`;
19
+ id = base.length + suffix.length > MAX_CALL_ID ? `call_${sha32(base)}${suffix}` : base + suffix;
20
+ }
21
+ this.used.add(id);
22
+ return id;
23
+ }
24
+ };
25
+ var textParts = (m) => (Array.isArray(m.content) ? m.content : []).filter((p) => !!p && p.type === "output_text" && typeof p.text === "string").map((p) => ({ type: "output_text", text: p.text }));
26
+ async function runToolLoop(opts) {
27
+ const limits = opts.limits ?? DEFAULT_LIMITS;
28
+ const history = [userMessage(opts.userText)];
29
+ const ids = new CallIds();
30
+ let modelCalls = 0;
31
+ let toolCalls = 0;
32
+ let unknownTools = 0;
33
+ let continuations = 0;
34
+ let answer = "";
35
+ const signal = opts.signal;
36
+ const progress = (text) => {
37
+ if (!signal?.aborted)
38
+ opts.onProgress(text);
39
+ };
40
+ for (; ; ) {
41
+ signal?.throwIfAborted();
42
+ if (modelCalls >= limits.maxModelCalls)
43
+ throw new Error(`Agent \u8D85\u51FA\u5355\u6B21\u8FD0\u884C\u4E0A\u9650\uFF08\u6A21\u578B\u8C03\u7528 ${limits.maxModelCalls} \u6B21\uFF09`);
44
+ modelCalls++;
45
+ const res = await opts.call([...history]);
46
+ signal?.throwIfAborted();
47
+ const calls = [];
48
+ let text = "";
49
+ for (const item of res.items) {
50
+ if (item.type === "reasoning") {
51
+ const enc = item.encrypted_content;
52
+ if (typeof enc !== "string" || enc === "")
53
+ continue;
54
+ const summary = item.summary;
55
+ const parts = (Array.isArray(summary) ? summary : []).filter((p) => !!p && p.type === "summary_text" && typeof p.text === "string").map((p) => ({ type: "summary_text", text: p.text }));
56
+ history.push({ type: "reasoning", summary: parts, encrypted_content: enc });
57
+ } else if (isMessage(item)) {
58
+ const parts = textParts(item);
59
+ if (parts.length === 0)
60
+ continue;
61
+ history.push({ type: "message", role: "assistant", content: parts });
62
+ text += parts.map((p) => p.text).join("");
63
+ } else if (isFunctionCall(item)) {
64
+ const call = {
65
+ call_id: ids.assign(item.call_id),
66
+ name: typeof item.name === "string" ? item.name : "",
67
+ arguments: typeof item.arguments === "string" ? item.arguments : ""
68
+ };
69
+ history.push({ type: "function_call", ...call });
70
+ calls.push(call);
71
+ }
72
+ }
73
+ if (calls.length > 0) {
74
+ answer = "";
75
+ for (const c of calls) {
76
+ if (toolCalls >= limits.maxToolCalls)
77
+ throw new Error(`Agent \u8D85\u51FA\u5355\u6B21\u8FD0\u884C\u4E0A\u9650\uFF08\u5DE5\u5177\u8C03\u7528 ${limits.maxToolCalls} \u6B21\uFF09`);
78
+ toolCalls++;
79
+ const r = await dispatchTool(c.name, c.arguments, opts.room, opts.pluginTools ?? []);
80
+ signal?.throwIfAborted();
81
+ if (!r.known) {
82
+ unknownTools++;
83
+ if (unknownTools > limits.maxUnknownTools)
84
+ throw new Error("Agent \u591A\u6B21\u8BF7\u6C42\u672A\u63D0\u4F9B\u7684\u5DE5\u5177\uFF0C\u5DF2\u4E2D\u6B62");
85
+ } else {
86
+ progress(`\u8C03\u7528 ${c.name}`);
87
+ }
88
+ history.push({ type: "function_call_output", call_id: c.call_id, output: r.output });
89
+ }
90
+ continue;
91
+ }
92
+ answer += text;
93
+ if (res.status === "completed") {
94
+ const reply = answer.trim();
95
+ if (reply === "")
96
+ throw new Error("Agent \u6CA1\u6709\u8FD4\u56DE\u56DE\u590D");
97
+ return { reply };
98
+ }
99
+ if (continuations >= limits.maxContinuations) {
100
+ throw new Error(`Agent \u8F93\u51FA\u4E0D\u5B8C\u6574\uFF0C\u7EED\u5199 ${limits.maxContinuations} \u6B21\u540E\u4ECD\u672A\u5B8C\u6210\uFF08${res.incompleteReason ?? "incomplete"}\uFF09`);
101
+ }
102
+ continuations++;
103
+ history.push(userMessage(CONTINUE_TEXT));
104
+ }
105
+ }
106
+ export {
107
+ CONTINUE_TEXT,
108
+ DEFAULT_LIMITS,
109
+ runToolLoop
110
+ };
@@ -0,0 +1,63 @@
1
+ import { type Refresher, type StoredAuth } from "./authStore.js";
2
+ export declare const SIWC: {
3
+ /** [SIGN-IN] first-time registration entrypoint; never saved or used for token exchange. */
4
+ readonly dynamicClientId: "dynamic_agent_client";
5
+ /** [SIGN-IN] app's actual name, used consistently, only on initial registration. */
6
+ readonly agentNameHint: "Nema Room";
7
+ /** [SIGN-IN] */
8
+ readonly authorizeUrl: "https://auth.openai.com/api/accounts/authorize";
9
+ /** [SIGN-IN] code exchange; [SESSIONS] refresh. */
10
+ readonly tokenUrl: "https://auth.openai.com/api/accounts/oauth/token";
11
+ /** [TOKENS] access-token `iss` in production. */
12
+ readonly issuer: "https://auth.openai.com";
13
+ /** [SIGN-IN] `resource` on authorize, exchange and refresh. */
14
+ readonly resource: "https://api.openai.com/v1";
15
+ /** [SIGN-IN] identity scopes (needed for the id_token / account identity). */
16
+ readonly identityScopes: "openid profile email";
17
+ /** [SIGN-IN] ChatGPT plan usage scopes. */
18
+ readonly planScopes: "offline_access resource.invoke chatgpt.tokens.use.direct";
19
+ /** [SIGN-IN] the granted scope that authorizes plan usage. */
20
+ readonly planUsageScope: "chatgpt.tokens.use.direct";
21
+ /** [SIGN-IN] loopback redirect `http://127.0.0.1:<port>/auth/callback`; only the port may vary. */
22
+ readonly callbackHost: "127.0.0.1";
23
+ readonly callbackPath: "/auth/callback";
24
+ /** [SIGN-IN] example port. */
25
+ readonly defaultPort: 1455;
26
+ /** [MODELS] */
27
+ readonly apiBase: "https://api.openai.com/v1";
28
+ /** Project rule (not from the docs). */
29
+ readonly originator: "nema-room";
30
+ };
31
+ /** [ERRORS] "Refresh errors": codes meaning the refresh token is unusable → re-login. */
32
+ export declare const UNUSABLE_REFRESH_CODES: readonly ["invalid_grant", "invalid_client", "invalid_refresh_token", "token_expired", "refresh_token_expired", "refresh_token_invalidated", "refresh_token_reused"];
33
+ export declare function cliVersion(): string;
34
+ export declare const nemaUserAgent: () => string;
35
+ export declare const nemaHeaders: () => Record<string, string>;
36
+ /** This host's stable `ext_agent_host_id` ([SIGN-IN]: choose and persist it before the first sign-in). */
37
+ export declare function hostId(nemaHome: string): string;
38
+ export type SiwcEndpoints = {
39
+ authorizeUrl?: string;
40
+ tokenUrl?: string;
41
+ };
42
+ export type SiwcLoginOptions = {
43
+ nemaHome: string;
44
+ /** Always called with the authorize URL; the caller also prints it. */
45
+ openBrowser: (url: string) => void;
46
+ /** Default 1455; when omitted and 1455 is busy an OS-assigned port is used ([SIGN-IN]: only the port may vary). */
47
+ port?: number;
48
+ /** Default 300000. */
49
+ timeoutMs?: number;
50
+ /** Test-only override of the auth.openai.com endpoints. */
51
+ endpoints?: SiwcEndpoints;
52
+ /** Cancels the callback listener and token exchange owned by the caller. */
53
+ signal?: AbortSignal;
54
+ /** Desktop owner requires its production opener to receive only the fixed provider endpoint. */
55
+ requireTrustedAuthorizeUrl?: boolean;
56
+ /** Hides provider and callback error text from the OAuth browser page. */
57
+ redactCallbackErrors?: boolean;
58
+ };
59
+ /** Browser PKCE login ([SIGN-IN]); saves `<nemaHome>/auth.json` (mode "siwc") under auth.lock. */
60
+ export declare function siwcLogin(opts: SiwcLoginOptions): Promise<StoredAuth>;
61
+ /** Refresh per [SESSIONS] "Refreshing tokens". `tokenUrl` is overridable for tests. */
62
+ export declare function makeSiwcRefresher(tokenUrl?: string): Refresher;
63
+ export declare const siwcRefresher: Refresher;