deepline 0.1.143 → 0.1.145
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/README.md +2 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +36 -4
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +5 -2
- package/dist/bundling-sources/sdk/src/client.ts +5 -0
- package/dist/bundling-sources/sdk/src/config.ts +310 -13
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/temporal/constants.ts +4 -3
- package/dist/cli/index.js +865 -82
- package/dist/cli/index.mjs +889 -98
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +151 -14
- package/dist/index.mjs +160 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,8 +65,8 @@ Do not route the SDK CLI through `.env`, `.env.local`, or `.env.worktree`.
|
|
|
65
65
|
Config resolution order:
|
|
66
66
|
|
|
67
67
|
```text
|
|
68
|
-
host: explicit SDK option -> DEEPLINE_HOST_URL -> nearest .env.deepline -> saved production auth -> https://code.deepline.com
|
|
69
|
-
key: explicit SDK option -> DEEPLINE_API_KEY -> matching nearest .env.deepline -> saved auth for the resolved host
|
|
68
|
+
host: explicit SDK option -> DEEPLINE_HOST_URL -> nearest .env.deepline -> Cowork workspace .env.deepline -> saved production auth -> https://code.deepline.com
|
|
69
|
+
key: explicit SDK option -> DEEPLINE_API_KEY -> matching nearest .env.deepline -> matching Cowork workspace .env.deepline -> saved auth for the resolved host
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## CLI commands
|
|
@@ -640,6 +640,8 @@ type DynamicWorkflowMetadata = {
|
|
|
640
640
|
const SUBMIT_INITIAL_STATE_MAX_WAIT_MS = 0;
|
|
641
641
|
const SUBMIT_INITIAL_STATE_POLL_MS = 50;
|
|
642
642
|
const WORKFLOW_RETRY_STATE_TTL_MS = 60 * 60 * 1000;
|
|
643
|
+
const COORDINATOR_WORKER_MEMORY_LIMIT_MESSAGE =
|
|
644
|
+
'Worker memory limit hit before the run could finish. For CSV enrich, use --output or --in-place for automatic batches; otherwise rerun smaller --rows ranges.';
|
|
643
645
|
|
|
644
646
|
function buildDynamicWorkflowMetadata(
|
|
645
647
|
params: PlayWorkflowParams,
|
|
@@ -1052,6 +1054,27 @@ function readWorkflowPayload(event: unknown): Record<string, unknown> | null {
|
|
|
1052
1054
|
return isRecord(payload.params) ? payload.params : payload;
|
|
1053
1055
|
}
|
|
1054
1056
|
|
|
1057
|
+
function isCoordinatorWorkerMemoryLimitError(error: unknown): boolean {
|
|
1058
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1059
|
+
return message.toLowerCase().includes('worker exceeded memory limit');
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function normalizeCoordinatorWorkflowRuntimeFailure(
|
|
1063
|
+
error: unknown,
|
|
1064
|
+
): ReturnType<typeof normalizePlayRunFailure> {
|
|
1065
|
+
const failure = normalizePlayRunFailure(error);
|
|
1066
|
+
if (!isCoordinatorWorkerMemoryLimitError(error)) {
|
|
1067
|
+
return failure;
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
...failure,
|
|
1071
|
+
code: 'WORKER_MEMORY_LIMIT_EXCEEDED',
|
|
1072
|
+
message: COORDINATOR_WORKER_MEMORY_LIMIT_MESSAGE,
|
|
1073
|
+
retryable: false,
|
|
1074
|
+
cause: failure.message,
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1055
1078
|
async function markWorkflowRuntimeFailure(input: {
|
|
1056
1079
|
env: CoordinatorEnv;
|
|
1057
1080
|
event: unknown;
|
|
@@ -1074,6 +1097,16 @@ async function markWorkflowRuntimeFailure(input: {
|
|
|
1074
1097
|
input.error instanceof Error && typeof input.error.stack === 'string'
|
|
1075
1098
|
? input.error.stack.split('\n').slice(0, 12).join('\n')
|
|
1076
1099
|
: null;
|
|
1100
|
+
const failure = normalizeCoordinatorWorkflowRuntimeFailure(input.error);
|
|
1101
|
+
const shouldPreserveRawError =
|
|
1102
|
+
failure.code === 'RUN_FAILED' ||
|
|
1103
|
+
(failure.cause !== undefined && failure.message === failure.cause);
|
|
1104
|
+
const normalizedError =
|
|
1105
|
+
shouldPreserveRawError
|
|
1106
|
+
? `DynamicWorkflow runner failed: ${errorName}: ${errorMessage}${
|
|
1107
|
+
errorStack ? `\n${errorStack}` : ''
|
|
1108
|
+
}`
|
|
1109
|
+
: `DynamicWorkflow runner failed: ${failure.message}`;
|
|
1077
1110
|
const headers = new Headers({
|
|
1078
1111
|
authorization: `Bearer ${executorToken}`,
|
|
1079
1112
|
'content-type': 'application/json',
|
|
@@ -1089,9 +1122,7 @@ async function markWorkflowRuntimeFailure(input: {
|
|
|
1089
1122
|
runId,
|
|
1090
1123
|
source: 'coordinator',
|
|
1091
1124
|
occurredAt: Date.now(),
|
|
1092
|
-
error:
|
|
1093
|
-
errorStack ? `\n${errorStack}` : ''
|
|
1094
|
-
}`,
|
|
1125
|
+
error: normalizedError,
|
|
1095
1126
|
} satisfies PlayRunLedgerEvent,
|
|
1096
1127
|
],
|
|
1097
1128
|
});
|
|
@@ -3062,7 +3093,8 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
|
|
|
3062
3093
|
});
|
|
3063
3094
|
return result;
|
|
3064
3095
|
} catch (innerError) {
|
|
3065
|
-
const failure =
|
|
3096
|
+
const failure =
|
|
3097
|
+
normalizeCoordinatorWorkflowRuntimeFailure(innerError);
|
|
3066
3098
|
console.error(
|
|
3067
3099
|
'[coordinator] DynamicWorkflow runner.run threw',
|
|
3068
3100
|
{
|
|
@@ -43,7 +43,10 @@ import {
|
|
|
43
43
|
type ChunkExecutionResult,
|
|
44
44
|
} from '../../../shared_libs/play-runtime/batch-runtime';
|
|
45
45
|
import { getPlayRuntimeBatchStrategy } from '../../../shared_libs/play-runtime/play-runtime-batching-registry';
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
|
|
48
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
|
|
49
|
+
} from '../../../shared_libs/temporal/constants';
|
|
47
50
|
import {
|
|
48
51
|
createPlayExecutionGovernor,
|
|
49
52
|
type GovernanceSnapshot,
|
|
@@ -6341,7 +6344,7 @@ async function executeRunRequest(
|
|
|
6341
6344
|
runtimeLimitExceeded = true;
|
|
6342
6345
|
if (!abortSignal.aborted) {
|
|
6343
6346
|
abortController.abort(
|
|
6344
|
-
`
|
|
6347
|
+
`Based on this plan, max runtime is ${STANDARD_PLAY_RUNTIME_LIMIT_LABEL}. Use smaller batches; ask for runtime.`,
|
|
6345
6348
|
);
|
|
6346
6349
|
}
|
|
6347
6350
|
}, STANDARD_PLAY_RUNTIME_LIMIT_SECONDS * 1000);
|
|
@@ -320,6 +320,7 @@ export type RunsNamespace = {
|
|
|
320
320
|
runId?: string;
|
|
321
321
|
limit?: number;
|
|
322
322
|
offset?: number;
|
|
323
|
+
rowMode?: 'output' | 'all';
|
|
323
324
|
}) => Promise<PlaySheetRowsResult>;
|
|
324
325
|
/** Stop a running/waiting run. */
|
|
325
326
|
stop: (
|
|
@@ -2434,6 +2435,7 @@ export class DeeplineClient {
|
|
|
2434
2435
|
runId?: string;
|
|
2435
2436
|
limit?: number;
|
|
2436
2437
|
offset?: number;
|
|
2438
|
+
rowMode?: 'output' | 'all';
|
|
2437
2439
|
}): Promise<PlaySheetRowsResult> {
|
|
2438
2440
|
const params = new URLSearchParams({
|
|
2439
2441
|
tableNamespace: input.tableNamespace,
|
|
@@ -2443,6 +2445,9 @@ export class DeeplineClient {
|
|
|
2443
2445
|
if (input.runId?.trim()) {
|
|
2444
2446
|
params.set('runId', input.runId.trim());
|
|
2445
2447
|
}
|
|
2448
|
+
if (input.rowMode === 'all') {
|
|
2449
|
+
params.set('rowMode', 'all');
|
|
2450
|
+
}
|
|
2446
2451
|
return await this.http.get<PlaySheetRowsResult>(
|
|
2447
2452
|
`/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`,
|
|
2448
2453
|
);
|
|
@@ -15,21 +15,31 @@
|
|
|
15
15
|
* 1. `options.baseUrl`
|
|
16
16
|
* 2. `DEEPLINE_HOST_URL`
|
|
17
17
|
* 3. nearest project `.env.deepline`
|
|
18
|
-
* 4.
|
|
19
|
-
* 5. production
|
|
18
|
+
* 4. Cowork mounted workspace `.env.deepline`
|
|
19
|
+
* 5. production host auth file
|
|
20
|
+
* 6. production fallback: `https://code.deepline.com`
|
|
20
21
|
*
|
|
21
22
|
* API key:
|
|
22
23
|
* 1. `options.apiKey`
|
|
23
24
|
* 2. `DEEPLINE_API_KEY`
|
|
24
25
|
* 3. nearest project `.env.deepline`
|
|
25
|
-
* 4.
|
|
26
|
+
* 4. Cowork mounted workspace `.env.deepline`
|
|
27
|
+
* 5. host auth file for the resolved base URL
|
|
26
28
|
*
|
|
27
29
|
* App/runtime env files such as `.env`, `.env.local`, and `.env.worktree` do
|
|
28
30
|
* not route the SDK CLI. Put CLI routing in `.env.deepline`.
|
|
29
31
|
*
|
|
30
32
|
* @module
|
|
31
33
|
*/
|
|
32
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
existsSync,
|
|
36
|
+
mkdirSync,
|
|
37
|
+
readdirSync,
|
|
38
|
+
realpathSync,
|
|
39
|
+
readFileSync,
|
|
40
|
+
statSync,
|
|
41
|
+
writeFileSync,
|
|
42
|
+
} from 'node:fs';
|
|
33
43
|
import { homedir } from 'node:os';
|
|
34
44
|
import { dirname, join, resolve } from 'node:path';
|
|
35
45
|
import type { DeeplineClientOptions, ResolvedConfig } from './types.js';
|
|
@@ -48,8 +58,41 @@ const DEFAULT_TIMEOUT = 60_000;
|
|
|
48
58
|
const DEFAULT_MAX_RETRIES = 3;
|
|
49
59
|
|
|
50
60
|
const PROJECT_DEEPLINE_ENV_FILE = '.env.deepline';
|
|
61
|
+
const COWORK_IGNORED_WORKSPACE_DIRS = new Set([
|
|
62
|
+
'.auto-memory',
|
|
63
|
+
'.claude',
|
|
64
|
+
'.remote-plugins',
|
|
65
|
+
'outputs',
|
|
66
|
+
'plugins',
|
|
67
|
+
'uploads',
|
|
68
|
+
]);
|
|
69
|
+
const COWORK_PROJECT_MARKERS = [
|
|
70
|
+
'.deepline',
|
|
71
|
+
'.env.deepline',
|
|
72
|
+
'.git',
|
|
73
|
+
'AGENTS.md',
|
|
74
|
+
'package.json',
|
|
75
|
+
'pyproject.toml',
|
|
76
|
+
];
|
|
51
77
|
|
|
52
78
|
type EnvValues = Record<string, string>;
|
|
79
|
+
type ProjectEnvCandidate = {
|
|
80
|
+
filePath: string;
|
|
81
|
+
env: EnvValues;
|
|
82
|
+
source: 'nearest' | 'cowork';
|
|
83
|
+
};
|
|
84
|
+
export type ProjectAuthSource = ProjectEnvCandidate;
|
|
85
|
+
export type ProjectPinTarget =
|
|
86
|
+
| {
|
|
87
|
+
ok: true;
|
|
88
|
+
dir: string;
|
|
89
|
+
source: 'nearest' | 'cowork' | 'cwd';
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
ok: false;
|
|
93
|
+
reason: 'ambiguous_cowork_project';
|
|
94
|
+
candidates: string[];
|
|
95
|
+
};
|
|
53
96
|
|
|
54
97
|
/**
|
|
55
98
|
* Convert a base URL to a filesystem-safe slug for per-host config storage.
|
|
@@ -119,9 +162,161 @@ function findNearestEnvFile(
|
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
|
|
165
|
+
function isDirectory(path: string): boolean {
|
|
166
|
+
try {
|
|
167
|
+
return statSync(path).isDirectory();
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function canonicalPath(path: string): string {
|
|
174
|
+
try {
|
|
175
|
+
return realpathSync(path);
|
|
176
|
+
} catch {
|
|
177
|
+
return resolve(path);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isTruthy(value: string | undefined): boolean {
|
|
182
|
+
return /^(1|true|yes|on)$/i.test(value?.trim() ?? '');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function sessionRootFromPath(path: string | undefined): string | null {
|
|
186
|
+
const trimmed = path?.trim();
|
|
187
|
+
if (!trimmed) return null;
|
|
188
|
+
const match = /^\/sessions\/[^/]+(?=\/|$)/.exec(trimmed);
|
|
189
|
+
return match?.[0] ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function coworkSessionRoot(): string | null {
|
|
193
|
+
const home = process.env.HOME?.trim();
|
|
194
|
+
const homeSessionRoot = sessionRootFromPath(home);
|
|
195
|
+
if (homeSessionRoot && isDirectory(join(homeSessionRoot, 'mnt'))) {
|
|
196
|
+
return homeSessionRoot;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const cwdSessionRoot = sessionRootFromPath(process.cwd());
|
|
200
|
+
if (cwdSessionRoot && isDirectory(join(cwdSessionRoot, 'mnt'))) {
|
|
201
|
+
return cwdSessionRoot;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isTruthy(process.env.CLAUDE_CODE_REMOTE) && home) {
|
|
205
|
+
const mountedRoot = join(home, 'mnt');
|
|
206
|
+
if (isDirectory(mountedRoot)) return resolve(home);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isCoworkLikeSandbox(): boolean {
|
|
213
|
+
const home = process.env.HOME?.trim();
|
|
214
|
+
return (
|
|
215
|
+
isTruthy(process.env.CLAUDE_CODE_REMOTE) ||
|
|
216
|
+
sessionRootFromPath(home) !== null ||
|
|
217
|
+
sessionRootFromPath(process.cwd()) !== null
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function coworkProjectScore(path: string): number {
|
|
222
|
+
let score = 0;
|
|
223
|
+
for (const marker of COWORK_PROJECT_MARKERS) {
|
|
224
|
+
if (existsSync(join(path, marker))) score += 1;
|
|
225
|
+
}
|
|
226
|
+
return score;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function listCoworkWorkspaceDirCandidates(): string[] {
|
|
230
|
+
if (!isCoworkLikeSandbox()) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const explicitProjectDir = process.env.CLAUDE_PROJECT_DIR?.trim();
|
|
235
|
+
if (explicitProjectDir && isDirectory(explicitProjectDir)) {
|
|
236
|
+
return [resolve(explicitProjectDir)];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sessionRoot = coworkSessionRoot();
|
|
240
|
+
if (!sessionRoot) return [];
|
|
241
|
+
|
|
242
|
+
const mountedRoot = join(sessionRoot, 'mnt');
|
|
243
|
+
if (!isDirectory(mountedRoot)) return [];
|
|
244
|
+
|
|
245
|
+
let names: string[];
|
|
246
|
+
try {
|
|
247
|
+
names = readdirSync(mountedRoot).sort();
|
|
248
|
+
} catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const candidates: string[] = [];
|
|
253
|
+
for (const name of names) {
|
|
254
|
+
if (name.startsWith('.') || COWORK_IGNORED_WORKSPACE_DIRS.has(name)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const candidate = join(mountedRoot, name);
|
|
258
|
+
if (isDirectory(candidate)) candidates.push(candidate);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (candidates.length <= 1) return candidates;
|
|
262
|
+
|
|
263
|
+
const projectLike = candidates.filter(
|
|
264
|
+
(candidate) => coworkProjectScore(candidate) > 0,
|
|
265
|
+
);
|
|
266
|
+
return projectLike.length > 0 ? projectLike : candidates;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isInIgnoredCoworkMount(path: string): boolean {
|
|
270
|
+
const sessionRoot = coworkSessionRoot();
|
|
271
|
+
if (!sessionRoot) return false;
|
|
272
|
+
const mountedRoot = canonicalPath(join(sessionRoot, 'mnt'));
|
|
273
|
+
const resolvedPath = canonicalPath(path);
|
|
274
|
+
const prefix = `${mountedRoot}/`;
|
|
275
|
+
if (!resolvedPath.startsWith(prefix)) return false;
|
|
276
|
+
const relativePath = resolvedPath.slice(prefix.length);
|
|
277
|
+
const mountName = relativePath.split('/')[0];
|
|
278
|
+
return (
|
|
279
|
+
mountName.startsWith('.') || COWORK_IGNORED_WORKSPACE_DIRS.has(mountName)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function detectCoworkWorkspaceDir(): string | null {
|
|
284
|
+
const candidates = listCoworkWorkspaceDirCandidates();
|
|
285
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function loadProjectEnvCandidates(
|
|
289
|
+
startDir: string = process.cwd(),
|
|
290
|
+
): ProjectEnvCandidate[] {
|
|
291
|
+
const filePaths: string[] = [];
|
|
292
|
+
const sources = new Map<string, ProjectEnvCandidate['source']>();
|
|
293
|
+
const nearestFile = findNearestEnvFile(PROJECT_DEEPLINE_ENV_FILE, startDir);
|
|
294
|
+
if (nearestFile && !isInIgnoredCoworkMount(nearestFile)) {
|
|
295
|
+
filePaths.push(nearestFile);
|
|
296
|
+
sources.set(resolve(nearestFile), 'nearest');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const coworkWorkspaceDir = detectCoworkWorkspaceDir();
|
|
300
|
+
if (coworkWorkspaceDir) {
|
|
301
|
+
const coworkFile = join(coworkWorkspaceDir, PROJECT_DEEPLINE_ENV_FILE);
|
|
302
|
+
if (
|
|
303
|
+
existsSync(coworkFile) &&
|
|
304
|
+
!filePaths.some((filePath) => resolve(filePath) === resolve(coworkFile))
|
|
305
|
+
) {
|
|
306
|
+
filePaths.push(coworkFile);
|
|
307
|
+
sources.set(resolve(coworkFile), 'cowork');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return filePaths.map((filePath) => ({
|
|
312
|
+
filePath,
|
|
313
|
+
env: parseEnvFile(filePath),
|
|
314
|
+
source: sources.get(resolve(filePath)) ?? 'nearest',
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
122
318
|
function loadProjectDeeplineEnv(startDir = process.cwd()): EnvValues {
|
|
123
|
-
|
|
124
|
-
return filePath ? parseEnvFile(filePath) : {};
|
|
319
|
+
return loadProjectEnvCandidates(startDir)[0]?.env ?? {};
|
|
125
320
|
}
|
|
126
321
|
|
|
127
322
|
function normalizeBaseUrl(baseUrl: string): string {
|
|
@@ -204,11 +399,13 @@ function loadGlobalCliEnv(): EnvValues {
|
|
|
204
399
|
* Auto-detect the best base URL when none is explicitly provided.
|
|
205
400
|
*/
|
|
206
401
|
function autoDetectBaseUrl(): string {
|
|
207
|
-
const
|
|
402
|
+
const projectEnvs = loadProjectEnvCandidates();
|
|
208
403
|
const globalEnv = loadGlobalCliEnv();
|
|
209
404
|
return (
|
|
210
405
|
normalizeBaseUrl(process.env[HOST_URL_ENV] ?? '') ||
|
|
211
|
-
|
|
406
|
+
firstNonEmpty(
|
|
407
|
+
...projectEnvs.map(({ env }) => normalizeBaseUrl(env[HOST_URL_ENV])),
|
|
408
|
+
) ||
|
|
212
409
|
normalizeBaseUrl(globalEnv[HOST_URL_ENV] ?? '') ||
|
|
213
410
|
PROD_URL
|
|
214
411
|
);
|
|
@@ -219,18 +416,38 @@ export function resolveApiKeyForBaseUrl(
|
|
|
219
416
|
explicitApiKey?: string | null,
|
|
220
417
|
): string {
|
|
221
418
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
222
|
-
const
|
|
419
|
+
const projectEnvs = loadProjectEnvCandidates();
|
|
223
420
|
const cliEnv = loadCliEnv(normalizedBaseUrl || baseUrl);
|
|
224
|
-
const projectBaseUrl = normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? '');
|
|
225
|
-
const projectKeyApplies = projectBaseUrl === normalizedBaseUrl;
|
|
226
421
|
return firstNonEmpty(
|
|
227
422
|
explicitApiKey,
|
|
228
423
|
process.env[API_KEY_ENV],
|
|
229
|
-
|
|
424
|
+
...projectEnvs.map(({ env }) => {
|
|
425
|
+
const projectBaseUrl = normalizeBaseUrl(env[HOST_URL_ENV] ?? '');
|
|
426
|
+
return projectBaseUrl === normalizedBaseUrl ? env[API_KEY_ENV] : '';
|
|
427
|
+
}),
|
|
230
428
|
cliEnv[API_KEY_ENV],
|
|
231
429
|
);
|
|
232
430
|
}
|
|
233
431
|
|
|
432
|
+
function getResolvedProjectAuthSource(
|
|
433
|
+
baseUrl: string,
|
|
434
|
+
apiKey: string,
|
|
435
|
+
startDir: string = process.cwd(),
|
|
436
|
+
): ProjectAuthSource | null {
|
|
437
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
438
|
+
const normalizedApiKey = apiKey.trim();
|
|
439
|
+
if (!normalizedBaseUrl || !normalizedApiKey) return null;
|
|
440
|
+
return (
|
|
441
|
+
loadProjectEnvCandidates(startDir).find(({ env }) => {
|
|
442
|
+
const projectBaseUrl = normalizeBaseUrl(env[HOST_URL_ENV] ?? '');
|
|
443
|
+
return (
|
|
444
|
+
projectBaseUrl === normalizedBaseUrl &&
|
|
445
|
+
(env[API_KEY_ENV] ?? '').trim() === normalizedApiKey
|
|
446
|
+
);
|
|
447
|
+
}) ?? null
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
234
451
|
/**
|
|
235
452
|
* Resolve SDK configuration from the public SDK CLI env contract.
|
|
236
453
|
*/
|
|
@@ -265,6 +482,7 @@ function mergeProjectEnvFile(filePath: string, values: EnvValues): void {
|
|
|
265
482
|
const merged = { ...existing, ...values };
|
|
266
483
|
const dir = dirname(filePath);
|
|
267
484
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
485
|
+
ensureProjectEnvIsIgnored(dir);
|
|
268
486
|
const allowedKeys = new Set([HOST_URL_ENV, API_KEY_ENV]);
|
|
269
487
|
const lines = Object.entries(merged)
|
|
270
488
|
.filter(([key, value]) => allowedKeys.has(key) && value !== '')
|
|
@@ -272,20 +490,99 @@ function mergeProjectEnvFile(filePath: string, values: EnvValues): void {
|
|
|
272
490
|
writeFileSync(filePath, `${lines.join('\n')}\n`, 'utf-8');
|
|
273
491
|
}
|
|
274
492
|
|
|
493
|
+
function ensureProjectEnvIsIgnored(dir: string): void {
|
|
494
|
+
const gitignorePath = join(dir, '.gitignore');
|
|
495
|
+
const entry = PROJECT_DEEPLINE_ENV_FILE;
|
|
496
|
+
const existing = existsSync(gitignorePath)
|
|
497
|
+
? readFileSync(gitignorePath, 'utf-8')
|
|
498
|
+
: '';
|
|
499
|
+
const alreadyIgnored = existing
|
|
500
|
+
.split(/\r?\n/)
|
|
501
|
+
.map((line) => line.trim())
|
|
502
|
+
.some((line) => line === entry || line === `/${entry}`);
|
|
503
|
+
if (alreadyIgnored) return;
|
|
504
|
+
const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
505
|
+
writeFileSync(gitignorePath, `${existing}${prefix}${entry}\n`, 'utf-8');
|
|
506
|
+
}
|
|
507
|
+
|
|
275
508
|
export function saveProjectDeeplineEnvValues(
|
|
276
509
|
values: EnvValues,
|
|
277
510
|
startDir: string = process.cwd(),
|
|
278
511
|
): string[] {
|
|
279
|
-
const
|
|
512
|
+
const target = resolveProjectPinTarget(startDir);
|
|
513
|
+
if (!target.ok) {
|
|
514
|
+
throw new ConfigError(
|
|
515
|
+
`Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
|
|
516
|
+
', ',
|
|
517
|
+
)}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder before running this command.`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
const filePath = join(target.dir, PROJECT_DEEPLINE_ENV_FILE);
|
|
280
521
|
mergeProjectEnvFile(filePath, values);
|
|
281
522
|
return [filePath];
|
|
282
523
|
}
|
|
283
524
|
|
|
525
|
+
export function resolveProjectPinDir(startDir: string = process.cwd()): string {
|
|
526
|
+
const target = resolveProjectPinTarget(startDir);
|
|
527
|
+
if (!target.ok) {
|
|
528
|
+
throw new ConfigError(
|
|
529
|
+
`Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
|
|
530
|
+
', ',
|
|
531
|
+
)}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder before running this command.`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return target.dir;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function resolveProjectPinTarget(
|
|
538
|
+
startDir: string = process.cwd(),
|
|
539
|
+
): ProjectPinTarget {
|
|
540
|
+
const nearestFile = findNearestEnvFile(PROJECT_DEEPLINE_ENV_FILE, startDir);
|
|
541
|
+
if (nearestFile && !isInIgnoredCoworkMount(nearestFile)) {
|
|
542
|
+
return { ok: true, dir: dirname(nearestFile), source: 'nearest' };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const coworkCandidates = listCoworkWorkspaceDirCandidates();
|
|
546
|
+
if (coworkCandidates.length === 1) {
|
|
547
|
+
return { ok: true, dir: coworkCandidates[0], source: 'cowork' };
|
|
548
|
+
}
|
|
549
|
+
if (coworkCandidates.length > 1) {
|
|
550
|
+
const resolvedStartDir = canonicalPath(startDir);
|
|
551
|
+
const cwdCandidate = coworkCandidates.find((candidate) => {
|
|
552
|
+
const resolvedCandidate = canonicalPath(candidate);
|
|
553
|
+
return (
|
|
554
|
+
resolvedStartDir === resolvedCandidate ||
|
|
555
|
+
resolvedStartDir.startsWith(`${resolvedCandidate}/`)
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
if (cwdCandidate) {
|
|
559
|
+
return { ok: true, dir: cwdCandidate, source: 'cowork' };
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
ok: false,
|
|
563
|
+
reason: 'ambiguous_cowork_project',
|
|
564
|
+
candidates: coworkCandidates,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { ok: true, dir: resolve(startDir), source: 'cwd' };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function getActiveProjectAuthSource(
|
|
572
|
+
startDir: string = process.cwd(),
|
|
573
|
+
): ProjectAuthSource | null {
|
|
574
|
+
return loadProjectEnvCandidates(startDir)[0] ?? null;
|
|
575
|
+
}
|
|
576
|
+
|
|
284
577
|
export {
|
|
285
578
|
baseUrlSlug,
|
|
286
579
|
loadCliEnv,
|
|
287
580
|
loadGlobalCliEnv,
|
|
581
|
+
loadProjectDeeplineEnv,
|
|
582
|
+
getResolvedProjectAuthSource,
|
|
583
|
+
listCoworkWorkspaceDirCandidates,
|
|
288
584
|
parseEnvFile,
|
|
585
|
+
detectCoworkWorkspaceDir,
|
|
289
586
|
autoDetectBaseUrl,
|
|
290
587
|
PROD_URL,
|
|
291
588
|
};
|
|
@@ -101,10 +101,10 @@ export const SDK_RELEASE = {
|
|
|
101
101
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
102
102
|
// the SDK enrich generator's one-second stale policy.
|
|
103
103
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
104
|
-
version: '0.1.
|
|
104
|
+
version: '0.1.145',
|
|
105
105
|
apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
|
|
106
106
|
supportPolicy: {
|
|
107
|
-
latest: '0.1.
|
|
107
|
+
latest: '0.1.145',
|
|
108
108
|
minimumSupported: '0.1.53',
|
|
109
109
|
deprecatedBelow: '0.1.53',
|
|
110
110
|
commandMinimumSupported: [
|
|
@@ -18,13 +18,14 @@ export const LOCAL_TEMPORAL_ADDRESS = `127.0.0.1:${LOCAL_TEMPORAL_FRONTEND_PORT}
|
|
|
18
18
|
export const LOCAL_TEMPORAL_UI_URL = `http://127.0.0.1:${LOCAL_TEMPORAL_UI_PORT}`;
|
|
19
19
|
|
|
20
20
|
/** Maximum active user-code runtime for a standard play, in seconds. */
|
|
21
|
-
export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS =
|
|
21
|
+
export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 20 * 60; // 20 minutes
|
|
22
|
+
export const STANDARD_PLAY_RUNTIME_LIMIT_LABEL = '20 minutes';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Activity timeout includes cleanup
|
|
25
|
+
* Activity timeout includes setup, cleanup, and billing headroom after the 20 minute
|
|
25
26
|
* user-code runtime cap. Keep this higher than STANDARD_PLAY_RUNTIME_LIMIT_SECONDS.
|
|
26
27
|
*/
|
|
27
|
-
export const PLAY_ACTIVITY_TIMEOUT_SECONDS =
|
|
28
|
+
export const PLAY_ACTIVITY_TIMEOUT_SECONDS = 30 * 60; // 30 minutes
|
|
28
29
|
|
|
29
30
|
/** Heartbeat cadence for the long-running play execution activity. */
|
|
30
31
|
export const PLAY_EXECUTE_ACTIVITY_HEARTBEAT_INTERVAL_SECONDS = 15;
|