@mogragsigma/dsh-dual-host-plugin 0.33.3 → 2.0.0

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 (98) hide show
  1. package/README.md +68 -97
  2. package/bundle/README.md +68 -97
  3. package/bundle/SETUP.md +87 -0
  4. package/bundle/approval-chat.js +2 -2
  5. package/bundle/approval-feishu.js +4 -1
  6. package/bundle/approval-ledger.js +30 -13
  7. package/bundle/approval-workbench.js +2 -0
  8. package/bundle/artifact-media.js +2 -2
  9. package/bundle/compatibility.json +47 -1
  10. package/bundle/configuration-v2.json +15 -0
  11. package/bundle/delivery-state.js +2 -0
  12. package/bundle/development-gate.js +2 -0
  13. package/bundle/distribution-lock.json +18 -18
  14. package/bundle/gateway-maintenance.js +1 -0
  15. package/bundle/global-rules/AGENTS.md +24 -20
  16. package/bundle/index.js +135 -25
  17. package/bundle/intervention-state.js +1 -1
  18. package/bundle/link-contract.js +9 -4
  19. package/bundle/managed-closeout.js +45 -0
  20. package/bundle/managed-cloud-policy.js +19 -0
  21. package/bundle/managed-command.js +71 -0
  22. package/bundle/managed-development-contract.js +43 -0
  23. package/bundle/managed-development.js +86 -0
  24. package/bundle/managed-endpoint.js +282 -0
  25. package/bundle/managed-execution.js +207 -0
  26. package/bundle/managed-policy.js +20 -0
  27. package/bundle/managed-runtime.js +186 -0
  28. package/bundle/managed-search.js +57 -0
  29. package/bundle/managed-tools.js +17 -0
  30. package/bundle/managed-triggers.js +85 -0
  31. package/bundle/native-context.js +4 -2
  32. package/bundle/native-entry.js +6 -2
  33. package/bundle/native-human-context.js +29 -0
  34. package/bundle/native-model-choice.js +12 -0
  35. package/bundle/native-rules/continuity.md +13 -0
  36. package/bundle/native-rules/execution.md +36 -0
  37. package/bundle/native-tools.js +52 -19
  38. package/bundle/notification-artifacts.js +26 -10
  39. package/bundle/notification-endpoint.js +1 -1
  40. package/bundle/notification-feishu.js +2 -2
  41. package/bundle/notification-media-endpoint.js +4 -3
  42. package/bundle/notification-observations.js +45 -0
  43. package/bundle/onboarding-cloud-package.js +1 -1
  44. package/bundle/onboarding-cloud.js +1 -1
  45. package/bundle/onboarding-file-contract.js +3 -2
  46. package/bundle/onboarding-file-transfer.js +4 -4
  47. package/bundle/onboarding-native-probe.js +1 -1
  48. package/bundle/onboarding-runtime-activation.js +1 -1
  49. package/bundle/onboarding-runtime-extension.js +2 -2
  50. package/bundle/package-lock.json +18 -18
  51. package/bundle/package.json +16 -14
  52. package/bundle/peer-file-review.js +1 -1
  53. package/bundle/peer-files.js +8 -4
  54. package/bundle/recovery.js +1 -1
  55. package/bundle/repository-gitee.js +12 -0
  56. package/bundle/repository-managed.js +1 -0
  57. package/bundle/runtime-im-lock.json +3409 -2849
  58. package/bundle/runtime-im-package.json +5 -5
  59. package/bundle/runtime-lock.json +3409 -2849
  60. package/bundle/runtime-package.json +5 -5
  61. package/bundle/schedule-ledger.js +35 -0
  62. package/bundle/search-contract.js +11 -1
  63. package/bundle/skills/task-approval/SKILL.md +20 -1
  64. package/bundle/skills/task-development/SKILL.md +30 -13
  65. package/bundle/skills/task-direct-link/SKILL.md +39 -13
  66. package/bundle/skills/task-file-transfer/SKILL.md +2 -2
  67. package/bundle/skills/task-notify/SKILL.md +2 -0
  68. package/bundle/skills/task-repository/SKILL.md +10 -1
  69. package/bundle/skills/task-research/SKILL.md +17 -2
  70. package/bundle/source-assessment.js +38 -0
  71. package/bundle/source-gitee.js +52 -0
  72. package/bundle/source-intake.js +137 -0
  73. package/bundle/source-policy-envelope.js +29 -0
  74. package/bundle/source-routing-tools.js +12 -0
  75. package/bundle/source-routing.js +218 -0
  76. package/bundle/source-search.js +2 -1
  77. package/bundle/store.js +6 -5
  78. package/bundle/task-artifacts.js +51 -12
  79. package/bundle/task-board.js +8 -4
  80. package/bundle/task-catalog.js +4 -3
  81. package/bundle/task-context.js +2 -2
  82. package/bundle/task-continuity-tools.js +14 -0
  83. package/bundle/task-continuity.js +248 -0
  84. package/bundle/task-envelope.js +31 -20
  85. package/bundle/task-notifications.js +6 -2
  86. package/bundle/task-observer-model.js +25 -0
  87. package/bundle/task-observer.js +120 -0
  88. package/bundle/task-results.js +1 -1
  89. package/bundle/task-scheduler.js +3 -3
  90. package/bundle/task-trigger-time.js +17 -12
  91. package/bundle/task-triggers.js +5 -30
  92. package/bundle/task-worker.js +10 -2
  93. package/bundle/task-workflows.js +2 -1
  94. package/bundle/workbench-client.js +165 -30
  95. package/bundle/workbench-decisions.js +22 -5
  96. package/bundle/worker-tools.js +8 -1
  97. package/compatibility.json +47 -1
  98. package/package.json +13 -13
package/bundle/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ import {validateAutonomousSearch} from './search-contract.js';
2
+ import {autonomous,CLOUD_TASK_POLICY} from './managed-cloud-policy.js';
3
+ import {TaskObserver,validateObserver} from './task-observer.js';
4
+ import {ManagedExecution,executionSpec} from './managed-execution.js';
5
+ import {ManagedEndpoint,validateManagedConfig} from './managed-endpoint.js';
6
+ import {resolveManagedModel} from './managed-runtime.js';
7
+ import {realpathSync} from 'node:fs';
8
+ import {TaskContinuity,ContinuityOutbox} from './task-continuity.js';
9
+ import {SourceRouting} from './source-routing.js';
10
+ import {GiteeIssueSources} from './source-gitee.js';
11
+ import {SourceIntake,assertSourceDatabasePath} from './source-intake.js';
1
12
  import {taskResults} from './task-results.js';
2
13
  import {ApprovalRelayBroker,LocalApprovalEndpoint,configureLocalApproval} from './approval-local-relay.js';
3
14
  import {feishuNotificationTarget} from './notification-feishu.js';
@@ -15,7 +26,7 @@ import {DevelopmentRuntime} from './development-runtime.js';
15
26
  import {TaskSources} from './task-sources.js';
16
27
  import {TaskArtifacts} from './task-artifacts.js';
17
28
  import {createServer} from 'node:http';
18
- import {mkdirSync,readFileSync,appendFileSync,lstatSync} from 'node:fs';
29
+ import {mkdirSync,existsSync,readFileSync,appendFileSync,lstatSync} from 'node:fs';
19
30
  import {join} from 'node:path';
20
31
  import {timingSafeEqual,randomUUID} from 'node:crypto';
21
32
  import {DatabaseSync} from 'node:sqlite';
