newmark-agent 0.6.2 → 0.6.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.
@@ -2,6 +2,10 @@
2
2
  export interface RequestContextEstimate {
3
3
  requestId: string;
4
4
  runId: string;
5
+ /** Immutable runtime branch that owned this model request. */
6
+ branchId: string;
7
+ /** Content hash of the actual submitted system/messages/tools payload. */
8
+ contextHash: string;
5
9
  model: string;
6
10
  at: string;
7
11
  estimatedTokens: number;
@@ -14,5 +18,5 @@ export interface RequestContextEstimate {
14
18
  inputTokens?: number;
15
19
  cacheReadTokens?: number;
16
20
  }
17
- export declare function estimateSubmittedContext(messages: Array<Record<string, unknown>>, systemPrompt: string, tools: unknown[], buildBlockStart: number): Omit<RequestContextEstimate, 'requestId' | 'runId' | 'model' | 'at'>;
21
+ export declare function estimateSubmittedContext(messages: Array<Record<string, unknown>>, systemPrompt: string, tools: unknown[], buildBlockStart: number): Omit<RequestContextEstimate, 'requestId' | 'runId' | 'model' | 'at' | 'branchId' | 'contextHash'>;
18
22
  //# sourceMappingURL=requestContextEstimate.d.ts.map
@@ -1311,12 +1311,19 @@ class LLMProvider {
1311
1311
  async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
1312
1312
  if (signal?.aborted)
1313
1313
  throw abortFailure(signal);
1314
+ // A non-positive value means "provider owns the output limit". Protocols
1315
+ // whose wire format requires a positive max_tokens (Anthropic, GitHub
1316
+ // Models) fall back to the selected reasoning tier's budget; OpenAI-style
1317
+ // adapters omit the field entirely so the endpoint decides.
1318
+ const requiredMaxTokens = maxTokens > 0
1319
+ ? maxTokens
1320
+ : this.intelligenceConfig(reasoningTier || 'medium').maxTokens;
1314
1321
  if (this.protocol() === 'anthropic') {
1315
- yield* this.anthropicChatWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal);
1322
+ yield* this.anthropicChatWithTools(model, messages, systemPrompt, temperature, requiredMaxTokens, tools, signal);
1316
1323
  return;
1317
1324
  }
1318
1325
  if (this.protocol() === 'github_models') {
1319
- yield* this.githubModelsChatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal);
1326
+ yield* this.githubModelsChatStreamWithTools(model, messages, systemPrompt, temperature, requiredMaxTokens, tools, signal);
1320
1327
  return;
1321
1328
  }
1322
1329
  if (this.useProviderAdaptersV2) {
package/dist/main.js CHANGED
@@ -3292,6 +3292,10 @@ else {
3292
3292
  // Other conversations' paused Flows are never touched by this send.
3293
3293
  const flow = activeFlowStateFor(target);
3294
3294
  const snapshot = await runtimeSnapshotForTarget(target);
3295
+ // The renderer's visible composer selection is authoritative for this
3296
+ // send. It is provider-qualified (deployment:providerId:modelId), so it
3297
+ // can never fall back to a same-named model on another provider.
3298
+ const requestedModelSelection = String(requested.model || '').trim();
3295
3299
  const structured = typeof message === 'string' ? { text: message } : { ...message };
3296
3300
  const requestedMode = String(requested.requestedMode || structured.visibleMode || snapshot.mode || 'build');
3297
3301
  const inputMode = String(requested.inputMode || snapshot.inputMode || 'guide') === 'next' ? 'next' : 'guide';
@@ -3319,9 +3323,18 @@ else {
3319
3323
  });
3320
3324
  return { ok: receipt.status !== 'rejected', accepted: receipt.status !== 'rejected', receipt, ...await runtimeSnapshotForTarget(target) };
3321
3325
  }
