@nowcrew/daemon 0.6.77-beta.1 → 0.6.77-beta.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.
- package/dist/attachment-limits.js +4 -0
- package/dist/attachments.js +33 -27
- package/dist/daemon-updater.js +50 -24
- package/dist/execution-protocol.js +5 -2
- package/dist/machine-info.js +2 -0
- package/dist/runtime-capabilities.js +3 -1
- package/package.json +1 -1
package/dist/attachments.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { mkdir,
|
|
1
|
+
import { mkdir, open, rm } from "node:fs/promises";
|
|
2
2
|
import { basename, extname, resolve, sep } from "node:path";
|
|
3
|
-
|
|
4
|
-
export const ATTACHMENT_MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
3
|
+
import { ATTACHMENT_DOWNLOAD_TIMEOUT_MS, ATTACHMENT_MAX_FILE_BYTES, ATTACHMENT_MAX_TOTAL_BYTES, } from "./attachment-limits.js";
|
|
5
4
|
const throwIfAborted = (signal) => {
|
|
6
5
|
if (signal?.aborted)
|
|
7
6
|
throw signal.reason ?? new Error("Attachment materialization aborted");
|
|
@@ -46,7 +45,7 @@ function checkedRedirect(value, base) {
|
|
|
46
45
|
}
|
|
47
46
|
return url;
|
|
48
47
|
}
|
|
49
|
-
async function
|
|
48
|
+
async function writeBounded(response, path, maxBytes, signal) {
|
|
50
49
|
const declared = Number(response.headers.get("content-length") ?? "");
|
|
51
50
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
52
51
|
throw new Error(`Attachment exceeds the ${maxBytes} byte limit`);
|
|
@@ -54,21 +53,33 @@ async function readBounded(response, maxBytes) {
|
|
|
54
53
|
if (!response.body)
|
|
55
54
|
throw new Error("Attachment response body is empty");
|
|
56
55
|
const reader = response.body.getReader();
|
|
57
|
-
const chunks = [];
|
|
58
56
|
let size = 0;
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
try {
|
|
58
|
+
const file = await open(path, "wx", 0o600);
|
|
59
|
+
try {
|
|
60
|
+
while (true) {
|
|
61
|
+
throwIfAborted(signal);
|
|
62
|
+
const part = await reader.read();
|
|
63
|
+
if (part.done)
|
|
64
|
+
break;
|
|
65
|
+
const chunk = Buffer.from(part.value);
|
|
66
|
+
size += chunk.length;
|
|
67
|
+
if (size > maxBytes) {
|
|
68
|
+
throw new Error(`Attachment exceeds the ${maxBytes} byte limit`);
|
|
69
|
+
}
|
|
70
|
+
await file.writeFile(chunk);
|
|
71
|
+
}
|
|
72
|
+
throwIfAborted(signal);
|
|
73
|
+
return size;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await file.close();
|
|
68
77
|
}
|
|
69
|
-
chunks.push(chunk);
|
|
70
78
|
}
|
|
71
|
-
|
|
79
|
+
catch (error) {
|
|
80
|
+
await reader.cancel().catch(() => { });
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
72
83
|
}
|
|
73
84
|
async function downloadAttachment(input) {
|
|
74
85
|
const initial = new URL(`/agent/attachments/${encodeURIComponent(input.attachment.id)}/download`, input.serverUrl);
|
|
@@ -104,12 +115,11 @@ async function downloadAttachment(input) {
|
|
|
104
115
|
}
|
|
105
116
|
if (!response.ok)
|
|
106
117
|
throw new Error(`Attachment download failed with status ${response.status}`);
|
|
107
|
-
const
|
|
118
|
+
const size = await writeBounded(response, input.path, input.maxFileBytes, input.signal);
|
|
108
119
|
throwIfAborted(input.signal);
|
|
109
|
-
if (
|
|
110
|
-
throw new Error(`Attachment size mismatch: expected ${input.attachment.sizeBytes}, received ${
|
|
120
|
+
if (size !== input.attachment.sizeBytes) {
|
|
121
|
+
throw new Error(`Attachment size mismatch: expected ${input.attachment.sizeBytes}, received ${size}`);
|
|
111
122
|
}
|
|
112
|
-
return data;
|
|
113
123
|
}
|
|
114
124
|
catch (error) {
|
|
115
125
|
if (input.signal?.aborted) {
|
|
@@ -161,22 +171,18 @@ export async function materializeAttachments(input) {
|
|
|
161
171
|
}
|
|
162
172
|
used.add(filename.toLowerCase());
|
|
163
173
|
const path = containedPath(directory, filename);
|
|
164
|
-
|
|
174
|
+
await downloadAttachment({
|
|
165
175
|
serverUrl: input.serverUrl,
|
|
166
176
|
token: input.token,
|
|
167
177
|
attachment,
|
|
168
178
|
fetchImpl: input.fetchImpl ?? fetch,
|
|
169
179
|
maxFileBytes,
|
|
170
180
|
maxRedirects: input.maxRedirects ?? 3,
|
|
171
|
-
timeoutMs: input.timeoutMs ??
|
|
181
|
+
timeoutMs: input.timeoutMs ?? ATTACHMENT_DOWNLOAD_TIMEOUT_MS,
|
|
182
|
+
path,
|
|
172
183
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
173
184
|
});
|
|
174
185
|
throwIfAborted(input.signal);
|
|
175
|
-
await writeFile(path, data, {
|
|
176
|
-
flag: "wx",
|
|
177
|
-
mode: 0o600,
|
|
178
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
179
|
-
});
|
|
180
186
|
materialized.push({ ...attachment, filename, path });
|
|
181
187
|
}
|
|
182
188
|
return { directory, attachments: materialized };
|
package/dist/daemon-updater.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { posix, resolve, win32 } from "node:path";
|
|
3
3
|
import { systemCommandRunner } from "./computer-service.js";
|
|
4
4
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
5
5
|
import { daemonReleaseChannel, isDaemonReleaseVersion } from "./daemon-release-version.js";
|
|
6
6
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
7
|
-
|
|
7
|
+
const PUBLIC_NPM_REGISTRY = normalizeDaemonNpmRegistry();
|
|
8
|
+
function configuredRegistries(betaUpdate) {
|
|
8
9
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
normalizeDaemonNpmRegistry(process.env.CREW_NPM_REGISTRY, { allowLoopbackHttp })
|
|
12
|
-
];
|
|
10
|
+
const configured = process.env.CREW_NPM_REGISTRY === undefined
|
|
11
|
+
? PUBLIC_NPM_REGISTRY
|
|
12
|
+
: normalizeDaemonNpmRegistry(process.env.CREW_NPM_REGISTRY, { allowLoopbackHttp: betaUpdate });
|
|
13
|
+
return betaUpdate ? [...new Set([configured, PUBLIC_NPM_REGISTRY])] : [configured];
|
|
13
14
|
}
|
|
14
15
|
catch {
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
function npmCommand(platform, nodePath) {
|
|
20
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
21
|
+
return paths.join(paths.dirname(nodePath), platform === "win32" ? "npm.cmd" : "npm");
|
|
22
|
+
}
|
|
23
|
+
function registryArgs(registry) {
|
|
24
|
+
return ["--registry", registry];
|
|
25
|
+
}
|
|
26
|
+
function isRegistryAuthFailure(stderr) {
|
|
27
|
+
return /(?:\bE401\b|\b401\b.*\bUnauthorized\b)/iu.test(stderr);
|
|
28
|
+
}
|
|
18
29
|
export async function prepareDaemonRestart(input) {
|
|
19
30
|
const local = input.localSlots.tryAcquireExclusive();
|
|
20
31
|
if (local === null)
|
|
@@ -40,10 +51,10 @@ async function readPackageVersion(packageRoot) {
|
|
|
40
51
|
const body = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
41
52
|
return typeof body.version === "string" ? body.version : "";
|
|
42
53
|
}
|
|
43
|
-
async function restoreInstalledVersion(input, runner, version, registryArgs) {
|
|
54
|
+
async function restoreInstalledVersion(input, runner, command, version, registryArgs) {
|
|
44
55
|
if (!isDaemonReleaseVersion(version))
|
|
45
56
|
return false;
|
|
46
|
-
const result = await runner(
|
|
57
|
+
const result = await runner(command, [
|
|
47
58
|
"install",
|
|
48
59
|
"--global",
|
|
49
60
|
"--prefix",
|
|
@@ -98,6 +109,8 @@ export async function installExactDaemonUpdate(input) {
|
|
|
98
109
|
return { ok: false, errorCode: "ineligible" };
|
|
99
110
|
}
|
|
100
111
|
const runner = input.runner ?? systemCommandRunner;
|
|
112
|
+
const platform = input.platform ?? process.platform;
|
|
113
|
+
const command = npmCommand(platform, input.nodePath ?? process.execPath);
|
|
101
114
|
let previousVersion = "";
|
|
102
115
|
try {
|
|
103
116
|
previousVersion = await (input.readInstalledVersion ?? readPackageVersion)(input.packageRoot);
|
|
@@ -111,9 +124,9 @@ export async function installExactDaemonUpdate(input) {
|
|
|
111
124
|
await release();
|
|
112
125
|
return { ok: false, errorCode: "install_failed" };
|
|
113
126
|
}
|
|
114
|
-
const
|
|
127
|
+
const registries = configuredRegistries(daemonReleaseChannel(previousVersion) === "beta"
|
|
115
128
|
|| daemonReleaseChannel(input.targetVersion) === "beta");
|
|
116
|
-
if (
|
|
129
|
+
if (registries === null) {
|
|
117
130
|
await release();
|
|
118
131
|
return { ok: false, errorCode: "install_failed" };
|
|
119
132
|
}
|
|
@@ -124,20 +137,33 @@ export async function installExactDaemonUpdate(input) {
|
|
|
124
137
|
await release();
|
|
125
138
|
return { ok: false, errorCode: "install_failed" };
|
|
126
139
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
let selectedRegistryArgs = [];
|
|
141
|
+
let installed = false;
|
|
142
|
+
let authFailed = false;
|
|
143
|
+
for (const registry of registries) {
|
|
144
|
+
const args = registryArgs(registry);
|
|
145
|
+
const result = await runner(command, [
|
|
146
|
+
"install",
|
|
147
|
+
"--global",
|
|
148
|
+
"--prefix",
|
|
149
|
+
input.npmPrefix,
|
|
150
|
+
"--ignore-scripts",
|
|
151
|
+
"--no-audit",
|
|
152
|
+
"--no-fund",
|
|
153
|
+
...args,
|
|
154
|
+
`@nowcrew/daemon@${input.targetVersion}`,
|
|
155
|
+
]).catch(() => null);
|
|
156
|
+
if (result?.exitCode === 0) {
|
|
157
|
+
selectedRegistryArgs = args;
|
|
158
|
+
installed = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (result !== null && isRegistryAuthFailure(result.stderr))
|
|
162
|
+
authFailed = true;
|
|
163
|
+
}
|
|
164
|
+
if (!installed) {
|
|
139
165
|
await release();
|
|
140
|
-
return { ok: false, errorCode: "install_failed" };
|
|
166
|
+
return { ok: false, errorCode: authFailed ? "registry_auth_failed" : "install_failed" };
|
|
141
167
|
}
|
|
142
168
|
let installedVersion = "";
|
|
143
169
|
try {
|
|
@@ -147,7 +173,7 @@ export async function installExactDaemonUpdate(input) {
|
|
|
147
173
|
// Version verification is authoritative; unreadable package metadata is a mismatch.
|
|
148
174
|
}
|
|
149
175
|
if (installedVersion !== input.targetVersion) {
|
|
150
|
-
const restored = await restoreInstalledVersion(input, runner, previousVersion,
|
|
176
|
+
const restored = await restoreInstalledVersion(input, runner, command, previousVersion, selectedRegistryArgs);
|
|
151
177
|
if (!restored) {
|
|
152
178
|
console.error(`[daemon-update] version verification failed; restore of ${previousVersion} was unsuccessful`);
|
|
153
179
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ATTACHMENT_MAX_FILE_BYTES, ATTACHMENT_MAX_TOTAL_BYTES, } from "./attachment-limits.js";
|
|
2
3
|
import { AbilityRepositoryUrlSchema, SafeAbilityPathSchema, WorkspaceAbilityTargetSchema, } from "./agent-ability/types.js";
|
|
3
4
|
import { compareProjectSkillRefs, MAX_AGENT_PROJECT_SKILL_BINDINGS, MAX_PROJECT_ID_LENGTH, MAX_PROJECT_SKILL_NAME_LENGTH, } from "./project-skills/types.js";
|
|
4
5
|
const ExecutionIdSchema = z.string().uuid();
|
|
@@ -160,8 +161,10 @@ export const ExecutionAttachmentSchema = z.object({
|
|
|
160
161
|
id: z.string().min(1).max(200),
|
|
161
162
|
filename: z.string().min(1).max(255),
|
|
162
163
|
mime: z.string().min(1).max(200),
|
|
163
|
-
sizeBytes: z.number().int().nonnegative().max(
|
|
164
|
+
sizeBytes: z.number().int().nonnegative().max(ATTACHMENT_MAX_FILE_BYTES),
|
|
164
165
|
}).strict();
|
|
166
|
+
const ExecutionAttachmentsSchema = z.array(ExecutionAttachmentSchema).max(20).refine((attachments) => attachments.reduce((total, attachment) => total + attachment.sizeBytes, 0)
|
|
167
|
+
<= ATTACHMENT_MAX_TOTAL_BYTES, "attachment bytes exceed the execution limit");
|
|
165
168
|
const ExecutionAgentSchema = z.object({
|
|
166
169
|
id: z.string().min(1),
|
|
167
170
|
handle: AgentHandleSchema,
|
|
@@ -214,7 +217,7 @@ export const ExecutionStartSchema = z.object({
|
|
|
214
217
|
scheduledRunId: ExecutionIdSchema.optional(),
|
|
215
218
|
externalResponseSessionId: ExecutionIdSchema.optional(),
|
|
216
219
|
answerStream: z.boolean().optional(),
|
|
217
|
-
attachments:
|
|
220
|
+
attachments: ExecutionAttachmentsSchema.optional(),
|
|
218
221
|
}).strict(),
|
|
219
222
|
reporting: z.object({
|
|
220
223
|
captureFinal: z.boolean(),
|
package/dist/machine-info.js
CHANGED
|
@@ -25,6 +25,7 @@ export { executableRuntimes } from "./runtime-capabilities.js";
|
|
|
25
25
|
const execFileP = promisify(execFile);
|
|
26
26
|
export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
|
|
27
27
|
export const TRAINING_PROJECT_DEPENDENCIES_CAPABILITY = "training_project_dependencies_v1";
|
|
28
|
+
export const EXECUTION_LARGE_ATTACHMENTS_CAPABILITY = "execution_attachments_300m_v1";
|
|
28
29
|
export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
|
|
29
30
|
resolverReady: true,
|
|
30
31
|
nativeRuntimeAdaptersReady: true,
|
|
@@ -36,6 +37,7 @@ export const DAEMON_CAPABILITIES = Object.freeze([
|
|
|
36
37
|
"execution_telemetry_ack_v1",
|
|
37
38
|
"execution_external_output_v1",
|
|
38
39
|
"execution_attachments_v1",
|
|
40
|
+
EXECUTION_LARGE_ATTACHMENTS_CAPABILITY,
|
|
39
41
|
"execution_answer_stream_v1",
|
|
40
42
|
"execution_machine_queue_v1",
|
|
41
43
|
"execution_agent_memory_policy_v1",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ATTACHMENT_NATIVE_IMAGE_MAX_BYTES } from "./attachment-limits.js";
|
|
1
2
|
export const LOCAL_EXECUTION_RUNTIMES = [
|
|
2
3
|
"claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness",
|
|
3
4
|
];
|
|
@@ -46,7 +47,8 @@ export function routeRuntimeAttachments(runtime, attachments) {
|
|
|
46
47
|
const lines = attachments.map((attachment) => `- ${JSON.stringify(attachment.path)} (${attachment.mime}, ${attachment.sizeBytes} bytes)`);
|
|
47
48
|
return {
|
|
48
49
|
nativeImagePaths: runtime === "codex"
|
|
49
|
-
? attachments.filter((attachment) => attachment.mime.startsWith("image/")
|
|
50
|
+
? attachments.filter((attachment) => attachment.mime.startsWith("image/")
|
|
51
|
+
&& attachment.sizeBytes <= ATTACHMENT_NATIVE_IMAGE_MAX_BYTES)
|
|
50
52
|
.map((attachment) => attachment.path)
|
|
51
53
|
: [],
|
|
52
54
|
promptSuffix: [
|