@@ -56,7 +67,8 @@ export class Gateway {
56
67
  this.retry={maxAttempts:16,baseMs:2000,maxMs:300000,sendTimeoutMs:15000,...(c.recovery??{})};
57
68
  for(const [k,v] of Object.entries(this.retry))if(!Number.isSafeInteger(v)||v<1||v>3600000)throw Error('INVALID_RECOVERY_CONFIG');
58
69
  if(!['local','cloud'].includes(c.role)||!c.stateDir||!c.tokenFile&&!io.secrets)throw Error('INVALID_DEPLOYMENT');
59
- validateA2AConfig(c.a2a);
70
+ validateA2AConfig(c.a2a);validateManagedConfig(c.managedExecution);validateObserver(c.observation);
71
+ if(c.observation?.enabled&&c.managedExecution?.enabled!==true)throw Error('TASK_OBSERVER_EXECUTION_REQUIRED');
60
72
  if(c.a2a?.enabled&&c.peerEnabled===false)throw Error('A2A_PEER_REQUIRED');
61
73
  if(c.legacyFeishu&&c.imDelivery)throw Error('MULTIPLE_NOTIFICATION_CONSUMERS');
62
74
  if(io.secrets)this.secrets=structuredClone(io.secrets);
@@ -65,6 +77,20 @@ export class Gateway {
65
77
  if(c.role==='cloud'&&equal(this.secrets.localToken,this.secrets.cloudToken))throw Error('DISTINCT_ROLE_TOKENS_REQUIRED');
66
78
  if(c.entryMode==='native'&&Object.keys(c.repositoryBindings??{}).length)throw Error('LEGACY_REPOSITORY_WRITER_DISABLED');
67
79
  const repositoryProvider=c.role==='cloud'?(c.repositoryAccess?.managedOnboarding===true?new ManagedGiteeReadOnly(c.repositoryAccess,io.repository):new GiteeReadOnly(c.repositoryAccess,io.repository)):null;
80
+ if(c.sourceIntake?.enabled===true)assertSourceDatabasePath(c.stateDir);
81
+ if(c.role==='cloud'&&c.continuity?.enabled!==true&&existsSync(join(c.stateDir,'coordinator/tasks.sqlite'))){
82
+ const existing=new DatabaseSync(join(c.stateDir,'coordinator/tasks.sqlite'),{readOnly:true});let managed=false;
83
+ try{managed=!!existing.prepare("SELECT 1 FROM sqlite_master WHERE name='task_continuity'").get()&&!!existing.prepare('SELECT 1 FROM task_continuity LIMIT 1').get();}finally{existing.close();}
84
+ if(managed)throw Error('TASK_CONTINUITY_REQUIRED'); // Do not disable a durable pause/stop fence by toggling configuration.
85
+ }
86
+ for(const [file,table,enabled] of [
87
+ ['coordinator/tasks.sqlite','managed_executions',c.managedExecution?.enabled===true],
88
+ ['gateway.sqlite','managed_endpoint_owner',c.managedExecution?.enabled===true&&!!c.managedExecution.endpoint],
89
+ ])if(!enabled&&existsSync(join(c.stateDir,file))){
90
+ const existing=new DatabaseSync(join(c.stateDir,file),{readOnly:true});let managed=false;
91
+ try{managed=!!existing.prepare('SELECT 1 FROM sqlite_master WHERE name=?').get(table)&&!!existing.prepare('SELECT 1 FROM '+table+' LIMIT 1').get();}finally{existing.close();}
92
+ if(managed)throw Error('TASK_EXECUTION_REQUIRED');
93
+ }
68
94
  mkdirSync(c.stateDir,{recursive:true,mode:0o700});
69
95
  this.trace=(type,data={})=>appendFileSync(join(c.stateDir,'events.jsonl'),JSON.stringify({type,...data,at:this.now()})+'\n',{mode:0o600});
70
96
  this.db=new DatabaseSync(join(c.stateDir,'gateway.sqlite'));
@@ -80,11 +106,11 @@ export class Gateway {
80
106
  this.peerLastError=null;
81
107
  try{if(c.fileTransfers?.enabled)this.files=new PeerFiles(this,c.fileTransfers);}catch(e){this.db.close();throw e;}
82
108
  this.writer=new RepositoryWriter(c.repositoryBindings??{});
83
- if(c.role==='cloud'){this.store=new Coordinator(join(c.stateDir,'coordinator'),this.writer);this.catalog=new TaskCatalog(this.store);this.repositories=new RepositoryCatalog(this.store,this.catalog,repositoryProvider,this.now,id=>this.assertTaskQuiescent(id));this.approvals=new ApprovalLedger(this.store,this.catalog,this.now,this.repositories);if(c.localApproval){this.approvalRelay=new ApprovalRelayBroker(this.store.db,c.localApproval,{now:this.now});this.chatApproval=new ChatApproval(this.approvals,{...c.localApproval,appSecretEnv:'LOCAL_RELAY_NO_SECRET',hostRole:'local',semantic:{enabled:true,...c.localApproval.semantic}},{classify:semanticClassifier(ctx,c.localApproval.semantic),source:this.approvalRelay.source()});}else if(c.chatApproval)this.chatApproval=new ChatApproval(this.approvals,c.chatApproval,{classify:semanticClassifier(ctx,c.chatApproval.semantic),...io.chatApproval});else this.store.db.prepare('UPDATE approval_chat_bindings SET disabled=1').run();this.approvalWindow=new ApprovalWindow();this.workbenchApprovals=new WorkbenchApprovals(this.approvals,()=>this.chatApproval);this.workbenchDecisions=new WorkbenchDecisions(this.approvals,()=>this.chatApproval,{hold:()=>this.maintenance.enter('workbench_interaction')});}
84
- if(c.role==='cloud'&&c.scheduler)this.scheduler=new TaskScheduler(ctx,{...c.scheduler,canTick:()=>!this.maintenance.blocked,stateDir:join(c.stateDir,'scheduler'),validateSchedule:s=>this.assertScheduleCurrent(s),admit:job=>{if(this.store.task(job.task_id).blocked)throw Error('TASK_BLOCKED');const e=parseEnvelope(job.goal),h=this.catalog.head(job.task_id);if(e&&(e.task_context?.spec_revision??1)!==h.spec_revision)throw Error('TASK_REVISION_CONFLICT');this.sources?.searcher.admit(job);}},io.scheduler);
109
+ if(c.role==='cloud'){this.store=new Coordinator(join(c.stateDir,'coordinator'),this.writer);this.catalog=new TaskCatalog(this.store);this.repositories=new RepositoryCatalog(this.store,this.catalog,repositoryProvider,this.now,id=>this.assertTaskQuiescent(id));this.approvals=new ApprovalLedger(this.store,this.catalog,this.now,this.repositories);if(c.localApproval){this.approvalRelay=new ApprovalRelayBroker(this.store.db,c.localApproval,{now:this.now});this.chatApproval=new ChatApproval(this.approvals,{...c.localApproval,appSecretEnv:'LOCAL_RELAY_NO_SECRET',hostRole:'local',semantic:{enabled:true,...c.localApproval.semantic}},{classify:semanticClassifier(ctx,c.localApproval.semantic),source:this.approvalRelay.source()});}else if(c.chatApproval)this.chatApproval=new ChatApproval(this.approvals,c.chatApproval,{classify:semanticClassifier(ctx,c.chatApproval.semantic),...io.chatApproval});else this.store.db.prepare('UPDATE approval_chat_bindings SET disabled=1').run();this.approvalWindow=new ApprovalWindow();this.workbenchApprovals=new WorkbenchApprovals(this.approvals,()=>this.chatApproval);this.workbenchDecisions=new WorkbenchDecisions(this.approvals,()=>this.chatApproval,{hold:()=>this.maintenance.enter('workbench_interaction'),nativeDecision:c.continuity?.enabled===true?(node,a)=>this.continuity.decide(node,a):undefined});}
110
+ if(c.role==='cloud'&&c.scheduler)this.scheduler=new TaskScheduler(ctx,{...c.scheduler,canTick:()=>!this.maintenance.blocked,stateDir:join(c.stateDir,'scheduler'),validateSchedule:s=>this.assertScheduleCurrent(s),admit:job=>{this.continuity?.assertActive(job.task_id);if(this.store.task(job.task_id).blocked)throw Error('TASK_BLOCKED');const e=parseEnvelope(job.goal),h=this.catalog.head(job.task_id);if(e&&(e.task_context?.spec_revision??1)!==h.spec_revision)throw Error('TASK_REVISION_CONFLICT');this.sources?.searcher.admit(job);}},io.scheduler);
85
111
  if(this.store){this.sources=new TaskSources(this,c.readonlySources??{});this.artifacts=new TaskArtifacts(this);if(this.scheduler?.worker){this.scheduler.worker.artifacts=this.artifacts;this.scheduler.worker.sources=this.sources;}}
86
112
  if(c.development?.enabled){this.development=new DevelopmentRuntime(this,c.development);this.scheduler.executors.set('development',{replaySafe:false,run:(j,s)=>this.development.run(j,s)});this.scheduler.c.canClaim=j=>j.kind==='development'?this.development.canClaim(j):!this.store.task(j.task_id).busy;this.scheduler.c.afterRecover=()=>this.development.recover();this.scheduler.c.afterFinish=j=>{if(j.kind==='development')this.development.finished(j);};this.scheduler.c.afterFailure=j=>{if(j.kind==='development')this.development.failed(j);};}
87
- if(this.scheduler){const admit=this.scheduler.c.canClaim;this.scheduler.c.canClaim=j=>!this.closed&&(admit?.(j)??true);}
113
+ if(this.scheduler){const admit=this.scheduler.c.canClaim;this.scheduler.c.canClaim=j=>!this.closed&&!this.execution?.owns(j.task_id)&&(this.continuity?.canRun(j.task_id)??true)&&(admit?.(j)??true);}
88
114
  if(this.store){this.delivery=new DeliveryPreview(this);this.approvals.delivery=this.delivery;}
89
115
  if(c.role==='cloud'&&c.delivery?.enabled===true)this.deliveryRuntime=new DeliveryRuntime(this,c.delivery,io.delivery);
90
116
  this.notifications=new TaskNotifications(this,c.notificationTargets??[]);
@@ -100,10 +126,27 @@ export class Gateway {
100
126
  this.trace('model-request',{agent:agent.id,provider:result.provider,model:result.model});
101
127
  return {...result,maxTokens:2048};
102
128
  },{prepend:true}); // Validate the final UI selection, even after a gateway hot reload.
129
+ if(c.continuity?.enabled===true){
130
+ this.continuityOutbox=new ContinuityOutbox(this);
131
+ if(this.store)this.continuity=new TaskContinuity(this);
132
+ if(this.scheduler){this.scheduler.worker.continuity=this.continuity;this.scheduler.c.beforeTick=()=>this.continuity.reconcileAll();this.scheduler.c.taskCanRun=id=>this.continuity.canRun(id)&&!this.execution?.owns(id);}
133
+ }
134
+ if(c.managedExecution?.enabled===true){
135
+ if(c.continuity?.enabled!==true)throw Error('TASK_CONTINUITY_REQUIRED');
136
+ if(this.store)this.execution=new ManagedExecution(this);
137
+ if(c.role==='cloud'&&c.managedExecution.endpoint)this.executionEndpoint=new ManagedEndpoint(this,c.managedExecution.endpoint,io.managedExecution);
138
+ }
139
+ if(c.observation?.enabled&&this.store)this.observer=new TaskObserver(this,c.observation);
140
+ this.sourceIntake=new SourceIntake(this.db,{enabled:()=>c.sourceIntake?.enabled===true,
141
+ resolveSource:id=>{const sources=c.sourceIntake?.sources;if(!Array.isArray(sources)||sources.length>128)return null;const found=sources.filter(s=>s?.id===id);return found.length===1?(this.sourceRouting?.config(found[0])??found[0]):null;},
142
+ now:this.now,run:(label,fn)=>this.maintenance.run(label,fn)});
143
+ if(this.store&&this.execution){this.sourceRouting=new SourceRouting(this,io.sourceRouting);this.sourceTransports=new GiteeIssueSources(this);}
103
144
  ctx.effect(()=>()=>this.close());
104
145
  }
105
146
  isManaged(agent){return this.c.entryMode!=='native'&&!!agent&&this.ctx.agentPresets.composedPreset(agent.ctx)==='dsh-task-intake';}
106
- assertNativeAgent(agent){if(this.ctx.agents.get(agent?.id)!==agent||!['standard','ptc','cordis',...(this.c.nativePresets??[])].includes(this.ctx.agentPresets.composedPreset(agent.ctx)))throw Error('UNBOUND_NATIVE_SESSION');}
147
+ // Session ownership is persisted before agents.create announces agent/created;
148
+ // its live binding is assigned only after that announcement returns.
149
+ assertNativeAgent(agent){if(this.executionEndpoint?.owned?.(agent?.id)||this.executionEndpoint?.binding(agent)||this.ctx.agents.get(agent?.id)!==agent||!['standard','ptc','cordis',...(this.c.nativePresets??[])].includes(this.ctx.agentPresets.composedPreset(agent.ctx)))throw Error('UNBOUND_NATIVE_SESSION');}
107
150
  assertAgent(agent){if(this.ctx.agents.get(agent?.id)!==agent||!this.isManaged(agent))throw Error('UNBOUND_ENTRY_SESSION');}
108
151
  assertScheduleCurrent(s){
109
152
  const h=this.catalog.head(s.task_id);if(this.store.task(s.task_id).blocked)throw Error('TASK_BLOCKED');
@@ -116,11 +159,18 @@ export class Gateway {
116
159
  const single=db.prepare('SELECT 1 FROM jobs WHERE trigger_id=?').get('task-plan:'+task_id+':'+revision);
117
160
  if(family!=='workflow'&&workflow||family!=='trigger'&&trigger||family!=='single'&&single)throw Error('SCHEDULER_DISPATCH_FAMILY_CONFLICT');
118
161
  }
119
- assertTaskQuiescent(id){if(this.deliveryRuntime?.busy(id)||this.development?.busy(id)||this.a2a?.busy(id)||this.scheduler?.workflows.list(id).some(w=>!['completed','cancelled'].includes(w.status))||this.scheduler?.triggers.list(id).some(s=>!['closed','cancelled'].includes(s.status))||this.store.task(id).busy||this.scheduler?.jobs.list(id).some(j=>!['completed','cancelled'].includes(j.status))||[...(this.scheduler?.active.values()??[])].some(r=>r.task_id===id))throw Error('TASK_NOT_QUIESCENT');}
162
+ assertTaskQuiescent(id){if(this.execution?.busy(id)||this.deliveryRuntime?.busy(id)||this.development?.busy(id)||this.a2a?.busy(id)||this.scheduler?.workflows.list(id).some(w=>!['completed','cancelled'].includes(w.status))||this.scheduler?.triggers.list(id).some(s=>!['closed','cancelled'].includes(s.status))||this.store.task(id).busy||this.scheduler?.jobs.list(id).some(j=>!['completed','cancelled'].includes(j.status))||[...(this.scheduler?.active.values()??[])].some(r=>r.task_id===id))throw Error('TASK_NOT_QUIESCENT');}
120
163
  async call(method,args,node=this.c.role){return this.maintenance.run('rpc',()=>this.callCore(method,args,node));}
121
164
  async callCore(method,args,node=this.c.role){
122
165
  await this.io.authorize?.();
123
166
  if(typeof method!=='string'||!Object.hasOwn(METHODS,method))throw Error('METHOD_DENIED');
167
+ if(this.continuity){
168
+ const dispatch=['trigger_create','workflow_create','development_schedule','workspace_development_schedule','delivery_schedule','delivery_push_schedule','a2a_create'];
169
+ if(dispatch.includes(method)&&args.task_id)this.continuity.assertActive(args.task_id);
170
+ if(method==='a2a_create'&&this.continuity.managed(args.task_id))throw Error('TASK_EXECUTION_ADAPTER_UNAVAILABLE'); // Managed native A2A is R3; old tasks retain their own contract.
171
+ if(['trigger_control','workflow_control'].includes(method)&&args.action==='resume')this.continuity.assertActive((method==='trigger_control'?(this.execution?.schedules.has(args.id)?this.execution.schedules:this.scheduler.triggers):this.scheduler.workflows).get(args.id).task_id);
172
+ if(method==='job_input')this.continuity.assertActive(this.scheduler.jobs.get(args.job_id).task_id);
173
+ }
124
174
  if(method==='onboarding_existing_repository'){
125
175
  if(!args||typeof args!=='object'||Array.isArray(args)||Object.keys(args).join()!=='url')throw Error('INVALID_ARGUMENTS');
126
176
  if(this.c.role!=='cloud'||!this.repositories?.provider?.inspect)throw Error('CAPABILITY_UNAVAILABLE');
@@ -143,14 +193,47 @@ export class Gateway {
143
193
  if(method.startsWith('workbench_')){if(!this.workbench)throw Error('CAPABILITY_UNAVAILABLE');return this.workbench.call(method,args);}
144
194
  if(method==='diagnostics')return this.diagnostics(args?.refresh!==false); // No remote arbitrary writer/admin/probe.
145
195
  if(method.startsWith('a2a_')){if(!this.c.a2a?.enabled)throw Error('CAPABILITY_UNAVAILABLE');if(this.a2a)return this.a2a.call(method,args,node);}
196
+ if(this.store&&['source_routing_status','source_routing_control'].includes(method)){
197
+ if(!this.sourceRouting)throw Error('CAPABILITY_UNAVAILABLE');
198
+ return method==='source_routing_status'?this.sourceRouting.status(args):this.sourceRouting.control(node,args);
199
+ }
200
+ if(this.store&&method.startsWith('execution_')){
201
+ if(!this.execution)throw Error('CAPABILITY_UNAVAILABLE');
202
+ if(method==='execution_start'||method==='execution_prepare'&&autonomous(parseEnvelope(this.catalog.head(args.task_id).goal))){
203
+ if(args.role!==undefined&&args.role!=='cloud')throw Error('TASK_EXECUTION_CLOUD_ONLY');
204
+ if(['max_requests','active_timeout_ms','ttl_seconds','commands'].some(k=>args[k]!==undefined))throw Error('TASK_EXECUTION_RETIRED_LIMIT');
205
+ const policy=await resolveManagedModel(this.ctx,args.model_policy??parseEnvelope(this.catalog.head(args.task_id).goal)?.model_policy,this.c.managedExecution.defaultModel);
206
+ const search=validateAutonomousSearch(args.search??parseEnvelope(this.catalog.head(args.task_id).goal)?.search);
207
+ if(search&&!search.model_policy){const p=this.c.managedExecution.endpoint?.search?.model_policy;if(!p)throw Error('TASK_SEARCH_DEFAULT_UNAVAILABLE');search.model_policy=p;}
208
+ const spec=executionSpec({execution_policy:CLOUD_TASK_POLICY,role:'cloud',cwd:realpathSync(args.cwd),...(search?{search}:{}),...Object.fromEntries(['end_at','period_seconds','schedule','development'].filter(k=>args[k]!==undefined).map(k=>[k,args[k]]))});
209
+ return this.execution.start({task_id:args.task_id,expected_revision:args.expected_revision,spec,policy});
210
+ }
211
+ if(method==='execution_prepare'){
212
+ if(!this.continuity?.managed(args.task_id))throw Error('TASK_EXECUTION_NATIVE_TASK_REQUIRED');
213
+ this.continuity.assertActive(args.task_id);this.assertTaskQuiescent(args.task_id);
214
+ if(args.role==='local'&&!args.model_policy)throw Error('MODEL_TARGET_ENTRY_REQUIRED');
215
+ const policy=args.role==='cloud'?await resolveManagedModel(this.ctx,args.model_policy,this.c.managedExecution.defaultModel):args.model_policy;
216
+ this.continuity.assertActive(args.task_id);this.assertTaskQuiescent(args.task_id);
217
+ const spec=executionSpec({role:args.role,cwd:args.role==='cloud'?realpathSync(args.cwd):args.cwd,max_requests:args.max_requests??200,active_timeout_ms:args.active_timeout_ms??3600000,...(args.period_seconds?{period_seconds:args.period_seconds}:{}),...(args.search!==undefined?{search:args.search}:{}),...(args.commands!==undefined?{commands:args.commands}:{}),...(args.development!==undefined?{development:args.development}:{})});
218
+ const old=this.execution.get(args.task_id);if(old&&(old.role!==spec.role||JSON.parse(old.spec).cwd!==spec.cwd))throw Error('TASK_EXECUTION_SESSION_BINDING_CHANGED');
219
+ if(old&&spec.max_requests<this.execution.budgets.view(args.task_id).used)throw Error('TASK_RUNTIME_BUDGET_BELOW_USED');
220
+ if(typeof args.scope!=='string'||!args.scope.trim()||args.scope.length>3000)throw Error('TASK_EXECUTION_SCOPE_INVALID');
221
+ return this.approvals.request(node,{task_id:args.task_id,expected_revision:args.expected_revision,event_id:args.event_id,ttl_seconds:args.ttl_seconds??7*86400,proposal:{summary:'原生持续执行 · '+(args.role==='cloud'?'云端':'本地'),changes:args.scope+'\n执行合同:'+JSON.stringify(spec)+'\n'+(args.role==='cloud'?'云端账号文件权限;cwd是工作起点,不是文件沙箱。':'仅获准本地任务副本;权限不因云端放宽而扩大。'),validation:'核对实际成果和相关验证,报告整体进展;后台工作结束才释放名额。',risks:'仅按执行合同交付;未约定的提交/推送/PR不授权;不含合并、发布部署、历史改写、大范围不可逆删除、系统/服务/凭据变更或敏感外发。CLI仍依赖执行账号与平台权限,策略不是完整文件沙箱。',rollback:'暂停/停止会围栏新操作并排空,已经发生的文件变更不自动回滚。',model_policy:policy,scopes:['develop'],repository:null,execution:spec}});
222
+ }
223
+ return this.execution.call(method,args,node);
224
+ }
146
225
  if(this.store){
226
+ if(args.task_id&&['schedule','workflow_create','trigger_create','a2a_create','a2a_start','development_schedule','workspace_development_schedule','approval_request'].includes(method)&&autonomous(parseEnvelope(this.catalog.head(args.task_id).goal)))throw Error('TASK_EXECUTION_DIRECT_PATH_REQUIRED');
147
227
  if(method.startsWith('approval_local_')){if(!this.approvalRelay)throw Error('CAPABILITY_UNAVAILABLE');return this.approvalRelay.call(node,method.slice('approval_local_'.length),args);}
148
228
  if(method.startsWith('intervention_action_'))return this.interventionActions.call(node,method.slice('intervention_action_'.length),args);
149
229
  if(method.startsWith('approval_interaction_'))return this.workbenchDecisions.call(node,method.slice('approval_interaction_'.length),args);
150
230
  if(method.startsWith('approval_workbench_')){const service=this.workbenchApprovals;if(!service){if(method==='approval_workbench_describe')return {enabled:false,reason:'APPROVAL_WORKBENCH_DISABLED'};throw Error('APPROVAL_WORKBENCH_DISABLED');}return service.call(node,method.slice('approval_workbench_'.length),args);}
231
+ if(['development_schedule','workspace_development_schedule','delivery_prepare','delivery_schedule','delivery_push_prepare','delivery_push_schedule'].includes(method)&&this.continuity?.managed(args.task_id))throw Error('TASK_EXECUTION_ADAPTER_UNAVAILABLE');
151
232
  if(method.startsWith('development_bridge_')||method.startsWith('workspace_bridge_')){if(!this.development?.bridge)throw Error('CAPABILITY_UNAVAILABLE');return this.development.bridge.call(method,args,node);}
233
+ if(['schedule','workflow_create','trigger_create','a2a_create','development_schedule','workspace_development_schedule'].includes(method)&&args.task_id&&this.execution?.owns(args.task_id))throw Error('TASK_EXECUTION_CONTRACT_ALREADY_EXISTS');
152
234
  if(['schedule','workflow_create','trigger_create','development_schedule','workspace_development_schedule'].includes(method)&&this.a2a?.busy(args.task_id))throw Error('A2A_TASK_BUSY');
153
235
  if(method.startsWith('notification_'))return this.notifications.call(method,args,node);
236
+ if(method==='source_capabilities'&&this.c.managedExecution?.enabled){await this.executionEndpoint?.search?.ready;return {schema:1,host:'cloud',execution_policy:CLOUD_TASK_POLICY,scope:'host-account and user task restrictions; cwd is not a sandbox',default_model:this.c.managedExecution.defaultModel??null,capabilities:{files:{mode:'native-host-account'},search:{enabled:!!this.executionEndpoint?.search?.reader,configured:!!this.c.managedExecution.endpoint?.search,model_policy:this.c.managedExecution.endpoint?.search?.model_policy??null,max_requests:null}},grant:false};}
154
237
  if(method==='source_capabilities')return {schema:1,host:'cloud',capabilities:this.sources.capabilities(args),worker_enabled:this.scheduler?.started===true&&!this.scheduler.closed,scope:'explicit current task references intersect operator read roots',grant:false,model_policy:'per-task; inventory never selects or validates a model'};
155
238
  if(method==='delivery_push_prepare'||method==='delivery_push_schedule'){if(!this.deliveryRuntime)throw Error('CAPABILITY_UNAVAILABLE');return method==='delivery_push_prepare'?this.deliveryRuntime.stagePush(node,args):this.deliveryRuntime.schedulePush(node,args);}
156
239
  if(method==='delivery_prepare'||method==='delivery_schedule'){if(!this.deliveryRuntime)throw Error('CAPABILITY_UNAVAILABLE');return method==='delivery_prepare'?this.deliveryRuntime.stage(node,args):this.deliveryRuntime.schedule(node,args);}
@@ -190,9 +273,11 @@ export class Gateway {
190
273
  return workflows.create({task_id:task.id,revision:h.spec_revision,goal:h.goal,policy:args.policy,nodes:args.nodes,max_total_runs:args.max_total_runs,end_at:args.end_at,event_id:args.event_id,source});
191
274
  }
192
275
  if(method.startsWith('trigger_')){
193
- if(!this.scheduler)throw Error('SCHEDULER_UNAVAILABLE');
194
- const triggers=this.scheduler.triggers;
195
- if(method==='trigger_list'){this.store.task(args.task_id);return {items:triggers.list(args.task_id),authority:'cloud',development:false};}
276
+ if(!this.scheduler&&!this.execution?.schedules)throw Error('SCHEDULER_UNAVAILABLE');
277
+ const managed=this.execution?.schedules;const triggers=args.id&&managed?.has(args.id)?managed:this.scheduler?.triggers;
278
+ if(method==='trigger_list'){this.store.task(args.task_id);return {items:[...(this.scheduler?.triggers.list(args.task_id)??[]),...(managed?.list(args.task_id)??[])],authority:'cloud',development:false};}
279
+ if(!triggers)throw Error('SCHEDULER_NOT_FOUND');
280
+ if(method==='trigger_control'&&triggers===managed)throw Error('SCHEDULER_USE_TASK_LIFECYCLE');
196
281
  if(method==='trigger_control')return triggers.control({...args,actor:'authenticated-'+node+'-entry'});
197
282
  const source=validSources(args.sources);if(!source.length||source.some(s=>s.role!==node))throw Error('CONTEXT_ORIGIN_MISMATCH');
198
283
  if(method==='trigger_signal'){
@@ -215,17 +300,28 @@ export class Gateway {
215
300
  if(method==='repository_bind'||method==='repository_unbind')return this.repositories.mutate(node,args,method==='repository_unbind');
216
301
  if(method==='repository_status')return args.refresh?this.repositories.refresh(args.task_id):this.repositories.status(args.task_id);
217
302
  if(method==='repository_gate')return this.repositories.gate(args.task_id,args,this.approvals);
218
- if(method==='approval_request'){const r=this.approvals.request(node,args);return {...r,chat_confirmation:this.chatApproval?.queue(r.id)??null,confirmation_url:null};}
303
+ if(method==='approval_request'){const r=this.approvals.request(node,args);return {...r,chat_confirmation:this.approvals.isNativeTask(r.task_id)?null:this.chatApproval?.queue(r.id)??null,confirmation_url:null};}
219
304
  if(method==='approval_reference')return approvalReference(this,args);
220
- if(method==='approval_status')return {items:this.approvals.list(args.task_id).map(r=>({...r,chat_confirmation:this.chatApproval?.delivery(r.id)??null})),confirmation_url:null,execution:this.development?'requires_runtime_gate':'disabled'};
305
+ if(method==='approval_status')return {items:this.approvals.list(args.task_id).map(r=>({...r,chat_confirmation:this.approvals.isNativeTask(r.task_id)?null:this.chatApproval?.delivery(r.id)??null})),confirmation_url:null,execution:this.development?'requires_runtime_gate':'disabled'};
306
+ if(METHODS[method]==='continuity'){
307
+ if(!this.continuity)throw Error('CAPABILITY_UNAVAILABLE');
308
+ if(method==='task_decide')return this.continuity.decide(node,args);
309
+ if(method==='task_resolve')return this.continuity.resolve(args);
310
+ if(method==='task_followups')return this.continuity.read(args.task_id,args);
311
+ if(method==='task_followup')return this.continuity.receive(node,args);
312
+ if(method==='task_lifecycle')return this.continuity.control(node,args);
313
+ if(method==='task_followup_reroute')return this.continuity.reroute(node,args);
314
+ return this.continuity.unlink(node,args);
315
+ }
221
316
  if(method==='catalog'){const r=this.catalog.find(args);return {...r,items:r.items.map(t=>({...t,jobs:this.scheduler?.jobs.list(t.task_id).slice(-5)??[]})),authority:'cloud'};}
222
- if(method==='task_amend')return this.catalog.amend(node,args,id=>this.assertTaskQuiescent(id));
223
- if(method==='status')return {...this.store.status(args.task_id),catalog:this.catalog.view(args.task_id),repository:this.repositories.status(args.task_id),approvals:this.approvals.list(args.task_id),...(this.scheduler?{jobs:this.scheduler.jobs.list(args.task_id),schedules:this.scheduler.triggers.list(args.task_id),workflows:this.scheduler.workflows.list(args.task_id)}:{})};
317
+ if(method==='task_amend'){if(this.continuity&&['stopped','resuming'].includes(this.continuity.state(args.task_id).state))throw Error('TASK_CONTROL_NOT_ALLOWED');return this.catalog.amend(node,args,id=>this.assertTaskQuiescent(id));}
318
+ if(method==='status')return {...this.store.status(args.task_id),...(this.execution?{managed_execution:this.execution.view(args.task_id),...(this.observer?{task_observation:this.observer.view(args.task_id)}:{})}:{}),...(this.continuity?{continuity:this.continuity.controlStatus(args.task_id)}:{}),catalog:this.catalog.view(args.task_id),repository:this.repositories.status(args.task_id),approvals:this.approvals.list(args.task_id),...(this.scheduler?{jobs:this.scheduler.jobs.list(args.task_id),schedules:[...this.scheduler.triggers.list(args.task_id),...(this.execution?.schedules.list(args.task_id)??[])],workflows:this.scheduler.workflows.list(args.task_id)}:{})};
224
319
  if(method==='inspect'){const task=this.store.db.prepare('SELECT id FROM tasks WHERE task_key=?').get(args.task_key);if(!task)throw Error('TASK_NOT_FOUND');return this.call('status',{task_id:task.id},node);}
225
320
  if(method==='schedule'){
226
321
  if(!this.scheduler)throw Error('SCHEDULER_UNAVAILABLE');
227
322
  const task=args.task_id?this.store.task(args.task_id):this.store.db.prepare('SELECT * FROM tasks WHERE task_key=?').get(args.task_key);
228
- if(!task)throw Error('TASK_NOT_FOUND');if(task.blocked)throw Error('NEEDS_REVIEW');
323
+ if(!task)throw Error('TASK_NOT_FOUND');this.continuity?.assertActive(task.id);if(task.blocked)throw Error('NEEDS_REVIEW');
324
+ if(this.execution?.owns(task.id))throw Error('TASK_EXECUTION_CONTRACT_ALREADY_EXISTS');
229
325
  if(this.a2a?.busy(task.id))throw Error('A2A_TASK_BUSY');
230
326
  if(this.scheduler.triggers.list(task.id).some(s=>!['closed','cancelled'].includes(s.status)))throw Error('SCHEDULER_ALREADY_EXISTS');
231
327
  if(this.scheduler.workflows.list(task.id).some(w=>!['completed','cancelled'].includes(w.status)))throw Error('WORKFLOW_ALREADY_EXISTS');
@@ -241,10 +337,12 @@ export class Gateway {
241
337
  }
242
338
  if(method==='job_control'){
243
339
  if(!this.scheduler||!['pause','resume','cancel'].includes(args.action))throw Error('METHOD_DENIED');
340
+ if(args.action==='resume')this.continuity?.assertActive(this.scheduler.jobs.get(args.job_id).task_id);
244
341
  return this.scheduler.control({id:args.job_id,action:args.action,event_id:args.event_id,actor:'authenticated-'+node+'-entry'});
245
342
  }
246
- const incoming=parseEnvelope(args.goal);if(incoming&&incoming.origin.role!==node)throw Error('CONTEXT_ORIGIN_MISMATCH');
247
- const t=this.store.accept(node,args);
343
+ const incoming=parseEnvelope(args.goal);if(incoming?.origin.kind==='policy')throw Error('SOURCE_POLICY_INTERNAL_ONLY');if(incoming&&incoming.origin.role!==node)throw Error('CONTEXT_ORIGIN_MISMATCH');
344
+ if(incoming?.continuity===1&&!this.continuity)throw Error('CAPABILITY_UNAVAILABLE');
345
+ const t=this.store.accept(node,args,t=>{this.catalog.ensure(t.id);this.continuity?.register(t,incoming);});
248
346
  if(incoming?.notification_contract===1)this.notifications.track(t.id);
249
347
  // Status is queued intake, not a claim that a worker has run.
250
348
  const h=this.catalog.head(t.id);return {...t,title:h.title,spec_revision:h.spec_revision,execution:'pending_dispatch'};
@@ -253,6 +351,9 @@ export class Gateway {
253
351
  }
254
352
  hello(){
255
353
  const capabilities={...CAPABILITIES};
354
+ if(this.c.managedExecution?.enabled!==true)delete capabilities.source_routing;
355
+ if(this.c.continuity?.enabled!==true)delete capabilities.continuity;
356
+ if(this.c.managedExecution?.enabled!==true)delete capabilities.cloud_autonomy;
256
357
  if(this.c.role==='cloud'&&!this.repositories?.provider?.inspect)delete capabilities.onboarding_existing_repository;
257
358
  if(this.c.role==='cloud'&&!this.onboardingInspection)delete capabilities.onboarding_existing;
258
359
  if(!this.files)delete capabilities.peer_files;
@@ -267,7 +368,8 @@ export class Gateway {
267
368
  if(this.c.role==='cloud'&&!this.development?.bridge)delete capabilities.development_workspace_bridge;
268
369
  if(!this.workbench)delete capabilities.workbench;
269
370
  if(!this.board){delete capabilities.taskboard;delete capabilities.task_results;}
270
- if(this.c.role==='cloud'){if(!this.peer)delete capabilities.peer;if(!this.scheduler){delete capabilities.scheduler;delete capabilities.triggers;delete capabilities.workflows;}}
371
+ if(this.c.managedExecution?.enabled!==true){delete capabilities.managed_execution;delete capabilities.managed_development;delete capabilities.managed_triggers;}
372
+ if(this.c.role==='cloud'){if(!this.peer)delete capabilities.peer;if(!this.scheduler){delete capabilities.scheduler;if(!this.execution)delete capabilities.triggers;delete capabilities.workflows;}}
271
373
  return {schema:1,protocol:{min:1,max:1},plugin_version:VERSION,role:this.c.role,namespace:this.c.namespace,instance_id:this.instanceId,capabilities};
272
374
  }
273
375
  observation(){return {authority:this.c.role,instance_id:this.instanceId,generated_at:this.now(),freshness:'live_response',age_ms:0,usable_for_authorization:false};}
@@ -307,7 +409,7 @@ export class Gateway {
307
409
  async submitCore(args){
308
410
  await this.io.authorize?.();
309
411
  if(this.closed)throw Error('GATEWAY_CLOSED');
310
- if(typeof args.goal!=='string'||!args.goal.trim()||args.goal.length>12000||!(/^[a-z0-9-]{1,64}$/).test(args.task_key)||typeof args.ingress_id!=='string'||!args.ingress_id||args.ingress_id.length>256||!['desktop','native-local','native-cloud',...(this.legacy?['feishu-local','feishu-cloud']:[])].includes(args.source))throw Error('INVALID_SUBMISSION');
412
+ if(typeof args.goal!=='string'||!args.goal.trim()||args.goal.length>1048576||!(/^[a-z0-9-]{1,64}$/).test(args.task_key)||typeof args.ingress_id!=='string'||!args.ingress_id||args.ingress_id.length>256||!['desktop','native-local','native-cloud',...(this.legacy?['feishu-local','feishu-cloud']:[])].includes(args.source))throw Error('INVALID_SUBMISSION');
311
413
  const fp=hash(JSON.stringify(args));
312
414
  this.db.prepare("INSERT OR IGNORE INTO submissions(id,fingerprint,payload,status,created_at) VALUES (?,?,?,'queued',?)").run(args.ingress_id,fp,JSON.stringify(args),this.now());
313
415
  const row=this.db.prepare('SELECT * FROM submissions WHERE id=?').get(args.ingress_id);
@@ -360,6 +462,11 @@ export class Gateway {
360
462
  this.tickPending=this.tickOnce().finally(()=>{this.tickPending=null;});return this.tickPending;
361
463
  }
362
464
  async tickOnce(){
465
+ await this.continuityOutbox?.tick();
466
+ this.continuity?.reconcileAll();this.execution?.reconcile();
467
+ // Do not block peer controls/heartbeats behind semantic assessment.
468
+ this.sourceTransports?.tick().catch(e=>this.trace('source-transport-error',{code:errorInfo(e).code}));
469
+ this.sourceRouting?.tick().catch(e=>this.trace('source-routing-error',{code:errorInfo(e).code}));
363
470
  this.deliveryRuntime?.tick();
364
471
  this.a2a?.reap(); // Expire authority even when the recipient is offline; never dispatch here.
365
472
  if(this.scheduler)for(const n of this.scheduler.jobs.db.prepare('SELECT id,text FROM job_notifications WHERE delivered=0 LIMIT 10').all()){
@@ -376,7 +483,7 @@ export class Gateway {
376
483
  await this.notifications?.tick();
377
484
  await this.chatApproval?.tick();
378
485
  }
379
- snapshot(){return {protocol:this.hello(),connection:this.link?.snapshot()??this.inboundSnapshot(),diagnostics:this.localDiagnostics(),scheduler:this.scheduler?.snapshot()??null,version:VERSION,approval:{chat:this.chatApproval?.snapshot()??null,window:false,identity_policy:'existing-im-only',execution:'disabled'},role:this.c.role,channelStatus:this.legacy?.status()??null,legacyFeishu:!!this.legacy,imDelivery:this.imDelivery?.snapshot()??null,files:this.files?.snapshot()??null,peer:this.peer?{...this.peer.snapshot(),lastError:this.peerLastError}:null,entryMode:this.c.entryMode??'legacy-test',managedPreset:this.c.entryMode==='native'?null:'dsh-task-intake',agents:this.ctx.agents.list().map(a=>({id:a.id,status:a.status,managed:this.isManaged(a)})),submissions:this.db.prepare('SELECT id,status,task_id,attempts,next_attempt_at,last_error FROM submissions').all(),notices:this.db.prepare('SELECT id,status,message_id,last_error FROM notices').all(),receipts:this.db.prepare('SELECT id,status,message_id,last_error FROM receipts').all(),a2a:this.db.prepare('SELECT id FROM a2a').all(),repositoryBindings:this.writer.bindings.size,repositoryAssociations:this.repositories?this.store.db.prepare("SELECT COUNT(*) AS count FROM repository_associations WHERE state<>'unbound'").get().count:null,sendNotices:!!this.c.sendNotices};}
486
+ snapshot(){return {...(this.executionEndpoint?{managed_execution_endpoint:this.executionEndpoint.snapshot()}:{}),...(this.execution?{managed_execution_slots:this.execution.slots.snapshot()}:{}),protocol:this.hello(),connection:this.link?.snapshot()??this.inboundSnapshot(),diagnostics:this.localDiagnostics(),scheduler:this.scheduler?.snapshot()??null,version:VERSION,approval:{chat:this.chatApproval?.snapshot()??null,window:false,identity_policy:this.c.continuity?.enabled===true?'task-native-with-legacy-im':'existing-im-only',execution:'disabled'},role:this.c.role,channelStatus:this.legacy?.status()??null,legacyFeishu:!!this.legacy,imDelivery:this.imDelivery?.snapshot()??null,files:this.files?.snapshot()??null,peer:this.peer?{...this.peer.snapshot(),lastError:this.peerLastError}:null,entryMode:this.c.entryMode??'legacy-test',managedPreset:this.c.entryMode==='native'?null:'dsh-task-intake',agents:this.ctx.agents.list().map(a=>({id:a.id,status:a.status,managed:this.isManaged(a)})),submissions:this.db.prepare('SELECT id,status,task_id,attempts,next_attempt_at,last_error FROM submissions').all(),notices:this.db.prepare('SELECT id,status,message_id,last_error FROM notices').all(),receipts:this.db.prepare('SELECT id,status,message_id,last_error FROM receipts').all(),a2a:this.db.prepare('SELECT id FROM a2a').all(),repositoryBindings:this.writer.bindings.size,repositoryAssociations:this.repositories?this.store.db.prepare("SELECT COUNT(*) AS count FROM repository_associations WHERE state<>'unbound'").get().count:null,sendNotices:!!this.c.sendNotices};}
380
487
  async start(){
381
488
  await this.sources?.ready;
382
489
  if(this.scheduler)await this.scheduler.start();
@@ -393,11 +500,11 @@ export class Gateway {
393
500
  if(req.url==='/health'){if(!own)throw Error('AUTH_DENIED');value=this.snapshot();}
394
501
  else{
395
502
  leave=this.maintenance.enter('http');
396
- let raw='';for await(const chunk of req){raw+=chunk;if(Buffer.byteLength(raw)>131072)throw Error('BODY_TOO_LARGE');}
503
+ let raw='';for await(const chunk of req){raw+=chunk;if(Buffer.byteLength(raw)>2097152)throw Error('BODY_TOO_LARGE');}
397
504
  const body=JSON.parse(raw);if(!body.args||typeof body.args!=='object'||Array.isArray(body.args))throw Error('INVALID_ARGUMENTS');
398
505
  if(body.method==='accept'){
399
506
  // Validate peer RPC as strictly as native submissions; identity is bound to token.
400
- if(typeof body.args.goal!=='string'||!body.args.goal.trim()||body.args.goal.length>12000)throw Error('INVALID_GOAL');
507
+ if(typeof body.args.goal!=='string'||!body.args.goal.trim()||body.args.goal.length>1048576)throw Error('INVALID_GOAL');
401
508
  }
402
509
  if(body.method==='negotiate'){
403
510
  if(this.c.role!=='cloud')throw Error('PROTOCOL_ROLE_MISMATCH');
@@ -423,20 +530,23 @@ export class Gateway {
423
530
  const loop=async()=>{try{await this.tick();}catch{this.trace('tick-error');}if(!this.closed)this.timer=setTimeout(()=>{this.pending=loop();},10000);};this.pending=loop();
424
531
  if(this.c.role==='local'&&this.c.ssh){this.connector=new SshConnector(this.c.ssh,this.trace,this.io.ssh);this.connector.start();}
425
532
  if(this.a2aEndpoint)await this.a2aEndpoint.start();
426
- this.localApprovalEndpoint?.start();
533
+ if(this.executionEndpoint)await this.executionEndpoint.start();
534
+ this.localApprovalEndpoint?.start();this.observer?.start();
427
535
  this.trace('gateway-active',{role:this.c.role,port:this.c.port});
428
536
  }
429
537
  close(){
430
538
  if(this.closing)return this.closing;
431
- this.closed=true;clearTimeout(this.timer);
539
+ this.closed=true;const transportClosed=this.sourceTransports?.close(),routingClosed=this.sourceRouting?.close();this.sourceIntake.close();clearTimeout(this.timer);
432
540
  const serverClosed=this.server?.listening?new Promise(resolve=>{this.server.close(resolve);this.server.closeIdleConnections();}):Promise.resolve();
433
541
  this.closing=(async()=>{
542
+ await transportClosed;await routingClosed;await this.observer?.close();
434
543
  this.approvalRelay?.close();
435
544
  await this.localApprovalEndpoint?.close();
436
545
  await this.deliveryRuntime?.close();
437
546
  await this.developmentPeer?.close();
438
547
  await this.a2aEndpoint?.close();
439
- const settled=await Promise.allSettled([this.pending,this.tickPending,...this.deliveries.values(),this.connector?.close(),this.scheduler?.close(),this.workbenchDecisions?.close(),this.chatApproval?.close(),serverClosed]);
548
+ await this.executionEndpoint?.close();
549
+ const settled=await Promise.allSettled([this.pending,this.tickPending,...this.deliveries.values(),...(this.continuityOutbox?.pending.values()??[]),this.connector?.close(),this.scheduler?.close(),this.workbenchDecisions?.close(),this.chatApproval?.close(),serverClosed]);
440
550
  await this.notifications?.close();await this.artifacts?.close();
441
551
  this.maintenance.completeClosure(()=>{this.store?.close();this.db.close();},settled.every(x=>x.status==='fulfilled'));
442
552
  })();return this.closing;
@@ -37,7 +37,7 @@ export function interventionFacts(item,now){
37
37
  const active=out.some(x=>x.lifecycle==='open'&&!x.historical);
38
38
  // Aggregate fallback cannot hide an unsupported state behind an empty list.
39
39
  const info=guidance('task',item.display_state,'',false);
40
- if(info&&!active)add('task',{id:'state',spec_revision:revision},item.display_state);
40
+ if(info&&!active&&!item.task_observation)add('task',{id:'state',spec_revision:revision},item.display_state);
41
41
  else add('task',{id:'state',spec_revision:revision},'inactive');
42
42
  return out;
43
43
  }
@@ -1,7 +1,7 @@
1
1
  // Application protocol, independent of plugin semver, IM transports and task authority.
2
- export const VERSION='0.33.3';
3
- export const CAPABILITIES=Object.freeze({artifact_download:1,task_results:1,intervention_actions:1,approval_local:1,approval_interaction:1,task_interventions:1,approval_workbench:2,onboarding_existing_repository:1,onboarding_existing:1,notification_default_peer:1,notification_defaults:1,schedule_budget:1,peer_files:1,model_selection:1,approval_reference:1,delivery_push_plan:1,delivery_push:1,delivery_commit_plan:1,delivery_commit:1,delivery_review:1,delivery_preview:1,development_workspace_bridge:1,development_workspace:1,development_claim:1,development_review:1,development_bridge:1,a2a:2,notification_media:2,notification_review:1,notification_discovery:1,notifications:1,search:1,sources:1,intake:1,catalog:1,approval:1,repository:1,peer:1,scheduler:1,triggers:1,workflows:1,diagnostics:1,workbench:1,taskboard:2,artifacts:2,development:2});
4
- export const METHODS=Object.freeze({artifact_download:'artifact_download',task_results:'task_results',intervention_action_inspect:'intervention_actions',intervention_action_prepare:'intervention_actions',intervention_action_confirm:'intervention_actions',intervention_action_status:'intervention_actions',approval_local_validate:'approval_local',approval_local_pull:'approval_local',approval_local_ack:'approval_local',approval_interaction_describe:'approval_interaction',approval_interaction_prepare:'approval_interaction',approval_interaction_confirm:'approval_interaction',approval_interaction_status:'approval_interaction',approval_interaction_identity_send:'approval_interaction',approval_interaction_identity_check:'approval_interaction',approval_interaction_identity_close:'approval_interaction',board_interventions:'task_interventions',approval_workbench_describe:'approval_workbench',approval_workbench_prepare:'approval_workbench',approval_workbench_begin:'approval_workbench',approval_workbench_finish:'approval_workbench',approval_workbench_status:'approval_workbench',onboarding_existing_repository:'onboarding_existing_repository',onboarding_existing_inspect:'onboarding_existing',notification_default_peer_discover:'notification_default_peer',notification_default_peer_candidate:'notification_default_peer',notification_default_publish:'notification_defaults',notification_default_status:'notification_defaults',peer_file_offer:'peer_files',peer_file_pull:'peer_files',peer_file_chunk:'peer_files',peer_file_commit:'peer_files',peer_file_read:'peer_files',peer_file_ack:'peer_files',peer_file_status:'peer_files',model_resolve:'model_selection',approval_reference:'approval_reference',delivery_push_prepare:'delivery_push_plan',delivery_push_schedule:'delivery_push',delivery_prepare:'delivery_commit_plan',delivery_schedule:'delivery_commit',delivery_file_page:'delivery_review',delivery_preview:'delivery_preview',delivery_status:'delivery_preview',workspace_bridge_enqueue:'development_workspace_bridge',workspace_bridge_cancel:'development_workspace_bridge',workspace_bridge_status:'development_workspace_bridge',workspace_bridge_claim_acquire:'development_workspace_bridge',workspace_bridge_claim_receipt:'development_workspace_bridge',workspace_bridge_read:'development_workspace_bridge',workspace_bridge_write:'development_workspace_bridge',workspace_bridge_command:'development_workspace_bridge',workspace_bridge_finish:'development_workspace_bridge',workspace_bridge_fail:'development_workspace_bridge',workspace_bridge_evidence:'development_workspace_bridge',workspace_bridge_review:'development_workspace_bridge',workspace_development_schedule:'development_workspace',development_bridge_claim_acquire:'development_claim',development_bridge_claim_receipt:'development_claim',development_bridge_evidence:'development_review',development_bridge_review:'development_review',development_bridge_cancel:'development_bridge',development_bridge_enqueue:'development_bridge',development_bridge_acquire:'development_bridge',development_bridge_read:'development_bridge',development_bridge_write:'development_bridge',development_bridge_test:'development_bridge',development_bridge_finish:'development_bridge',development_bridge_fail:'development_bridge',development_bridge_status:'development_bridge',a2a_create:'a2a',a2a_start:'a2a',a2a_list:'a2a',a2a_control:'a2a',a2a_claim:'a2a',a2a_begin:'a2a',a2a_validate:'a2a',a2a_reserve:'a2a',a2a_finish:'a2a',a2a_failed:'a2a',notification_artifact_send:'notification_media',notification_media_pull:'notification_media',notification_media_materialize:'notification_media',notification_media_validate:'notification_media',notification_media_wait:'notification_media',notification_artifact_access:'notification_media',notification_reconcile:'notification_review',notification_discover:'notification_discovery',notification_target_add:'notification_discovery',notification_target_publish:'notification_discovery',notification_targets:'notifications',notification_bind:'notifications',notification_status:'notifications',notification_pull:'notifications',notification_validate:'notifications',notification_ack:'notifications',notification_wait:'notifications',source_capabilities:'sources',workflow_create:'workflows',workflow_list:'workflows',workflow_control:'workflows',job_input:'triggers',trigger_create:'triggers',trigger_list:'triggers',trigger_control:'triggers',trigger_signal:'triggers',development_schedule:'development',artifact_list:'artifacts',artifact_register_gitee:'artifacts',artifact_access:'artifacts',accept:'intake',status:'catalog',inspect:'catalog',catalog:'catalog',task_amend:'catalog',approval_request:'approval',approval_status:'approval',repository_resolve:'repository',repository_bind:'repository',repository_status:'repository',repository_unbind:'repository',repository_gate:'repository',peer_send:'peer',peer_pull:'peer',peer_ack:'peer',peer_status:'peer',schedule:'scheduler',job_control:'scheduler',diagnostics:'diagnostics',workbench_status:'workbench',workbench_bootstrap:'workbench',board_list:'taskboard',board_detail:'taskboard'});
2
+ export const VERSION='2.0.0';
3
+ export const CAPABILITIES=Object.freeze({source_routing:1,managed_triggers:1,cloud_autonomy:1,managed_development:1,managed_execution:1,continuity:1,artifact_download:1,task_results:1,intervention_actions:1,approval_local:1,approval_interaction:1,task_interventions:1,approval_workbench:2,onboarding_existing_repository:1,onboarding_existing:1,notification_default_peer:1,notification_defaults:1,schedule_budget:1,peer_files:1,model_selection:1,approval_reference:1,delivery_push_plan:1,delivery_push:1,delivery_commit_plan:1,delivery_commit:1,delivery_review:1,delivery_preview:1,development_workspace_bridge:1,development_workspace:1,development_claim:1,development_review:1,development_bridge:1,a2a:2,notification_media:2,notification_review:1,notification_discovery:1,notifications:1,search:1,sources:1,intake:1,catalog:1,approval:1,repository:1,peer:1,scheduler:1,triggers:1,workflows:1,diagnostics:1,workbench:1,taskboard:2,artifacts:2,development:2});
4
+ export const METHODS=Object.freeze({source_routing_status:'source_routing',source_routing_control:'source_routing',execution_usage:'cloud_autonomy',execution_recover:'cloud_autonomy',execution_start:'cloud_autonomy',notification_media_chunk:'managed_development',execution_evidence:'managed_development',execution_artifact:'managed_development',execution_deliver:'managed_development',execution_suspend:'managed_execution',execution_prepare:'managed_execution',execution_enqueue:'managed_execution',execution_status:'managed_execution',execution_claim:'managed_execution',execution_begin:'managed_execution',execution_validate:'managed_execution',execution_reserve:'managed_execution',execution_inputs:'managed_execution',execution_finish:'managed_execution',task_decide:'continuity',task_followup:'continuity',task_followups:'continuity',task_resolve:'continuity',task_lifecycle:'continuity',task_followup_reroute:'continuity',task_thread_unlink:'continuity',artifact_download:'artifact_download',task_results:'task_results',intervention_action_inspect:'intervention_actions',intervention_action_prepare:'intervention_actions',intervention_action_confirm:'intervention_actions',intervention_action_status:'intervention_actions',approval_local_validate:'approval_local',approval_local_pull:'approval_local',approval_local_ack:'approval_local',approval_interaction_describe:'approval_interaction',approval_interaction_prepare:'approval_interaction',approval_interaction_confirm:'approval_interaction',approval_interaction_status:'approval_interaction',approval_interaction_identity_send:'approval_interaction',approval_interaction_identity_check:'approval_interaction',approval_interaction_identity_close:'approval_interaction',board_interventions:'task_interventions',approval_workbench_describe:'approval_workbench',approval_workbench_prepare:'approval_workbench',approval_workbench_begin:'approval_workbench',approval_workbench_finish:'approval_workbench',approval_workbench_status:'approval_workbench',onboarding_existing_repository:'onboarding_existing_repository',onboarding_existing_inspect:'onboarding_existing',notification_default_peer_discover:'notification_default_peer',notification_default_peer_candidate:'notification_default_peer',notification_default_publish:'notification_defaults',notification_default_status:'notification_defaults',peer_file_offer:'peer_files',peer_file_pull:'peer_files',peer_file_chunk:'peer_files',peer_file_commit:'peer_files',peer_file_read:'peer_files',peer_file_ack:'peer_files',peer_file_status:'peer_files',model_resolve:'model_selection',approval_reference:'approval_reference',delivery_push_prepare:'delivery_push_plan',delivery_push_schedule:'delivery_push',delivery_prepare:'delivery_commit_plan',delivery_schedule:'delivery_commit',delivery_file_page:'delivery_review',delivery_preview:'delivery_preview',delivery_status:'delivery_preview',workspace_bridge_enqueue:'development_workspace_bridge',workspace_bridge_cancel:'development_workspace_bridge',workspace_bridge_status:'development_workspace_bridge',workspace_bridge_claim_acquire:'development_workspace_bridge',workspace_bridge_claim_receipt:'development_workspace_bridge',workspace_bridge_read:'development_workspace_bridge',workspace_bridge_write:'development_workspace_bridge',workspace_bridge_command:'development_workspace_bridge',workspace_bridge_finish:'development_workspace_bridge',workspace_bridge_fail:'development_workspace_bridge',workspace_bridge_evidence:'development_workspace_bridge',workspace_bridge_review:'development_workspace_bridge',workspace_development_schedule:'development_workspace',development_bridge_claim_acquire:'development_claim',development_bridge_claim_receipt:'development_claim',development_bridge_evidence:'development_review',development_bridge_review:'development_review',development_bridge_cancel:'development_bridge',development_bridge_enqueue:'development_bridge',development_bridge_acquire:'development_bridge',development_bridge_read:'development_bridge',development_bridge_write:'development_bridge',development_bridge_test:'development_bridge',development_bridge_finish:'development_bridge',development_bridge_fail:'development_bridge',development_bridge_status:'development_bridge',a2a_create:'a2a',a2a_start:'a2a',a2a_list:'a2a',a2a_control:'a2a',a2a_claim:'a2a',a2a_begin:'a2a',a2a_validate:'a2a',a2a_reserve:'a2a',a2a_finish:'a2a',a2a_failed:'a2a',notification_artifact_send:'notification_media',notification_media_pull:'notification_media',notification_media_materialize:'notification_media',notification_media_validate:'notification_media',notification_media_wait:'notification_media',notification_artifact_access:'notification_media',notification_reconcile:'notification_review',notification_discover:'notification_discovery',notification_target_add:'notification_discovery',notification_target_publish:'notification_discovery',notification_targets:'notifications',notification_bind:'notifications',notification_status:'notifications',notification_pull:'notifications',notification_validate:'notifications',notification_ack:'notifications',notification_wait:'notifications',source_capabilities:'sources',workflow_create:'workflows',workflow_list:'workflows',workflow_control:'workflows',job_input:'triggers',trigger_create:'triggers',trigger_list:'triggers',trigger_control:'triggers',trigger_signal:'triggers',development_schedule:'development',artifact_list:'artifacts',artifact_register_gitee:'artifacts',artifact_access:'artifacts',accept:'intake',status:'catalog',inspect:'catalog',catalog:'catalog',task_amend:'catalog',approval_request:'approval',approval_status:'approval',repository_resolve:'repository',repository_bind:'repository',repository_status:'repository',repository_unbind:'repository',repository_gate:'repository',peer_send:'peer',peer_pull:'peer',peer_ack:'peer',peer_status:'peer',schedule:'scheduler',job_control:'scheduler',diagnostics:'diagnostics',workbench_status:'workbench',workbench_bootstrap:'workbench',board_list:'taskboard',board_detail:'taskboard'});
5
5
  export const compatibilityErrors=new Set(['PROTOCOL_NEGOTIATION_REQUIRED','PROTOCOL_VERSION_INCOMPATIBLE','PROTOCOL_PEER_LEGACY','PROTOCOL_DESCRIPTOR_INVALID','PROTOCOL_NAMESPACE_MISMATCH','PROTOCOL_ROLE_MISMATCH','PROTOCOL_INSTANCE_CHANGED','CAPABILITY_UNAVAILABLE','CAPABILITY_VERSION_INCOMPATIBLE']);
6
6
  export const retryBarrierErrors=new Set([...compatibilityErrors,'PROTOCOL_MAINTENANCE_ACTIVE']);
7
7
  export function errorInfo(error){
@@ -10,7 +10,7 @@ export function errorInfo(error){
10
10
  if(['WORKER_MODEL_OUTPUT_LIMIT','APPROVAL_WORKBENCH_CHANGED','APPROVAL_OPERATION_NOT_READY','APPROVAL_IDENTITY_SOURCE_CHANGED','APPROVAL_INTERPRETATION_INTERRUPTED','APPROVAL_INTERPRETATION_UNAVAILABLE'].includes(input))return {code:input,category:'validation_or_review',retry:'operator_review',does_not_grant_authority:true};
11
11
  const raw=({'account-unverified':'IM_ACCOUNT_UNVERIFIED','capability-unavailable':'IM_CAPABILITY_UNAVAILABLE','account-changed':'IM_ROUTE_REVIEW_REQUIRED','target-changed':'IM_ROUTE_REVIEW_REQUIRED','unknown-target':'IM_ROUTE_REVIEW_REQUIRED','unknown-bot':'IM_ROUTE_REVIEW_REQUIRED','bad-request':'IM_BAD_REQUEST'})[input]??input;
12
12
  const code=raw==='NEEDS_REVIEW'?raw:typeof raw==='string'&&/^(UPDATE_|UNSUPPORTED_|PROTOCOL_|CAPABILITY_|AUTH_|INVALID_|INTERVENTION_|A2A_|SOURCE_|TASK_|APPROVAL_|PASSKEY_|APPROVER_|DEVELOPMENT_|ARTIFACT_|REPOSITORY_|GITEE_|PEER_|IM_|CHAT_|CONTEXT_|REFERENCE_|INGRESS_|MODEL_|NOTIFICATION_|SCHEDULER_|WORKFLOW_|COORDINATOR_|UNSAFE_|UNCONFIRMED_|SEND_|DELIVERY_|LEGACY_|METHOD_|TRANSIENT_|GATEWAY_|NEEDS_REVIEW)[A-Z0-9_]{1,90}$/.test(raw)?raw:'TRANSIENT_FAILURE';
13
- const category=code==='PROTOCOL_MAINTENANCE_ACTIVE'?'maintenance':compatibilityErrors.has(code)?'compatibility':code==='AUTH_DENIED'||code.startsWith('PASSKEY_')||code.startsWith('APPROVER_')?'authentication':/UNKNOWN|UNCERTAIN/.test(code)?'uncertain_delivery':/(?:^|_)INVALID(?:_|$)|RETIRED|CONTRACT_CHANGED|SOURCE_CHANGED|LINK_PENDING|CONFLICT|DENIED|MISMATCH|NOT_FOUND|NEEDS_REVIEW|REVIEW_REQUIRED|BAD_REQUEST|RESPONSE_TOO_LARGE|EXHAUSTED|ROUTE_CHANGED|OWNER_CHANGED|SCHEDULER_MIGRATION_|DEVELOPMENT_CLAIM_|DEVELOPMENT_WORKSPACE_(?:(?:WORKER|BRIDGE)_PENDING|DISABLED)|DEVELOPMENT_(?:CONTAINER|IMAGE)_UNAVAILABLE|DEVELOPMENT_(?:WRITE(?:_CALL)?|COMMAND|MODEL)_BUDGET|DEVELOPMENT_(?:WORKSPACE_IDENTITY|BASELINE|ROOT|REMOTE|APPROVAL)_CHANGED|DELIVERY_RESOURCE_BUSY|AUTHORIZATION_ENDED|QUEUE_LIMIT|NOT_ALLOWED|NOT_ACCEPTED|ALREADY_|NOT_IN_|DEADLINE|INPUT_STALE|CONTROL_PENDING|QUEUE_FULL|TASK_BUSY|TASK_NOT_QUIESCENT|REQUIRED|REGISTER_ONLY/.test(code)?'validation_or_review':'transient';
13
+ const category=code==='PROTOCOL_MAINTENANCE_ACTIVE'?'maintenance':compatibilityErrors.has(code)?'compatibility':code==='AUTH_DENIED'||code.startsWith('PASSKEY_')||code.startsWith('APPROVER_')?'authentication':/UNKNOWN|UNCERTAIN/.test(code)?'uncertain_delivery':/(?:^|_)INVALID(?:_|$)|RETIRED|REVOKED|CONTRACT_CHANGED|SOURCE_CHANGED|LINK_PENDING|CONFLICT|DENIED|MISMATCH|NOT_FOUND|NEEDS_REVIEW|REVIEW_REQUIRED|BAD_REQUEST|RESPONSE_TOO_LARGE|EXHAUSTED|ROUTE_CHANGED|OWNER_CHANGED|SCHEDULER_MIGRATION_|DEVELOPMENT_CLAIM_|DEVELOPMENT_WORKSPACE_(?:(?:WORKER|BRIDGE)_PENDING|DISABLED)|DEVELOPMENT_(?:CONTAINER|IMAGE)_UNAVAILABLE|DEVELOPMENT_(?:WRITE(?:_CALL)?|COMMAND|MODEL)_BUDGET|DEVELOPMENT_(?:WORKSPACE_IDENTITY|BASELINE|ROOT|REMOTE|APPROVAL)_CHANGED|DELIVERY_RESOURCE_BUSY|AUTHORIZATION_ENDED|QUEUE_LIMIT|NOT_ALLOWED|NOT_ACCEPTED|ALREADY_|NOT_IN_|DEADLINE|INPUT_STALE|CONTROL_PENDING|QUEUE_FULL|TASK_BUSY|TASK_NOT_QUIESCENT|REQUIRED|REGISTER_ONLY/.test(code)?'validation_or_review':'transient';
14
14
  return {code,category,retry:category==='maintenance'?'after_explicit_peer_release':category==='compatibility'?'after_compatible_peer':category==='transient'?'bounded_backoff':'operator_review',does_not_grant_authority:true};
15
15
  }
16
16
  const object=x=>!!x&&typeof x==='object'&&!Array.isArray(x);
@@ -30,9 +30,14 @@ export function negotiate(own,remote){
30
30
  }
31
31
  export function requireCapability(method,own,remote,args={}){
32
32
  if(typeof method!=='string'||!Object.hasOwn(METHODS,method))throw Error('METHOD_DENIED');
33
+ if((args.schedule!==undefined||method==='trigger_signal'&&String(args.id).startsWith('schedule-native-'))&&(own.capabilities.managed_triggers!==1||remote.capabilities.managed_triggers!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
34
+ if((args.autonomy_contract!==undefined||method==='accept'&&typeof args.goal==='string'&&args.goal.includes('cloud-autonomous-v1'))&&(own.capabilities.cloud_autonomy!==1||remote.capabilities.cloud_autonomy!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
35
+ if((args.development||args.proposal?.execution?.development||args.development_contract!==undefined)&&(own.capabilities.managed_development!==1||remote.capabilities.managed_development!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
33
36
  if(method==='schedule'&&args.max_runs!==undefined&&(own.capabilities.schedule_budget!==1||remote.capabilities.schedule_budget!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
34
37
  let notify=args.notification_contract===1;try{notify ||= JSON.parse(args.goal).notification_contract===1;}catch{}
35
38
  if(notify&&(own.capabilities.notifications!==1||remote.capabilities.notifications!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
39
+ let continuity=false;try{continuity=JSON.parse(args.goal).continuity===1;}catch{}
40
+ if(continuity&&(own.capabilities.continuity!==1||remote.capabilities.continuity!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
36
41
  let search=!!args.search;try{search ||= !!JSON.parse(args.goal).search;}catch{}
37
42
  if(search&&(own.capabilities.search!==1||remote.capabilities.search!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
38
43
  if(args.proposal?.delivery&&(own.capabilities.delivery_review!==1||remote.capabilities.delivery_review!==1))throw Error('CAPABILITY_VERSION_INCOMPATIBLE');
@@ -0,0 +1,45 @@
1
+ // Deterministic closeout journal. No model, retry loop, new delivery engine or
2
+ // inference of completion from files. The worker must explicitly declare ready.
3
+ import {snapshotFile} from './managed-development.js';
4
+ const json=JSON.stringify;
5
+ const fail=s=>{throw Error('TASK_EXECUTION_'+s);};
6
+ const binding=b=>({task_id:b.item.task_id,claim:b.item.claim,instance_id:b.item.instance});
7
+ export class ManagedCloseout {
8
+ constructor(endpoint){this.e=endpoint;this.db=endpoint.db;this.db.exec('CREATE TABLE IF NOT EXISTS managed_closeouts(claim TEXT PRIMARY KEY,record TEXT NOT NULL,state TEXT NOT NULL,error TEXT)');}
9
+ get(claim){const r=this.db.prepare('SELECT * FROM managed_closeouts WHERE claim=?').get(claim);return r?{...r,record:JSON.parse(r.record)}:null;}
10
+ async intent(b,a){
11
+ if(!b.originalComplete)fail('ORIGINAL_CONTEXT_NOT_COVERED');
12
+ const status=await this.e.validate(b);
13
+ if(b.tools>1||this.e.background(b.agent.id))fail('BACKGROUND_NOT_DRAINED');
14
+ if(status.pending_input||status.input_cursor!==(b.coveredInput??b.item.input_cursor??0))fail('INPUTS_NOT_COVERED');
15
+ const evidence=this.e.development.ready(b,a.review),files={};
16
+ for(const path of b.item.spec.development?.artifacts??[]){const f=snapshotFile(b.item.spec.cwd,path);files[path]={sha256:f.sha256,size:f.size};}
17
+ const record={task_id:b.item.task_id,revision:b.item.revision,round:b.item.runs,input_cursor:status.input_cursor,control_version:status.control_version,summary:a.summary,review:a.review??null,evidence,files};
18
+ // A repeated call can replace an intent only within the SAME still-owned run.
19
+ this.db.prepare("INSERT INTO managed_closeouts VALUES (?,?,'intent',NULL) ON CONFLICT(claim) DO UPDATE SET record=excluded.record,state='intent',error=NULL").run(b.item.claim,json(record));
20
+ this.db.prepare('UPDATE managed_endpoint_runs SET safe_checkpoint=1 WHERE claim=?').run(b.item.claim);
21
+ b.completion=record;return {saved:true,completion_intent:true,committed:false,input_cursor:record.input_cursor};
22
+ }
23
+ async finish(b){
24
+ const r=this.get(b.item.claim);if(!r||!['intent','materialized'].includes(r.state))fail('COMPLETION_INTENT_REQUIRED');
25
+ const c=r.record,status=await this.e.validate(b);
26
+ if(c.revision!==b.item.revision||c.round!==b.item.runs||status.control_version!==c.control_version||status.pending_input||status.input_cursor!==c.input_cursor)fail('COMPLETION_STALE');
27
+ if(b.agent&&this.e.background(b.agent.id)||b.tools)fail('BACKGROUND_NOT_DRAINED');
28
+ // Check exact current inputs again before importing artifacts. Unrelated receipt
29
+ // files cannot invalidate checks with explicitly declared relevant inputs.
30
+ const evidence=this.e.development.ready(b,c.review);
31
+ for(const [path,saved] of Object.entries(c.files)){
32
+ const f=snapshotFile(b.item.spec.cwd,path);if(f.sha256!==saved.sha256||f.size!==saved.size)fail('COMPLETION_ARTIFACT_CHANGED');
33
+ const stored=await this.e.artifact(b,{path,stage:'candidate'});if(!stored?.complete||stored.artifact.sha256!==saved.sha256||stored.artifact.size!==saved.size)fail('COMPLETION_ARTIFACT_CHANGED');
34
+ }
35
+ // Snapshotting yields between chunks. Recheck real test inputs after import.
36
+ const finalEvidence=this.e.development.ready(b,c.review);
37
+ if(finalEvidence)await this.e.call('evidence',{...binding(b),evidence:finalEvidence});
38
+ const latest=await this.e.validate(b);
39
+ if(latest.pending_input||latest.input_cursor!==c.input_cursor||latest.control_version!==c.control_version)fail('COMPLETION_STALE');
40
+ this.db.prepare("UPDATE managed_closeouts SET state='materialized',error=NULL WHERE claim=?").run(b.item.claim);
41
+ b.completion=c;b.report={outcome:'ready',summary:c.summary};b.review=c.review;
42
+ return {outcome:'ready',summary:c.summary,completion:{input_cursor:c.input_cursor,control_version:c.control_version,round:c.round}};
43
+ }
44
+ invalidate(claim,error){this.db.prepare("UPDATE managed_closeouts SET state='needs_review',error=? WHERE claim=?").run(error,claim);}
45
+ }
@@ -0,0 +1,19 @@
1
+ // Versioned task semantics. Historical approvals/limits are never migrated on read.
2
+ export const CLOUD_TASK_POLICY='cloud-autonomous-v1';
3
+ export const autonomous=spec=>spec?.execution_policy===CLOUD_TASK_POLICY;
4
+ export const TASK_CONTEXT_MAX=1024*1024; // Transport bound, not a model/request budget.
5
+
6
+ // This public lookup classifies an already-admitted native session, NOT a person.
7
+ // dsh-im owns ingress owner/whitelist checks. Never trust its last-speaker field
8
+ // as authentication, read its private storage, or couple execution to IM grants.
9
+ export async function taskChannel(ctx,role,agent){
10
+ const im=ctx.get?.('dshIm');
11
+ const conversation=await im?.describeConversation?.(agent.id);
12
+ if(conversation)return role==='cloud'?'cloud-im':'local-im';
13
+ if(role==='local')return 'local-dsh';
14
+ throw Error('TASK_CREATION_CHANNEL_UNSUPPORTED');
15
+ }
16
+ export function validateTaskChannel(role,channel){
17
+ if(!(role==='local'&&['local-dsh','local-im'].includes(channel)||role==='cloud'&&channel==='cloud-im'))throw Error('TASK_CREATION_CHANNEL_UNSUPPORTED');
18
+ return channel;
19
+ }
@@ -0,0 +1,71 @@
1
+ import {createHash} from 'node:crypto';
2
+ // Small macOS native leaf-command adapter. No shell, VM, background job or
3
+ // privilege fallback. Commands needing child processes are deliberately denied.
4
+ import {spawn} from 'node:child_process';
5
+ import {realpathSync,lstatSync,readdirSync,mkdirSync} from 'node:fs';
6
+ import {isAbsolute,join,relative,dirname} from 'node:path';
7
+ const fail=s=>{throw Error('TASK_COMMAND_'+s);};
8
+ const inside=(root,p)=>{const r=relative(root,p);return !r||r!=='..'&&!r.startsWith('../')&&!isAbsolute(r);};
9
+ export function validateManagedCommands(c){
10
+ if(c===undefined)return;
11
+ if(!c||typeof c!=='object'||Array.isArray(c)||!Object.keys(c).length||Object.keys(c).length>8)fail('CONFIG_INVALID');
12
+ for(const [name,v] of Object.entries(c))if(!/^[a-z][a-z0-9_-]{0,39}$/.test(name)||!v||typeof v!=='object'||Object.keys(v).some(k=>!['executable','args','timeout_ms'].includes(k))||typeof v.executable!=='string'||!isAbsolute(v.executable)||/[\x00-\x1f]/.test(v.executable)||!Array.isArray(v.args)||v.args.length>32||v.args.some(a=>typeof a!=='string'||a.length>2000||a.includes('\0'))||!Number.isInteger(v.timeout_ms)||v.timeout_ms<100||v.timeout_ms>300000)fail('CONFIG_INVALID');
13
+ }
14
+ function checkTree(root){
15
+ let n=0;const visit=p=>{if(++n>50000)fail('TREE_LIMIT');const s=lstatSync(p);if(s.isSymbolicLink())return;if(s.isFile()&&s.nlink!==1)fail('HARDLINK_DENIED');if(s.isDirectory())for(const f of readdirSync(p))visit(join(p,f));};visit(root);
16
+ }
17
+ export function macCommandPlan(cwd,command,{protectedRoots=[]}={}){
18
+ validateManagedCommands({command});const root=realpathSync(cwd),executable=realpathSync(command.executable);
19
+ if(root!==cwd||root==='/'||protectedRoots.some(p=>inside(root,p)||inside(p,root))||inside(root,executable))fail('SCOPE_INVALID');
20
+ if(!lstatSync(root).isDirectory()||!lstatSync(executable).isFile())fail('SCOPE_INVALID');checkTree(root);
21
+ const home=join(root,'.managed-command-home'),tmp=join(root,'.managed-command-tmp');
22
+ for(const p of [home,tmp]){mkdirSync(p,{recursive:true,mode:0o700});if(realpathSync(p)!==p||!lstatSync(p).isDirectory())fail('SCOPE_INVALID');}
23
+ const q=JSON.stringify,system=['/System/Library','/usr/lib','/usr/share','/Library/Apple/System/Library','/System/Volumes/Preboot/Cryptexes/OS/System/Library','/System/Volumes/Preboot/Cryptexes/OS/usr/lib','/System/Cryptexes/OS/System/Library','/System/Cryptexes/OS/usr/lib'];
24
+ const policy=`(version 1)
25
+ (deny default)
26
+ (allow process-exec (literal ${q(executable)}))
27
+ (deny process-fork)
28
+ (allow signal (target self))
29
+ (allow sysctl-read)
30
+ (allow file-read* file-map-executable (literal "/") ${system.map(p=>`(subpath ${q(p)})`).join(' ')} (subpath ${q(root)}) (literal ${q(executable)}) (literal "/dev/null") (literal "/dev/urandom") (literal "/dev/random"))
31
+ (allow file-read-metadata (path-ancestors "/System/Volumes/Preboot/Cryptexes/OS/System/Library") (path-ancestors "/System/Cryptexes/OS/System/Library") (path-ancestors ${q(root)}) (path-ancestors ${q(executable)}) (literal "/") ${[...new Set([dirname(root),dirname(executable)])].map(p=>`(literal ${q(p)})`).join(' ')})
32
+ (allow file-write* (subpath ${q(root)}) (literal "/dev/null"))
33
+ (deny network*)
34
+ ${protectedRoots.map(p=>`(deny file-read* file-write* (subpath ${q(p)}))`).join('\n')}`;
35
+ return {root,executable,args:command.args,timeout:command.timeout_ms,policy,env:{PATH:'/usr/bin:/bin',HOME:home,TMPDIR:tmp,LANG:'en_US.UTF-8'}};
36
+ }
37
+ export async function runMacCommand(plan,signal){
38
+ if(process.platform!=='darwin')fail('MACOS_REQUIRED');signal?.throwIfAborted();
39
+ // Every process starts under the policy; sandbox failure is a failed command,
40
+ // never a reason to retry outside the sandbox. No credentials/login environment.
41
+ return new Promise((resolve,reject)=>{
42
+ let child;try{child=spawn('/usr/bin/sandbox-exec',['-p',plan.policy,plan.executable,...plan.args],{cwd:plan.root,env:plan.env,stdio:['ignore','pipe','pipe']});}catch{reject(Error('TASK_COMMAND_START_FAILED'));return;}
43
+ let output='',bytes=0,truncated=false,timedOut=false,aborted=false;
44
+ const collect=chunk=>{const available=65536-bytes;if(available>0){const v=chunk.subarray(0,available);output+=v.toString('utf8');bytes+=v.length;}if(chunk.length>available)truncated=true;};
45
+ child.stdout.on('data',collect);child.stderr.on('data',collect);
46
+ // Fork/posix_spawn is denied by Seatbelt. Thus this sole process + its threads
47
+ // is the complete execution boundary; no process-tree guessing is needed.
48
+ const stop=()=>{aborted=true;child.kill('SIGKILL');};signal?.addEventListener('abort',stop,{once:true});
49
+ const timer=setTimeout(()=>{timedOut=true;child.kill('SIGKILL');},plan.timeout);
50
+ child.once('error',()=>{clearTimeout(timer);signal?.removeEventListener('abort',stop);reject(Error('TASK_COMMAND_START_FAILED'));});
51
+ child.once('close',(code,killedBy)=>{clearTimeout(timer);signal?.removeEventListener('abort',stop);resolve({exit_code:code,signal:killedBy,output,truncated,timed_out:timedOut,aborted,drained:true,backend:'macos-seatbelt-leaf',network:false,child_processes:false});});
52
+ if(signal?.aborted)stop();
53
+ });
54
+ }
55
+ export class ManagedCommands {
56
+ constructor(endpoint,config,run=runMacCommand){validateManagedCommands(config);this.ep=endpoint;this.config=config;this.run=run;this.busy=false;endpoint.db?.exec('CREATE TABLE IF NOT EXISTS managed_command_bindings(task_id TEXT NOT NULL,name TEXT NOT NULL,fingerprint TEXT NOT NULL,PRIMARY KEY(task_id,name));');}
57
+ async execute(b,a,signal){
58
+ if(!a||Object.keys(a).join()!=='name'||typeof a.name!=='string')fail('ARGUMENT_INVALID');
59
+ if(this.ep.g.c.role!=='local'||!b.item.spec.commands?.includes(a.name))fail('NOT_AUTHORIZED');
60
+ const command=this.config?.[a.name];if(!command)fail('UNAVAILABLE');if(this.busy)fail('BUSY');
61
+ this.busy=true;try{
62
+ await this.ep.validate(b);
63
+ // A device config edit must not silently change what an existing task runs.
64
+ const fingerprint=createHash('sha256').update(JSON.stringify(command)).digest('hex'),db=this.ep.db;
65
+ if(db){const old=db.prepare('SELECT fingerprint FROM managed_command_bindings WHERE task_id=? AND name=?').get(b.item.task_id,a.name);if(old&&old.fingerprint!==fingerprint)fail('DEFINITION_CHANGED');db.prepare('INSERT OR IGNORE INTO managed_command_bindings VALUES (?,?,?)').run(b.item.task_id,a.name,fingerprint);}
66
+ const plan=macCommandPlan(b.item.spec.cwd,command,{protectedRoots:[this.ep.g.c.stateDir,...(this.ep.c.protectedRoots??[])].filter(Boolean)});
67
+ const result=await this.run(plan,AbortSignal.any([b.controller.signal,signal].filter(Boolean)));
68
+ if(result?.drained!==true)fail('DRAIN_UNVERIFIED');return result;
69
+ }finally{this.busy=false;}
70
+ }
71
+ }