3326
+ const requestedBranchPath = Array.isArray(structured.branchPath)
3327
+ ? (structured.branchPath || []).map(id => String(id || '')).filter(Boolean)
3328
+ : String(structured.branchPath || '').split('>').map(id => id.trim()).filter(Boolean);
3329
+ const requestedBranchNodeId = String(structured.branchNodeId
3330
+ || requestedBranchPath[requestedBranchPath.length - 1]
3331
+ || '');
3322
3332
  return await applyConversationAction(target, 'queue_enqueue', '', {
3323
3333
  id, text: structured.visibleUserInput || structured.text, requestedMode: requestedMode === 'flow' ? 'build' : requestedMode,
3324
3334
  goalObjective: objective, images: structured.images, createdAt: new Date().toISOString(),
3335
+ branchNodeId: requestedBranchNodeId || undefined,
3336
+ branchPath: requestedBranchPath.length ? requestedBranchPath : undefined,
3337
+ modelSelection: requestedModelSelection || undefined,
3325
3338
  });
3326
3339
  }
3327
3340
  await selectConversationMode(target, requestedMode);
@@ -3335,10 +3348,10 @@ else {
3335
3348
  // 发送命令时锁定输入框选择的模型:接受命令后的整个运行过程都以该
3336
3349
  // 模型为准(唯一例外是显式的不可用回退,且回退会以结构化事件同步
3337
3350
  // 到前端输入框下方的选择区,而不是隐藏的参数回退)。
3338
- const requestedModel = String(snapshot.model || agent.model);
3351
+ const requestedModel = String(requestedModelSelection || snapshot.model || agent.model);
3339
3352
  const options = {
3340
3353
  mode: requestedMode === 'goal' ? 'goal' : requestedMode,
3341
- model: String(snapshot.model || agent.ensureUsableModelSelection()),
3354
+ model: String(requestedModelSelection || snapshot.model || agent.ensureUsableModelSelection()),
3342
3355
  intelligence: String(snapshot.intelligence || agent.intelligence),
3343
3356
  inputMode: inputMode,
3344
3357
  engine: agent.engine,
@@ -3460,6 +3473,16 @@ else {
3460
3473
  settlement: new Promise(resolve => { resolvePendingSettlement = resolve; }),
3461
3474
  };
3462
3475
  pendingFlowStarts.set(flowKey, pending);
3476
+ // Admit the Flow build at command-acceptance time. It may wait behind a
3477
+ // running Build, but its queue position fixes the parent of every later
3478
+ // Next item; the actual Flow start only claims this existing build.
3479
+ const flowCommandId = `flow-${flowKey}-${(0, crypto_1.randomUUID)()}`;
3480
+ const flowBuildId = ensureConversationKernel(root).admitExternalBuild(flowTarget, {
3481
+ commandId: flowCommandId,
3482
+ text: String(input || ''),
3483
+ requestedMode: 'flow',
3484
+ modelSelection: agent.modelSelectionValue(),
3485
+ });
3463
3486
  let previousQueuePaused = false;
3464
3487
  try {
3465
3488
  let state = await runtimeSnapshotForTarget(flowTarget);
@@ -3485,7 +3508,13 @@ else {
3485
3508
  }
3486
3509
  const flowAgent = ensureConversationKernel(root).beginExternalRun(flowTarget, {
3487
3510
  mode: 'flow', model: agent.model, intelligence: agent.intelligence, inputMode: agent.inputMode, engine: agent.engine,
3488
- }, () => isolatedConversationAgent(flowTarget), previousQueuePaused);
3511
+ }, () => isolatedConversationAgent(flowTarget), previousQueuePaused, {
3512
+ commandId: flowCommandId,
3513
+ text: String(input || ''),
3514
+ requestedMode: 'flow',
3515
+ modelSelection: agent.modelSelectionValue(),
3516
+ existingBuildId: flowBuildId || undefined,
3517
+ });
3489
3518
  mainConversationOwners.add(flowKey);
3490
3519
  conversationSelections.set(flowKey, { ...conversationSelections.get(flowKey), mode: 'flow' });
3491
3520
  const previousMode = flowAgent.mode;
@@ -3655,7 +3684,13 @@ else {
3655
3684
  const flowAgent = ensureConversationKernel(root).beginExternalRun(flowTarget, {
3656
3685
  mode: 'flow', model: suspension.flowAgent?.model || agent.model, intelligence: suspension.flowAgent?.intelligence || agent.intelligence,
3657
3686
  inputMode: suspension.flowAgent?.inputMode || agent.inputMode, engine: agent.engine,
3658
- }, () => suspension.flowAgent || isolatedConversationAgent(flowTarget), suspension.queueWasPaused ?? true);
3687
+ }, () => suspension.flowAgent || isolatedConversationAgent(flowTarget), suspension.queueWasPaused ?? true, {
3688
+ commandId: `flow-resume-${flowKey}-${(0, crypto_1.randomUUID)()}`,
3689
+ text: String(response || ''),
3690
+ requestedMode: 'flow',
3691
+ modelSelection: agent.modelSelectionValue(),
3692
+ resume: true,
3693
+ });
3659
3694
  mainConversationOwners.add(flowKey);
