@yeaft/webchat-agent 1.0.412 → 1.0.414

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 (53) hide show
  1. package/browser-runtime/browser-install.js +497 -0
  2. package/browser-runtime/cli.js +88 -0
  3. package/browser-runtime/config.js +116 -0
  4. package/browser-runtime/errors.js +8 -0
  5. package/browser-runtime/extension/manifest.json +18 -0
  6. package/browser-runtime/extension/offscreen.html +5 -0
  7. package/browser-runtime/extension/offscreen.js +101 -0
  8. package/browser-runtime/extension/popup.html +5 -0
  9. package/browser-runtime/extension/popup.js +1 -0
  10. package/browser-runtime/extension/service-worker.js +48 -0
  11. package/browser-runtime/extension.js +45 -0
  12. package/browser-runtime/index.js +5 -0
  13. package/browser-runtime/probe.js +427 -0
  14. package/browser-runtime/protocol.js +71 -0
  15. package/browser-runtime/service.js +132 -0
  16. package/browser-runtime/windows-version-job.ps1 +233 -0
  17. package/browser-runtime/windows-version-worker.js +75 -0
  18. package/browser-runtime/windows-version.js +85 -0
  19. package/cli.js +24 -7
  20. package/context.js +1 -0
  21. package/index.js +18 -1
  22. package/llm-config-cli.js +24 -21
  23. package/local-runtime/version.json +1 -1
  24. package/local-runtime/web/app.bundle.js +22 -5
  25. package/local-runtime/web/app.bundle.js.gz +0 -0
  26. package/local-runtime/web/index.html +2 -2
  27. package/local-runtime/web/style.bundle.css +1 -1
  28. package/local-runtime/web/style.bundle.css.gz +0 -0
  29. package/package.json +5 -1
  30. package/service/config.js +23 -2
  31. package/service/index.js +1 -0
  32. package/service/linux.js +3 -2
  33. package/yeaft/config-api.js +138 -192
  34. package/yeaft/config-store.js +192 -0
  35. package/yeaft/config.js +3 -0
  36. package/yeaft/init.js +20 -7
  37. package/yeaft/sessions/feature-flag.js +15 -33
  38. package/yeaft/storage/atomic.js +43 -17
  39. package/yeaft/tools/create-work-item.js +1 -1
  40. package/yeaft/tools/process-runner.js +86 -13
  41. package/yeaft/work-center/bridge.js +3 -2
  42. package/yeaft/work-center/completion-contract.js +6 -0
  43. package/yeaft/work-center/controller.js +2 -1
  44. package/yeaft/work-center/coordinator.js +45 -14
  45. package/yeaft/work-center/durable-model.js +45 -1
  46. package/yeaft/work-center/dynamic-coordination.js +34 -0
  47. package/yeaft/work-center/evidence.js +235 -0
  48. package/yeaft/work-center/mainline-projection.js +4 -1
  49. package/yeaft/work-center/projection.js +45 -4
  50. package/yeaft/work-center/runner.js +82 -7
  51. package/yeaft/work-center/service.js +6 -0
  52. package/yeaft/work-center/store.js +162 -19
  53. package/yeaft/work-center/workflow.js +6 -0
@@ -7,6 +7,7 @@ import {
7
7
  sanitizeDiagnosticText,
8
8
  } from './debug-projection.js';
9
9
  import { runMatchesActionIdentity } from './action-identity.js';
10
+ import { normalizeOutputs } from './evidence.js';
10
11
  import { taskSpecificActionBrief } from './workflow.js';
11
12
  import { buildMainlineProjection } from './mainline-projection.js';
12
13
 
@@ -60,7 +61,7 @@ function projectCurrentActionSummary(action, projectedAction = action) {
60
61
  };
61
62
  }
62
63
 
63
- const BOARD_ACTION_STATUSES = ['completed', 'running', 'ready', 'waiting', 'failed'];
64
+ const BOARD_ACTION_STATUSES = ['completed', 'closed', 'running', 'ready', 'waiting', 'failed'];
64
65
 
