@oar-sh/oar 0.9.15 → 0.9.17
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/.github/extensions/web-relay/polling/polling-loop.mjs +14 -87
- package/CHANGELOG.md +305 -0
- package/README.md +12 -11
- package/package.json +2 -1
- package/server/claude-worker/claude-attachments.mjs +7 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-sdk-adapter.mjs +167 -9
- package/server/claude-worker/claude-session-process.mjs +131 -33
- package/server/claude-worker/claude-session-worker-main.mjs +266 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +282 -13
- package/server/claude-worker/claude-usage-totals.mjs +108 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +55 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +73 -7
- package/server/db-schema.mjs +115 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +433 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/migrations/0024-deepseek-usage-totals.mjs +151 -0
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +179 -0
- package/server/migrations/0026-deepseek-usage-served-models.mjs +222 -0
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +101 -0
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +99 -0
- package/server/migrations/0029-deepseek-balance-rises.mjs +99 -0
- package/server/migrations/0030-deepseek-usage-actuals.mjs +123 -0
- package/server/public/app/api-client.js +53 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +162 -16
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/conversation-provider-indicator.mjs +7 -0
- package/server/public/app/conversation-view.js +2 -0
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +367 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +54 -5
- package/server/public/app/model-selector-options.mjs +11 -0
- package/server/public/app/model-vision-support.mjs +2 -0
- package/server/public/app/no-conversation-state.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +165 -13
- package/server/public/app/provider-readiness.mjs +4 -1
- package/server/public/app/relay-error-ctas.mjs +8 -0
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +11 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +37 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +567 -35
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +110 -17
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +19 -3
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +195 -18
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +932 -97
- package/server/server-runtime.mjs +203 -11
- package/server/services/about-service.mjs +1 -0
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/claude-session-leftovers.mjs +226 -0
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/deepseek-balance-service.mjs +102 -0
- package/server/services/deepseek-settings-service.mjs +380 -0
- package/server/services/deepseek-usage-import.mjs +471 -0
- package/server/services/model-variant-catalog-service.mjs +1 -0
- package/server/services/plan-usage-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +431 -0
- package/server/services/provider-model-selection.mjs +1 -0
- package/server/services/provider-reasoning-effort.mjs +15 -2
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +958 -31
- package/server/services/session-worker-launch-service.mjs +233 -82
- package/server/services/worker-secret-redaction.mjs +318 -0
- package/shared/context-window-fallbacks.mjs +3 -0
- package/shared/deepseek-api.mjs +137 -0
- package/shared/deepseek-models.mjs +114 -0
- package/shared/deepseek-pricing.mjs +146 -0
- package/shared/host-claude-session-env.mjs +101 -0
- package/shared/model-id.mjs +7 -0
- package/shared/preview-tool-core.mjs +1 -1
- package/shared/provider-routing.mjs +2 -2
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +554 -26
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
REMOTE_RELAY_MODES,
|
|
14
14
|
REMOTE_RELAY_PROVIDERS,
|
|
15
15
|
REMOTE_RELAY_SESSION_SCOPES,
|
|
16
|
+
REMOTE_RELAY_THINKING_DISPLAYS,
|
|
16
17
|
} from '../../shared/remote-relay-contract.mjs';
|
|
18
|
+
import { RELAY_ASK_URGENCIES } from '../../shared/relay-asks.mjs';
|
|
17
19
|
import {
|
|
18
20
|
REMOTE_RELAY_CALL_FAILED,
|
|
19
21
|
REMOTE_RELAY_TOOL_DESCRIPTION,
|
|
@@ -67,7 +69,21 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
|
|
|
67
69
|
mode: choice('mode', REMOTE_RELAY_MODES),
|
|
68
70
|
effort: text('effort'),
|
|
69
71
|
title: text('title'),
|
|
72
|
+
// The one exception to the parity with the JSON schema: Claude may send
|
|
73
|
+
// null for update_session's group (out of the group), as "none" does on
|
|
74
|
+
// the providers whose strict schemas take a string only.
|
|
75
|
+
group: z.string().nullable().optional().describe(FIELDS.group.description),
|
|
76
|
+
auto_compact_window: number('auto_compact_window'),
|
|
77
|
+
thinking_enabled: z.boolean().optional().describe(FIELDS.thinking_enabled.description),
|
|
78
|
+
thinking_display: choice('thinking_display', REMOTE_RELAY_THINKING_DISPLAYS),
|
|
70
79
|
question_id: text('question_id'),
|
|
80
|
+
// ask: the contract's validator refuses a question or detail that is too
|
|
81
|
+
// long, or more than eight options, with a message the model can act on.
|
|
82
|
+
question: text('question'),
|
|
83
|
+
options: z.array(z.string()).optional().describe(FIELDS.options.description),
|
|
84
|
+
recommendation: text('recommendation'),
|
|
85
|
+
detail: text('detail'),
|
|
86
|
+
urgency: choice('urgency', RELAY_ASK_URGENCIES),
|
|
71
87
|
answer: text('answer'),
|
|
72
88
|
choices: z.array(z.string()).optional().describe(FIELDS.choices.description),
|
|
73
89
|
if_busy: choice('if_busy', REMOTE_RELAY_IF_BUSY),
|
|
@@ -78,18 +94,37 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
|
|
|
78
94
|
match: text('match'),
|
|
79
95
|
stalled_minutes: number('stalled_minutes'),
|
|
80
96
|
heartbeat_minutes: number('heartbeat_minutes'),
|
|
97
|
+
digest_minutes: number('digest_minutes'),
|
|
98
|
+
// Loose and every field optional: the contract's validator checks the
|
|
99
|
+
// times and the zone and says what is wrong; an empty one is no option.
|
|
100
|
+
quiet_hours: z.looseObject({
|
|
101
|
+
from: z.string().optional().describe(FIELDS.quiet_hours.properties.from.description),
|
|
102
|
+
to: z.string().optional().describe(FIELDS.quiet_hours.properties.to.description),
|
|
103
|
+
tz: z.string().optional().describe(FIELDS.quiet_hours.properties.tz.description),
|
|
104
|
+
}).optional().describe(FIELDS.quiet_hours.description),
|
|
105
|
+
// A list, or the comma-separated text the contract takes as well.
|
|
106
|
+
exclude: z.union([z.array(z.string()), z.string()]).optional().describe(FIELDS.exclude.description),
|
|
81
107
|
subscription_id: text('subscription_id'),
|
|
82
108
|
watcher: text('watcher'),
|
|
83
109
|
from: text('from'),
|
|
110
|
+
at: text('at'),
|
|
111
|
+
every_minutes: number('every_minutes'),
|
|
112
|
+
schedule_id: text('schedule_id'),
|
|
84
113
|
task_id: text('task_id'),
|
|
85
114
|
lines: number('lines'),
|
|
86
115
|
};
|
|
87
116
|
|
|
117
|
+
// What the tool registers: the shape as a loose object. A raw shape becomes a
|
|
118
|
+
// strict-stripping object in the SDK's MCP layer, which would drop a key the
|
|
119
|
+
// schema lacks before the contract's validator could refuse it (create_session)
|
|
120
|
+
// or name it in `ignored` (the other actions). The loose object lets it through.
|
|
121
|
+
export const REMOTE_RELAY_TOOL_ZOD_SCHEMA = z.looseObject(REMOTE_RELAY_TOOL_ZOD_SHAPE);
|
|
122
|
+
|
|
88
123
|
export function createRemoteRelayToolDefinition({ api, getConversationId = () => '', dbg = () => {} } = {}) {
|
|
89
124
|
return {
|
|
90
125
|
name: REMOTE_RELAY_TOOL_NAME,
|
|
91
126
|
description: REMOTE_RELAY_TOOL_DESCRIPTION,
|
|
92
|
-
inputSchema:
|
|
127
|
+
inputSchema: REMOTE_RELAY_TOOL_ZOD_SCHEMA,
|
|
93
128
|
// `extra.signal` is the MCP request's: the SDK aborts it when the CLI
|
|
94
129
|
// cancels the call (a Stop interrupting the turn) or the process's MCP
|
|
95
130
|
// transport closes, and the pending relay request goes with it.
|
|
@@ -6,6 +6,9 @@ import { MEDIA_EMBED_INSTRUCTION_TEXT } from '../../shared/media-embed-instructi
|
|
|
6
6
|
import { REMOTE_RELAY_TOOL_CALL_TIMEOUT_MS } from '../../shared/remote-relay-tool-core.mjs';
|
|
7
7
|
import { createPreviewToolDefinition } from './claude-preview-tool.mjs';
|
|
8
8
|
import { createRemoteRelayToolDefinition } from './claude-remote-relay-tool.mjs';
|
|
9
|
+
import { CLAUDE_LIVE_FLAG_SETTINGS_CODE, CLAUDE_LIVE_FLAG_SETTINGS_GUIDANCE } from './claude-turn-failure.mjs';
|
|
10
|
+
|
|
11
|
+
export { CLAUDE_LIVE_FLAG_SETTINGS_CODE };
|
|
9
12
|
|
|
10
13
|
// The name the in-process MCP server is registered under; it prefixes every
|
|
11
14
|
// tool it carries on the wire (`mcp__relay__preview`, `mcp__relay__remote_relay`).
|
|
@@ -229,14 +232,25 @@ export function createRelayMcpServer({ api, getConversationId, dbg, remoteRelayT
|
|
|
229
232
|
* emitted, which also closes the control transport (canUseTool, context
|
|
230
233
|
* usage), so the session process keeps this open while background tasks or
|
|
231
234
|
* queued continuations still need the process alive.
|
|
235
|
+
*
|
|
236
|
+
* `held`: nothing is sent until `release()` (the messages pushed meanwhile
|
|
237
|
+
* wait in order). `fail(error)` instead makes the stream throw it, which the
|
|
238
|
+
* SDK answers by aborting the query; nothing held is ever sent, and neither
|
|
239
|
+
* is it when the stream ends while still held.
|
|
232
240
|
*/
|
|
233
|
-
function createPushableUserMessageStream() {
|
|
241
|
+
function createPushableUserMessageStream({ held = false } = {}) {
|
|
234
242
|
const queued = [];
|
|
235
243
|
let wake = null;
|
|
236
244
|
let ended = false;
|
|
245
|
+
let holding = held;
|
|
246
|
+
let failure = null;
|
|
237
247
|
async function* stream() {
|
|
238
248
|
for (;;) {
|
|
239
|
-
|
|
249
|
+
if (failure) throw failure;
|
|
250
|
+
if (!holding) {
|
|
251
|
+
while (queued.length && !failure) yield queued.shift();
|
|
252
|
+
if (failure) continue;
|
|
253
|
+
}
|
|
240
254
|
if (ended) return;
|
|
241
255
|
await new Promise((resolve) => { wake = resolve; });
|
|
242
256
|
wake = null;
|
|
@@ -257,9 +271,80 @@ function createPushableUserMessageStream() {
|
|
|
257
271
|
ended = true;
|
|
258
272
|
wake?.();
|
|
259
273
|
},
|
|
274
|
+
release() {
|
|
275
|
+
holding = false;
|
|
276
|
+
wake?.();
|
|
277
|
+
},
|
|
278
|
+
fail(error) {
|
|
279
|
+
failure = error;
|
|
280
|
+
wake?.();
|
|
281
|
+
},
|
|
260
282
|
};
|
|
261
283
|
}
|
|
262
284
|
|
|
285
|
+
/** Every string in a settings object, for taking them out of a text. */
|
|
286
|
+
function settingsStrings(value, found = []) {
|
|
287
|
+
if (typeof value === 'string') {
|
|
288
|
+
if (value) found.push(value);
|
|
289
|
+
} else if (value && typeof value === 'object') {
|
|
290
|
+
for (const item of Object.values(value)) settingsStrings(item, found);
|
|
291
|
+
}
|
|
292
|
+
return found;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The blanked keys of a backend's live flag settings (top-level and `env`
|
|
297
|
+
* values that are ''), for the spawn's `settings` too: an empty value holds
|
|
298
|
+
* no secret, and the CLI acts on some settings at startup, before the live
|
|
299
|
+
* ones land (it runs a folder's apiKeyHelper then; CLI 2.1.304,
|
|
300
|
+
* scripts/deepseek-cli-proof.mjs). Null when there is none.
|
|
301
|
+
*/
|
|
302
|
+
function liveSettingsBlanks(live) {
|
|
303
|
+
if (!live) return null;
|
|
304
|
+
const blanks = {};
|
|
305
|
+
for (const [key, value] of Object.entries(live)) {
|
|
306
|
+
if (value === '') blanks[key] = '';
|
|
307
|
+
}
|
|
308
|
+
const env = Object.entries(live.env && typeof live.env === 'object' ? live.env : {}).filter(([, value]) => value === '');
|
|
309
|
+
if (env.length) blanks.env = Object.fromEntries(env);
|
|
310
|
+
return Object.keys(blanks).length ? blanks : null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** The spawn's `settings` with the live settings' blanks on top. */
|
|
314
|
+
function withLiveSettingsBlanks(spawnSettings, live) {
|
|
315
|
+
const blanks = liveSettingsBlanks(live);
|
|
316
|
+
if (!blanks) return spawnSettings;
|
|
317
|
+
const settings = { ...(spawnSettings || {}), ...blanks };
|
|
318
|
+
if (spawnSettings?.env || blanks.env) settings.env = { ...(spawnSettings?.env || {}), ...(blanks.env || {}) };
|
|
319
|
+
return settings;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* How long the CLI may take to answer a backend's live flag settings (ms).
|
|
324
|
+
* It answers in well under a second (CLI 2.1.304); one that never does fails
|
|
325
|
+
* the turn like a refusal, and the held message is never sent.
|
|
326
|
+
*/
|
|
327
|
+
export const LIVE_FLAG_SETTINGS_TIMEOUT_MS = 60_000;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The Error a spawn fails with when the CLI refused its live flag settings.
|
|
331
|
+
* The message names no value; `detail` is the CLI's own words with every
|
|
332
|
+
* value of the settings taken out.
|
|
333
|
+
*/
|
|
334
|
+
function liveFlagSettingsError(cause, settings, label = '') {
|
|
335
|
+
const error = new Error(
|
|
336
|
+
`Claude Code did not take the ${label ? `${label} ` : ''}connection settings before the first message, so nothing was sent.`,
|
|
337
|
+
);
|
|
338
|
+
error.code = CLAUDE_LIVE_FLAG_SETTINGS_CODE;
|
|
339
|
+
error.guidance = CLAUDE_LIVE_FLAG_SETTINGS_GUIDANCE;
|
|
340
|
+
let detail = String(cause?.message || cause || '');
|
|
341
|
+
for (const value of settingsStrings(settings).sort((a, b) => b.length - a.length)) {
|
|
342
|
+
detail = detail.split(value).join('[redacted]');
|
|
343
|
+
}
|
|
344
|
+
error.detail = detail.slice(0, 200);
|
|
345
|
+
return error;
|
|
346
|
+
}
|
|
347
|
+
|
|
263
348
|
/**
|
|
264
349
|
* The only module that imports `@anthropic-ai/claude-agent-sdk`.
|
|
265
350
|
*
|
|
@@ -293,13 +378,19 @@ export function startClaudeSession({
|
|
|
293
378
|
// Whether this process carries `remote_relay` (the worker's registration
|
|
294
379
|
// decision). Fixed for the process: the tool list is part of the spawn.
|
|
295
380
|
remoteRelayTool = false,
|
|
381
|
+
// Another endpoint for the CLI (null = Anthropic through the host's Claude
|
|
382
|
+
// login): it builds the CLI's environment and maps model and effort into
|
|
383
|
+
// its terms (../deepseek-worker/deepseek-claude-backend.mjs).
|
|
384
|
+
backend = null,
|
|
385
|
+
liveFlagSettingsTimeoutMs = LIVE_FLAG_SETTINGS_TIMEOUT_MS,
|
|
296
386
|
queryImpl = query,
|
|
297
387
|
dbg = () => {},
|
|
298
388
|
} = {}) {
|
|
299
389
|
const normalizedModel = String(model || '').trim().toLowerCase();
|
|
300
|
-
const effort = normalizeClaudeEffort(reasoningEffort);
|
|
390
|
+
const effort = backend ? '' : normalizeClaudeEffort(reasoningEffort);
|
|
301
391
|
const ultracode = effort === CLAUDE_ULTRACODE_EFFORT;
|
|
302
|
-
const effortOption = ultracode ? 'xhigh' : effort;
|
|
392
|
+
const effortOption = backend ? backend.effortOption(reasoningEffort) : (ultracode ? 'xhigh' : effort);
|
|
393
|
+
const cliModel = backend ? backend.cliModel(model) : String(model || '').trim();
|
|
303
394
|
// Spawn-time twin of the flag-settings helpers: the settings layer is the
|
|
304
395
|
// only way to hand a fresh CLI the session-scoped ultracode flag, the
|
|
305
396
|
// auto-compact window and the thinking pin, and all three share one
|
|
@@ -310,17 +401,40 @@ export function startClaudeSession({
|
|
|
310
401
|
const relayMcpServer = typeof api === 'function'
|
|
311
402
|
? createRelayMcpServer({ api, getConversationId, dbg, remoteRelayTool: remoteRelayTool === true })
|
|
312
403
|
: null;
|
|
313
|
-
|
|
404
|
+
// A backend's CLI always gets an environment of its own: the worker's
|
|
405
|
+
// carries the backend's secret and maybe the host's Anthropic settings.
|
|
406
|
+
// The model goes in as well: the backend pins Claude Code's model aliases to
|
|
407
|
+
// it, so a sub-agent asked for as "opus" runs on the session's model. The
|
|
408
|
+
// Bash limits go on top: the backend drops a host session's Bash cap, not
|
|
409
|
+
// the relay's own.
|
|
410
|
+
const backendEnv = backend ? backend.spawnEnv(process.env, { effort: reasoningEffort, model }) : null;
|
|
411
|
+
const limitEnv = claudeSpawnEnv({ backgroundCommandLimitMs, baseEnv: backendEnv || process.env });
|
|
412
|
+
const env = limitEnv || backendEnv;
|
|
413
|
+
// A backend's pins that may not go on the command line (a key): `settings`
|
|
414
|
+
// below is the CLI's `--settings` argument, which other users of the host
|
|
415
|
+
// can read. These go into the same flag layer over the control channel
|
|
416
|
+
// (stdin), and no user message is sent before the CLI has taken them: the
|
|
417
|
+
// first API request of the process comes after (measured on CLI 2.1.304,
|
|
418
|
+
// the CLI proofs in scripts/). The layer outranks the user's, the
|
|
419
|
+
// project's and the local settings and holds through their reloads and
|
|
420
|
+
// through later applyFlagSettings calls of other keys. A refusal ends the
|
|
421
|
+
// process before anything is sent (CLAUDE_LIVE_FLAG_SETTINGS_CODE), and so
|
|
422
|
+
// does no answer within liveFlagSettingsTimeoutMs. Their blanks go on the
|
|
423
|
+
// command line as well, for what the CLI does at startup. They are built
|
|
424
|
+
// from the CLI's environment: a pin holds the value the CLI starts with.
|
|
425
|
+
const liveSettings = typeof backend?.liveFlagSettings === 'function' ? backend.liveFlagSettings(env || process.env) : null;
|
|
426
|
+
const live = liveSettings && typeof liveSettings === 'object' && Object.keys(liveSettings).length ? liveSettings : null;
|
|
427
|
+
const settings = withLiveSettingsBlanks(spawnSettings, live);
|
|
314
428
|
const options = {
|
|
315
429
|
cwd,
|
|
316
430
|
permissionMode: permissionModeForRelayMode(relayMode),
|
|
317
|
-
systemPrompt: systemPromptForRelayMode(relayMode, { backgroundCommandLimitMs:
|
|
431
|
+
systemPrompt: systemPromptForRelayMode(relayMode, { backgroundCommandLimitMs: limitEnv ? backgroundCommandLimitMs : null }),
|
|
318
432
|
includePartialMessages: true,
|
|
319
433
|
forwardSubagentText: true,
|
|
320
434
|
...(effortOption ? { effort: effortOption } : {}),
|
|
321
|
-
...(
|
|
435
|
+
...(settings ? { settings } : {}),
|
|
322
436
|
...(env ? { env } : {}),
|
|
323
|
-
...(normalizedModel && normalizedModel !== 'auto' ? { model:
|
|
437
|
+
...(normalizedModel && normalizedModel !== 'auto' && cliModel ? { model: cliModel } : {}),
|
|
324
438
|
...(String(resume || '').trim() ? { resume: String(resume).trim() } : {}),
|
|
325
439
|
...(abortController ? { abortController } : {}),
|
|
326
440
|
...(typeof canUseTool === 'function' ? { canUseTool } : {}),
|
|
@@ -329,8 +443,52 @@ export function startClaudeSession({
|
|
|
329
443
|
? { pathToClaudeCodeExecutable: String(pathToClaudeCodeExecutable).trim() }
|
|
330
444
|
: {}),
|
|
331
445
|
};
|
|
332
|
-
const { stream, push, end } = createPushableUserMessageStream();
|
|
446
|
+
const { stream, push, end, release, fail } = createPushableUserMessageStream({ held: Boolean(live) });
|
|
333
447
|
const turn = queryImpl({ prompt: stream, options });
|
|
448
|
+
if (live) {
|
|
449
|
+
// The wait ends with the answer, the timeout or the session's abort,
|
|
450
|
+
// whichever comes first; each one clears the others.
|
|
451
|
+
const signal = abortController?.signal || null;
|
|
452
|
+
let timer = null;
|
|
453
|
+
let onAbort = null;
|
|
454
|
+
const settled = () => {
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
timer = null;
|
|
457
|
+
if (onAbort) signal?.removeEventListener('abort', onAbort);
|
|
458
|
+
onAbort = null;
|
|
459
|
+
};
|
|
460
|
+
const unanswered = new Promise((resolve, reject) => {
|
|
461
|
+
const waitMs = Math.max(0, Number(liveFlagSettingsTimeoutMs) || 0);
|
|
462
|
+
timer = setTimeout(() => {
|
|
463
|
+
timer = null;
|
|
464
|
+
reject(new Error(`no answer within ${Math.ceil(waitMs / 1000)} s`));
|
|
465
|
+
}, waitMs);
|
|
466
|
+
timer.unref?.();
|
|
467
|
+
if (signal) {
|
|
468
|
+
onAbort = settled;
|
|
469
|
+
if (signal.aborted) settled();
|
|
470
|
+
else signal.addEventListener('abort', onAbort, { once: true });
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
turn.liveFlagSettingsApplied = Promise.race([
|
|
474
|
+
Promise.resolve().then(() => {
|
|
475
|
+
if (typeof turn.applyFlagSettings !== 'function') throw new Error('applyFlagSettings is not available');
|
|
476
|
+
return turn.applyFlagSettings(live);
|
|
477
|
+
}),
|
|
478
|
+
unanswered,
|
|
479
|
+
])
|
|
480
|
+
.finally(settled)
|
|
481
|
+
.then(() => {
|
|
482
|
+
release();
|
|
483
|
+
return true;
|
|
484
|
+
}, (error) => {
|
|
485
|
+
const refused = liveFlagSettingsError(error, live, backend?.label || '');
|
|
486
|
+
turn.liveFlagSettingsError = refused;
|
|
487
|
+
dbg('live flag settings refused', refused.detail);
|
|
488
|
+
fail(refused);
|
|
489
|
+
return false;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
334
492
|
applyThinkingDisplay(turn, thinkingDisplay, { dbg });
|
|
335
493
|
turn.pushUserMessage = push;
|
|
336
494
|
// The session process MUST call this on every teardown path or the CLI
|