3660
3695
  suspension.flowAgent = flowAgent;
3661
3696
  // A previous Flow may have been interrupted just before its isolated
@@ -44,8 +44,13 @@ class ChatCompletionsAdapter {
44
44
  model: request.model,
45
45
  messages,
46
46
  temperature: request.temperature,
47
- max_tokens: request.maxOutputTokens,
48
47
  };
48
+ // Omit the cap entirely for provider-owned output. Only auxiliary
49
+ // requests that explicitly need a bounded completion send a positive
50
+ // max_tokens value.
51
+ if (Number.isFinite(request.maxOutputTokens) && request.maxOutputTokens > 0) {
52
+ body.max_tokens = request.maxOutputTokens;
53
+ }
49
54
  if (tools.length) {
50
55
  body.tools = tools;
51
56
  body.tool_choice = 'auto';
@@ -69,8 +69,11 @@ class ResponsesAdapter {
69
69
  model: request.model,
70
70
  input: input.length ? input : [{ role: 'user', content: '' }],
71
71
  temperature: request.temperature,
72
- max_output_tokens: request.maxOutputTokens,
73
72
  };
73
+ // Omit the cap entirely for provider-owned output.
74
+ if (Number.isFinite(request.maxOutputTokens) && request.maxOutputTokens > 0) {
75
+ body.max_output_tokens = request.maxOutputTokens;
76
+ }
74
77
  if (request.reasoningEffort)
75
78
  body.reasoning = { effort: request.reasoningEffort, summary: 'auto' };
76
79
  if (request.systemPrompt)
package/dist/server.d.ts CHANGED
@@ -32,6 +32,7 @@ export interface HostedMobileServerOptions {
32
32
  clientMessageId?: string;
33
33
  flowName?: string;
34
34
  flowStart?: number;
35
+ model?: string;
35
36
  }) => Promise<Record<string, unknown>>;
36
37
  }
37
38
  export interface HostedMobileServerStatus {
package/dist/server.js CHANGED
@@ -153,6 +153,7 @@ function hostedCommandOptions(params) {
153
153
  clientMessageId: String(params.clientMessageId || ''),
154
154
  flowName: String(params.flowName || params.defaultFlow || ''),
155
155
  flowStart: typeof params.flowStart === 'number' && Number.isFinite(params.flowStart) ? params.flowStart : undefined,
156
+ model: String(params.model || ''),
156
157
  };
157
158
  }