65
66
  function boardActionCounts(actions) {
66
67
  const counts = Object.fromEntries(BOARD_ACTION_STATUSES.map(status => [status, 0]));
@@ -610,6 +611,8 @@ function projectAction(action, runs, events, includeBody = true) {
610
611
  requiredRole: action.requiredRole || '',
611
612
  generation: Math.max(1, count(action.generation) || 1),
612
613
  replacesActionId: action.replacesActionId || null,
614
+ closeReason: action.closeReason || null,
615
+ closedAt: count(action.closedAt),
613
616
  brief: projectedBrief,
614
617
  status: action.status,
615
618
  assignedVp,
@@ -784,14 +787,17 @@ function sanitizeMainlineDiagnostic(value, maxBytes) {
784
787
  .replace(/(?<![:/])\/(?:[^/\s"'<>]+\/)*[^/\s"'<>]+/g, '[path redacted]');
785
788
  }
786
789
 
787
- function projectCanonicalEvidence(value) {
790
+ function projectCanonicalEvidence(value, options = {}) {
788
791
  if (!Array.isArray(value)) return [];
789
792
  return value.slice(0, 20).map(item => {
790
793
  if (typeof item === 'string') return sanitizeMainlineDiagnostic(item, 1_000);
791
794
  if (!item || typeof item !== 'object') return null;
792
795
  const projected = {};
793
796
  for (const key of ['kind', 'label', 'ref', 'status']) {
794
- if (typeof item[key] === 'string') projected[key] = sanitizeMainlineDiagnostic(item[key], 1_000);
797
+ if (typeof item[key] !== 'string') continue;
798
+ projected[key] = options.preserveRef === true && key === 'ref'
799
+ ? truncateUtf8(item[key], 1_000)
800
+ : sanitizeMainlineDiagnostic(item[key], 1_000);
795
801
  }
796
802
  return Object.keys(projected).length > 0 ? projected : null;
797
803
  }).filter(Boolean);
@@ -810,7 +816,7 @@ function projectMainlineBrowser(detail) {
810
816
  const attentionActionIds = Array.isArray(detail.attentionActionIds)
811
817
  ? detail.attentionActionIds
812
818
  : nodes.filter(node => ['waiting', 'failed'].includes(node.status)).map(node => node.id);
813
- const counts = Object.fromEntries(['completed', 'running', 'ready', 'waiting', 'failed']
819
+ const counts = Object.fromEntries(['completed', 'closed', 'running', 'ready', 'waiting', 'failed']
814
820
  .map(status => [status, nodes.filter(node => node.status === status).length]));
815
821
  return {
816
822
  contract: {
@@ -848,6 +854,7 @@ function projectMainlineBrowser(detail) {
848
854
  status: result.status,
849
855
  summary: sanitizeMainlineDiagnostic(result.summary, MAX_ACTION_DIAGNOSTIC_CHARS),
850
856
  evidence: projectCanonicalEvidence(result.evidence),
857
+ outputs: projectCanonicalEvidence(normalizeOutputs(result.outputs), { preserveRef: true }),
851
858
  waitingReason: sanitizeDiagnosticText(result.waitingReason, MAX_ACTION_DIAGNOSTIC_CHARS) || null,
852
859
  reviewDecision: typeof result.reviewDecision === 'string'
853
860
  ? truncateUtf8(result.reviewDecision, 256) : null,
@@ -859,6 +866,10 @@ function projectMainlineBrowser(detail) {
859
866
 
860
867
  function waitingReason(detail) {
861
868
  if (typeof detail?.waitingReason === 'string') return detail.waitingReason;
869
+ const coordinatorQuestion = [...(Array.isArray(detail?.messages) ? detail.messages : [])]
870
+ .reverse().find(message => message?.role === 'assistant'
871
+ && message?.decision?.kind === 'request_human')?.decision?.question;
872
+ if (typeof coordinatorQuestion === 'string' && coordinatorQuestion.trim()) return coordinatorQuestion;
862
873
  if (detail?.status !== 'waiting') return '';
863
874
  const waitingEvent = Array.isArray(detail?.events)
864
875
  ? detail.events.find(event => event?.type === 'action.waiting'
@@ -893,6 +904,19 @@ export function projectWorkItemDetail(detail, options = {}) {
893
904
  : detail.events;
894
905
  const mainline = projectMainlineBrowser(detail);
895
906
  const mainlineActionById = new Map((mainline?.actions || []).map(action => [action.id, action]));
907
+ const canonicalOutputs = [];
908
+ const seenOutputs = new Set();
909
+ const runById = new Map((Array.isArray(detail.runs) ? detail.runs : []).map(run => [run.id, run]));
910
+ for (const action of Array.isArray(detail.actions) ? detail.actions : []) {
911
+ const run = action?.resultRunId ? runById.get(action.resultRunId) : null;
912
+ if (!run || run.status !== 'completed') continue;
913
+ for (const output of normalizeOutputs(run.outputs)) {
914
+ const key = `${output.kind}\u0000${output.ref}`;
915
+ if (seenOutputs.has(key)) continue;
916
+ seenOutputs.add(key);
917
+ canonicalOutputs.push({ ...output, actionId: action.id, runId: run.id });
918
+ }
919
+ }
896
920
  const projected = {
897
921
  id: detail.id,
898
922
  revision: detail.revision,
@@ -900,6 +924,11 @@ export function projectWorkItemDetail(detail, options = {}) {
900
924
  ledgerRevision: count(detail.ledgerRevision),
901
925
  coordinatorRevision: count(detail.coordinatorRevision),
902
926
  coordinationMode: detail.coordinationMode || 'legacy',
927
+ outputs: canonicalOutputs.slice(0, 50).map(output => ({
928
+ ...projectCanonicalEvidence([output], { preserveRef: true })[0],
929
+ actionId: truncateUtf8(output.actionId || '', 256) || null,
930
+ runId: truncateUtf8(output.runId || '', 256) || null,
931
+ })).filter(output => output.kind && output.label && output.ref),
903
932
  finalResult: detail.finalResult && typeof detail.finalResult === 'object' ? {
904
933
  summary: truncateUtf8(detail.finalResult.summary || '', MAX_ACTION_MESSAGE_CHARS),
905
934
  acceptanceResults: Array.isArray(detail.finalResult.acceptanceResults)
@@ -911,6 +940,15 @@ export function projectWorkItemDetail(detail, options = {}) {
911
940
  })) : [],
912
941
  evidenceRunIds: Array.isArray(detail.finalResult.evidenceRunIds)
913
942
  ? detail.finalResult.evidenceRunIds.map(String).slice(0, 64) : [],
943
+ outputs: Array.isArray(detail.finalResult.outputs)
944
+ ? detail.finalResult.outputs.slice(0, 50).map(rawOutput => {
945
+ const output = normalizeOutputs([rawOutput])[0];
946
+ if (!output) return null;
947
+ return {
948
+ ...projectCanonicalEvidence([output], { preserveRef: true })[0],
949
+ runId: truncateUtf8(rawOutput?.runId || '', 256) || null,
950
+ };
951
+ }).filter(output => output?.kind && output.label && output.ref) : [],
914
952
  residualRisks: Array.isArray(detail.finalResult.residualRisks)
915
953
  ? detail.finalResult.residualRisks
916
954
  .map(risk => truncateUtf8(risk, MAX_ACTION_MESSAGE_CHARS)).slice(0, 24) : [],
@@ -933,6 +971,8 @@ export function projectWorkItemDetail(detail, options = {}) {
933
971
  ? sumExecutionStats(detail.runs)
934
972
  : executionStats(detail.executionStats),
935
973
  reuseMemory: detail.reuseMemory !== false,
974
+ deliveryTarget: ['workspace_files', 'pull_request', 'merge'].includes(detail.deliveryTarget)
975
+ ? detail.deliveryTarget : null,
936
976
  waitingReason: sanitizeDiagnosticText(waitingReason(detail), MAX_ACTION_DIAGNOSTIC_CHARS),
937
977
  failureReason: workItemFailureReason(detail),
938
978
 
@@ -955,6 +995,7 @@ export function projectWorkItemDetail(detail, options = {}) {
955
995
  .includes(message.decision.kind)
956
996
  ? message.decision.kind : null,
957
997
  reason: truncateUtf8(message.decision.reason || '', MAX_ACTION_DIAGNOSTIC_CHARS),
998
+ question: truncateUtf8(message.decision.question || '', MAX_ACTION_DIAGNOSTIC_CHARS) || null,
958
999
  changedContract: message.decision.changedContract === true,
959
1000
  affectedActionIds: Array.isArray(message.decision.affectedActionIds)
960
1001
  ? message.decision.affectedActionIds.map(id => String(id)).slice(0, 8) : [],
@@ -4,6 +4,8 @@ import { defineTool } from '../tools/types.js';
4
4
  import { allTools } from '../tools/index.js';
5
5
  import { parsePatch } from '../tools/apply-patch.js';
6
6
  import { defaultRegistry } from '../vp/registry.js';
7
+ import { createVp } from '../vp/vp-crud.js';
8
+ import { loadVpFromDir } from '../vp/vp-store.js';
7
9
  import { createTrace } from '../debug-trace.js';
8
10
  import { isPathInsideOrEqual } from '../tools/path-safety.js';
9
11
  import { resolveWorkItemModel, selectWorkItemVp } from './assignment.js';
@@ -41,7 +43,7 @@ import {
41
43
  MAX_REPLAN_ADDED_ACTIONS,
42
44
  } from './plan-mutation.js';
43
45
  import { normalizeContractPatch, validateCompletedResult } from './completion-contract.js';
44
- import { normalizeEvidence } from './evidence.js';
46
+ import { normalizeEvidence, normalizeOutputs } from './evidence.js';
45
47
  import {
46
48
  MAINLINE_CONTEXT_HARD_LIMIT_BYTES,
47
49
  buildMainlineContextSnapshot,
@@ -268,18 +270,18 @@ function assertToolInput(toolName, input, workDir, attachmentFiles) {
268
270
  return next;
269
271
  }
270
272
 
271
- export function workItemToolPolicySnapshot(workDir, attachmentRefs = [], mcpToolNames = []) {
273
+ export function workItemToolPolicySnapshot(workDir, attachmentRefs = [], extraToolNames = []) {
272
274
  const hasAttachments = attachmentRefs.length > 0;
273
275
  const builtInTools = WORK_ITEM_TOOL_NAMES.filter(name => !hasAttachments || name !== 'Bash');
274
276
  return {
275
277
  policyVersion: 1,
276
- allowedToolNames: [...builtInTools, ...mcpToolNames],
278
+ allowedToolNames: [...builtInTools, ...extraToolNames],
277
279
  readRoots: [workDir],
278
280
  attachmentRefs,
279
281
  writeRoots: [workDir],
280
282
  shell: { enabled: !hasAttachments, fixedCwd: workDir, background: false, sandboxed: false },
281
283
  async: false,
282
- mcpTools: [...mcpToolNames],
284
+ mcpTools: extraToolNames.filter(name => name.startsWith('mcp__')),
283
285
  };
284
286
  }
285
287
 
@@ -329,6 +331,69 @@ function wrapWorkItemTool(tool, canonicalDir, canonicalAttachmentFiles, isRunAct
329
331
  };
330
332
  }
331
333
 
334
+ export function createWorkItemVpTool({ yeaftDir, registry, isRunActive }) {
335
+ return defineTool({
336
+ name: 'CreateWorkItemVp',
337
+ description: 'Create one persistent specialist VP in this Agent instance after the Coordinator assigned this VP-authoring Action. Use a narrow role and persona for the missing capability; do not clone an existing VP or create a general-purpose replacement.',
338
+ parameters: {
339
+ type: 'object',
340
+ additionalProperties: false,
341
+ required: ['vpId', 'displayName', 'role', 'area', 'traits', 'persona'],
342
+ properties: {
343
+ vpId: { type: 'string', minLength: 1, maxLength: 64 },
344
+ displayName: { type: 'string', minLength: 1, maxLength: 120 },
345
+ displayNameZh: { type: 'string', maxLength: 120 },
346
+ description: { type: 'string', maxLength: 500 },
347
+ descriptionZh: { type: 'string', maxLength: 500 },
348
+ role: { type: 'string', minLength: 1, maxLength: 200 },
349
+ roleZh: { type: 'string', maxLength: 200 },
350
+ area: { type: 'string', minLength: 1, maxLength: 64 },
351
+ traits: { type: 'array', minItems: 1, maxItems: 20, uniqueItems: true, items: { type: 'string', minLength: 1, maxLength: 80 } },
352
+ modelHint: { type: 'string', enum: ['primary', 'fast'] },
353
+ persona: { type: 'string', minLength: 1, maxLength: 12_000 },
354
+ },
355
+ },
356
+ async execute(input) {
357
+ if (!isRunActive()) throw new Error('Work Center Run is no longer active');
358
+ if (!yeaftDir) throw new Error('Work Center VP creation requires the current Agent data directory');
359
+ const libDir = path.join(yeaftDir, 'virtual-persons');
360
+ const { vpId, dir } = createVp(input, {
361
+ libDir,
362
+ memoryRoot: path.join(yeaftDir, 'memory'),
363
+ });
364
+ const vp = loadVpFromDir(dir);
365
+ if (!vp) throw new Error(`Created Work Center VP could not be loaded: ${vpId}`);
366
+ registry?.setVp?.(vp);
367
+ return JSON.stringify({ created: true, vpId });
368
+ },
369
+ isConcurrencySafe: () => false,
370
+ isReadOnly: () => false,
371
+ sideEffectScope: 'external',
372
+ });
373
+ }
374
+
375
+ function assertCreateVpActionAuthority(workItem, action, registry) {
376
+ if (action?.type !== 'create_vp') return;
377
+ if (action.workspaceMode === 'read') {
378
+ const error = new Error('create_vp Action cannot use read workspace mode because VP creation mutates Agent-global state');
379
+ error.retryable = false;
380
+ throw error;
381
+ }
382
+ const assignmentPolicy = action.assignmentPolicy;
383
+ const assignedVpIds = assignmentPolicy?.mode === 'planned'
384
+ ? assignmentPolicy.candidateVpIds || []
385
+ : [];
386
+ if (!isDynamicWorkItem(workItem)
387
+ || action.creationSource !== 'dynamic_coordinator'
388
+ || assignedVpIds.length !== 1
389
+ || !String(assignmentPolicy?.assignmentReason || '').trim()
390
+ || !registry?.getVp?.(assignedVpIds[0])) {
391
+ const error = new Error('create_vp Action lacks dynamic Coordinator provenance and one explicit existing VP assignment');
392
+ error.retryable = false;
393
+ throw error;
394
+ }
395
+ }
396
+
332
397
  export function planningVpCatalog(vps) {
333
398
  return vps.map(vp => ({
334
399
  id: vp.id,
@@ -350,7 +415,7 @@ export function createSubmitWorkItemPlanTool({
350
415
  }) {
351
416
  const vpCatalog = planningVpCatalog(vps);
352
417
  const vpIds = vpCatalog.map(vp => vp.id);
353
- const actionTypes = BUILT_IN_ACTION_TYPES.filter(type => type !== 'triage');
418
+ const actionTypes = BUILT_IN_ACTION_TYPES.filter(type => !['triage', 'create_vp'].includes(type));
354
419
  const catalogDescription = `Action types: ${actionTypes.join(', ')}. Available VPs: ${vpCatalog.map(vp => `${vp.id} (${vp.role || vp.area || 'VP'}; ${vp.traits.join(', ') || 'no traits'})`).join('; ')}.`;
355
420
  return defineTool({
356
421
  name: 'SubmitWorkItemPlan',
@@ -445,7 +510,7 @@ function plannedActionSchema(vpIds, { requireCandidates = true } = {}) {
445
510
  if (requireCandidates) required.push('candidateVpIds', 'assignmentReason');
446
511
  return { type: 'object', additionalProperties: false, required, properties: {
447
512
  id: { type: 'string', minLength: 1, maxLength: 64 }, name: { type: 'string', minLength: 1, maxLength: 120 },
448
- type: { type: 'string', enum: BUILT_IN_ACTION_TYPES.filter(type => type !== 'triage') }, capability: { type: 'string', maxLength: 64 },
513
+ type: { type: 'string', enum: BUILT_IN_ACTION_TYPES.filter(type => !['triage', 'create_vp'].includes(type)) }, capability: { type: 'string', maxLength: 64 },
449
514
  objective: { type: 'string', minLength: 1, maxLength: 2_000 }, approach: { type: 'string', minLength: 1, maxLength: 2_000 }, expectedOutcome: { type: 'string', minLength: 1, maxLength: 2_000 },
450
515
  candidateVpIds: { type: 'array', minItems: 1, uniqueItems: true, items: { type: 'string', enum: vpIds } }, assignmentReason: { type: 'string', minLength: 1, maxLength: 1_000 },
451
516
  dependsOnActionIds: { type: 'array', uniqueItems: true, items: { type: 'string' } }, workspaceMode: { type: 'string', enum: ['read', 'isolated-write', 'integrate', 'shared'] },
@@ -631,6 +696,7 @@ export function parseStructuredResult(text, actionType) {
631
696
  outcome: parsed.outcome,
632
697
  summary: String(parsed.summary || ''),
633
698
  evidence: Array.isArray(parsed.evidence) ? parsed.evidence : [],
699
+ outputs: normalizeOutputs(parsed.outputs),
634
700
  waitingReason: parsed.waitingReason ? String(parsed.waitingReason) : null,
635
701
  error: parsed.error ? String(parsed.error) : null,
636
702
  reviewDecision: ['approved', 'changes_requested'].includes(parsed.reviewDecision)
@@ -689,10 +755,11 @@ function completionContract(action, workItem) {
689
755
  "outcome": "completed|waiting|retryable|failed",
690
756
  "summary": "short result",
691
757
  "evidence": ["test, PR, file, or other verifiable evidence"],
758
+ "outputs": [{ "kind": "file|link|pr|commit", "label": "user-facing output name", "ref": "safe relative path for file, safe HTTP(S) URL for link/pr, or commit hash/full refs/... name for commit; never relabel a URL as file/commit" }],
692
759
  "acceptanceChecks": ${JSON.stringify(acceptanceChecks)},
693
760
  "waitingReason": null,
694
761
  "error": null${reviewField}${triageField}${planField}
695
- }\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, deferred, or not_applicable and a non-empty evidence reference. Triage must use its proposed criteria when submitting a contractPatch. An intermediate Action may defer criteria outside its task-specific expected result; the final deliver Action, and an approved review with no downstream work, require every criterion to pass. If a criterion is no longer applicable, ask the WorkItem Coordinator to revise the contract instead of pretending it passed. This is a deterministic submission gate, not independent proof: later verification and delivery Actions must verify the claims. A model turn ending is not completion. Use waiting when user or external input is required. Use retryable only for a transient failure. Do not start background jobs or delegate this Action.`;
762
+ }\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, deferred, or not_applicable and a non-empty evidence reference. Report every user-consumable file, URL, PR, or commit in outputs; evidence proves work, while outputs tell the user where the deliverable is. Triage must use its proposed criteria when submitting a contractPatch. An intermediate Action may defer criteria outside its task-specific expected result; the final deliver Action, and an approved review with no downstream work, require every criterion to pass. If a criterion is no longer applicable, ask the WorkItem Coordinator to revise the contract instead of pretending it passed. This is a deterministic submission gate, not independent proof: later verification and delivery Actions must verify the claims. A model turn ending is not completion. Use waiting when user or external input is required. Use retryable only for a transient failure. Do not start background jobs or delegate this Action.`;
696
763
  }
697
764
 
698
765
  function safeCheckpointUrl(value) {
@@ -1023,6 +1090,7 @@ export class WorkItemRunner {
1023
1090
  }
1024
1091
 
1025
1092
  async run({ workItem, action, run, signal, ownerBootId, onProgress, registerProgressReader, registerInputWake, onEngineEvent = null }) {
1093
+ assertCreateVpActionAuthority(workItem, action, this.registry);
1026
1094
  const runtime = await this.runtimeProvider();
1027
1095
  const currentSettings = ['ai', 'coordinator'].includes(workItem?.workflowSnapshot?.planningMode)
1028
1096
  && this.policyProvider ? await this.policyProvider() : null;
@@ -1106,6 +1174,13 @@ export class WorkItemRunner {
1106
1174
  && workItem?.workflowSnapshot?.planningMode === 'ai'
1107
1175
  && !replanToolEnabled;
1108
1176
  const runTools = [];
1177
+ if (executionAction.type === 'create_vp') {
1178
+ runTools.push(createWorkItemVpTool({
1179
+ yeaftDir: runtime.yeaftDir || this.yeaftDir,
1180
+ registry: this.registry,
1181
+ isRunActive,
1182
+ }));
1183
+ }
1109
1184
  if (planToolEnabled) runTools.push(createSubmitWorkItemPlanTool({
1110
1185
  vps: this.registry.listVps(),
1111
1186
  workItem,
@@ -259,6 +259,12 @@ export class WorkCenterService {
259
259
  coordinationMode: DYNAMIC_COORDINATION_MODE,
260
260
  executionSchemaVersion: DYNAMIC_EXECUTION_SCHEMA_VERSION,
261
261
  workDir,
262
+ // Creation-time delivery authority comes only from an explicit
263
+ // browser/user request. Trusted model producers may provide
264
+ // Session provenance, but cannot grant themselves delivery rights.
265
+ deliveryTarget: requestContext.userOriginated === true
266
+ && ['workspace_files', 'pull_request', 'merge'].includes(payload.deliveryTarget)
267
+ ? payload.deliveryTarget : null,
262
268
  reuseMemory: payload.reuseMemory !== false,
263
269
  origin: payload.origin && typeof payload.origin === 'object'
264
270
  ? {