158
159
  const MOBILE_EDITABLE_EXTENSIONS = new Set([
@@ -7143,6 +7143,10 @@ var state = {
7143
7143
  _conversationLoadingMsgEl: null,
7144
7144
  _syncedMode: null,
7145
7145
  _syncedModel: null,
7146
+ // Monotonic revision for explicit renderer model changes. Activation
7147
+ // snapshots may restore the conversation's remembered model only when no
7148
+ // newer local selection was made while the snapshot was in flight.
7149
+ modelSelectionRevision: 0,
7146
7150
  _syncedInputMode: null,
7147
7151
  _editingQueueIndex: -1,
7148
7152
  _editingGoalText: '',
@@ -9353,7 +9357,7 @@ var api = window.api || {
9353
9357
  conversationId: target.conversationId,
9354
9358
  action: action
9355
9359
  };
9356
- ['id', 'text', 'value', 'requestedMode', 'goalObjective', 'createdAt', 'images', 'paused', 'orderedIds'].forEach(function(field) {
9360
+ ['id', 'text', 'value', 'requestedMode', 'goalObjective', 'createdAt', 'images', 'paused', 'orderedIds', 'branchNodeId', 'branchPath'].forEach(function(field) {
9357
9361
  if (input && Object.prototype.hasOwnProperty.call(input, field)) body[field] = input[field];
9358
9362
  });
9359
9363
  var r = await fetch('/api/queue-action', {
@@ -13287,6 +13291,35 @@ function hydrateConversationBranchState(snapshot) {
13287
13291
  rememberRuntimeConversationBranchPath(currentConversationTarget(s.conversationId || activeConversationId()));
13288
13292
  }
13289
13293
 
13294
+ // Apply the conversation's remembered provider-qualified model to the
13295
+ // composer selector. The backend snapshot is authoritative; a newer explicit
13296
+ // user selection made while the snapshot was in flight wins via the revision
13297
+ // guard passed by the activation caller.
13298
+ function applyConversationModelSelection(snapshot) {
13299
+ var nextModel = String(snapshot && snapshot.model || '').trim();
13300
+ if (!nextModel) return;
13301
+ state.model = nextModel;
13302
+ if (typeof snapshot.modelLabel === 'string' && snapshot.modelLabel) state.modelLabel = snapshot.modelLabel;
13303
+ state._syncedModel = nextModel;
13304
+ var sel = els['model-select'] || document.getElementById('model-select');
13305
+ if (!sel) {
13306
+ if (window.syncModelSelectSurface) window.syncModelSelectSurface();
13307
+ return;
13308
+ }
13309
+ var hasOption = Array.prototype.some.call(sel.options, function(option) {
13310
+ return String(option.value || '') === nextModel;
13311
+ });
13312
+ if (hasOption) {
13313
+ sel.value = nextModel;
13314
+ window.syncModelSelectSurface();
13315
+ } else if (window.refreshModelSelect) {
13316
+ // The remembered deployment is not in the current enabled catalog (for
13317
+ // example its provider was explicitly disabled). Reuse the existing
13318
+ // visibility fallback instead of displaying a model that cannot run.
13319
+ window.refreshModelSelect();
13320
+ }
13321
+ }
13322
+
13290
13323
  function applyConversationSnapshot(s, requestedConversationId, options) {
13291
13324
  if (!s) return;
13292
13325
  if (s.computerUse) {
@@ -13309,6 +13342,11 @@ function applyConversationSnapshot(s, requestedConversationId, options) {
13309
13342
  if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
13310
13343
  hydrateConversationDraftSnapshot(s, snapshotTarget);
13311
13344
  hydrateConversationBranchState(s);
13345
+ var modelRevisionMatches = !options || options.modelRevision === undefined
13346
+ || Number(state.modelSelectionRevision || 0) === Number(options.modelRevision || 0);
13347
+ if (modelRevisionMatches && isActiveConversationTarget(snapshotTarget)) {
13348
+ applyConversationModelSelection(s);
13349
+ }
13312
13350
  rebindQueueToRuntimeBranch(snapshotTarget);
13313
13351
  if (s && Array.isArray(s.workRuns) && !preserveReadableHistory) {
13314
13352
  var viewedBranchIdForSync = Array.isArray(s.viewedBranchNodePath) && s.viewedBranchNodePath.length
@@ -14443,6 +14481,7 @@ function applyModelFallbackToInputSurface(event) {
14443
14481
  var nextValue = modelDeploymentValueForName(f.to, f.providerId || '');
14444
14482
  if (!nextValue || String(state.model || '') === nextValue) return;
14445
14483
  state.model = nextValue;
14484
+ state.modelSelectionRevision = Number(state.modelSelectionRevision || 0) + 1;
14446
14485
  state.modelLabel = '';
14447
14486
  // 不预置 _syncedModel:下一次发送前的 syncConversationExecutionState 会把
14448
14487
  // 回退模型正式同步到后端 host agent(并持久化为该对话的首选模型)。
@@ -17208,6 +17247,11 @@ function bindBackendQueuedRequestToTarget(text, target, occurrence) {
17208
17247
  request.createdAt = queueItem.createdAt;
17209
17248
  request.runId = queueItem.runId;
17210
17249
  request.images = (queueItem.images || []).map(function(image) { return Object.assign({}, image); });
17250
+ if (Array.isArray(queueItem.branchPath) && queueItem.branchPath.length) {
17251
+ request.branchPath = queueItem.branchPath.join('>');
17252
+ } else if (queueItem.branchNodeId) {
17253
+ request.branchPath = String(queueItem.branchNodeId);
17254
+ }
17211
17255
  }
17212
17256
  return request;
17213
17257
  }
@@ -17920,7 +17964,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
17920
17964
  queuedModeRequest.goalDeclaration = true;
17921
17965
  queuedModeRequest.goalObjective = rawText;
17922
17966
  }
17923
- var queueResult = await api.sendMessage({ text: rawText, images: queuedModeRequest.images }, lockedTarget, {
17967
+ var queuedBranchPath = Array.isArray(queuedModeRequest.branchPath)
17968
+ ? queuedModeRequest.branchPath
17969
+ : String(queuedModeRequest.branchPath || '').split('>').filter(Boolean);
17970
+ var queueResult = await api.sendMessage({
17971
+ text: rawText,
17972
+ images: queuedModeRequest.images,
17973
+ branchPath: queuedBranchPath,
17974
+ branchNodeId: String(queuedBranchPath[queuedBranchPath.length - 1] || '')
17975
+ }, lockedTarget, {
17924
17976
  clientMessageId: queuedModeRequest.clientMessageId,
17925
17977
  inputMode: 'next',
17926
17978
  requestedMode: requestedMode,
@@ -18117,12 +18169,6 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
18117
18169
  updateSubmitButtonState();
18118
18170
  var executionMode = opts.goalDeclaration || requestedMode === 'goal' ? 'build'
18119
18171
  : ((opts.forceBuild || idleBuildNextImmediate) && requestedMode === 'build' ? 'build' : requestedMode);
18120
- // The command freezes its mode and delivery policy; selector persistence
18121
- // must not rewrite the mode of an already running target before acceptance.
18122
- if (api.setModel && state.model !== state._syncedModel) {
18123
- var selectedModelResult = await api.setModel(state.model);
18124
- if (selectedModelResult != null) state._syncedModel = state.model;
18125
- }
18126
18172
  if (consumeProvisionalStop(lockedTarget, localRunId)) {
18127
18173
  // Stop/Escape won before the backend accepted the prompt. Do not send the
18128
18174
  // provider request after the user has already cancelled it.
@@ -18166,10 +18212,20 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
18166
18212
  try {
18167
18213
  var activationBeforeSend = pendingConversationActivation(lockedTarget);
18168
18214
  if (activationBeforeSend) await activationBeforeSend;
18215
+ // Bind the model actually shown in the composer to the exact target
18216
+ // conversation after its activation snapshot restored the remembered
18217
+ // selection. This makes the visible provider/model authoritative for the
18218
+ // send and prevents a stale renderer selection from reaching another
18219
+ // conversation's remembered deployment.
18220
+ if (api.setModel && state.model && state.model !== state._syncedModel) {
18221
+ var selectedModelResult = await api.setModel(state.model);
18222
+ if (selectedModelResult != null) state._syncedModel = state.model;
18223
+ }
18169
18224
  var sendPromise = api.sendMessage(requestMessage, lockedTarget, {
18170
18225
  requestedMode: requestedMode, inputMode: effectiveInputMode,
18171
18226
  goalObjective: opts.goalObjective || '',
18172
- clientMessageId: String(requestMessage && requestMessage.clientMessageId || localRunId)
18227
+ clientMessageId: String(requestMessage && requestMessage.clientMessageId || localRunId),
18228
+ model: state.model
18173
18229
  });
18174
18230
  if (requestedMode === 'goal') activateSubmittedGoal(opts.goalObjective || rawText);
18175
18231
  var r = await sendPromise;
@@ -23965,6 +24021,7 @@ function syncBackendConversation() {
23965
24021
  }
23966
24022
  if (conv && api.ensureConversation) {
23967
24023
  var requestedTarget = currentConversationTarget(conv.id);
24024
+ var modelRevisionAtActivation = Number(state.modelSelectionRevision || 0);
23968
24025
  state.activeBackendConversationId = conv.id;
23969
24026
  var historyPromise = loadActiveConversationMessages(conv.id);
23970
24027
  requestedLoadGeneration = state.conversationLoadGeneration;
@@ -23973,7 +24030,7 @@ function syncBackendConversation() {
23973
24030
  var id = (s && s.conversationId) || conv.id;
23974
24031
  if (!requestIsCurrent() || String(id) !== requestedConversationId) return;
23975
24032
  state.activeBackendConversationId = id;
23976
- applyConversationSnapshot(s, id, { preserveReadableHistory: true });
24033
+ applyConversationSnapshot(s, id, { preserveReadableHistory: true, modelRevision: modelRevisionAtActivation });
23977
24034
  }).catch(function(error) {
23978
24035
  if (!requestIsCurrent()) return;
23979
24036
  showUiNotice(
@@ -24505,12 +24562,13 @@ window.newConversation = function(workspaceReference, branchCommunication) {
24505
24562
  renderConversations();
24506
24563
  els['chat-area'].innerHTML = '';
24507
24564
  var target = currentConversationTarget(id);
24565
+ var modelRevisionAtActivation = Number(state.modelSelectionRevision || 0);
24508
24566
  var activation = api.activateConversation ? api.activateConversation(target) : (api.ensureConversation ? api.ensureConversation(target) : Promise.resolve(null));
24509
24567
  var activationKey = runtimeKeyFor(target.workspaceId, target.conversationId);
24510
24568
  var activationReady = activation.then(function(s) {
24511
24569
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
24512
24570
  state.activeBackendConversationId = String((s && s.conversationId) || id);
24513
- if (s) applyConversationSnapshot(s, id, { preserveReadableHistory: true });
24571
+ if (s) applyConversationSnapshot(s, id, { preserveReadableHistory: true, modelRevision: modelRevisionAtActivation });
24514
24572
  if (branchCommunication && api.setConversationBranchCommunication) {
24515
24573
  api.setConversationBranchCommunication(target, true).then(function() {
24516
24574
  var convsNow = currentWorkspaceConversations();
@@ -28307,6 +28365,7 @@ function schedulePostStartupUiRendering() {
28307
28365
  var selectedContextGeneration = state.conversationLoadGeneration;
28308
28366
  var selectedContextModel = this.value;
28309
28367
  state.model = selectedContextModel;
28368
+ state.modelSelectionRevision = Number(state.modelSelectionRevision || 0) + 1;
28310
28369
  window.syncModelSelectSurface();
28311
28370
  if (state.model !== state._syncedModel) {
28312
28371
  var modelResult = await api.setModel(selectedContextModel);