@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.
- package/README.md +68 -97
- package/bundle/README.md +68 -97
- package/bundle/SETUP.md +87 -0
- package/bundle/approval-chat.js +2 -2
- package/bundle/approval-feishu.js +4 -1
- package/bundle/approval-ledger.js +30 -13
- package/bundle/approval-workbench.js +2 -0
- package/bundle/artifact-media.js +2 -2
- package/bundle/compatibility.json +47 -1
- package/bundle/configuration-v2.json +15 -0
- package/bundle/delivery-state.js +2 -0
- package/bundle/development-gate.js +2 -0
- package/bundle/distribution-lock.json +18 -18
- package/bundle/gateway-maintenance.js +1 -0
- package/bundle/global-rules/AGENTS.md +24 -20
- package/bundle/index.js +135 -25
- package/bundle/intervention-state.js +1 -1
- package/bundle/link-contract.js +9 -4
- package/bundle/managed-closeout.js +45 -0
- package/bundle/managed-cloud-policy.js +19 -0
- package/bundle/managed-command.js +71 -0
- package/bundle/managed-development-contract.js +43 -0
- package/bundle/managed-development.js +86 -0
- package/bundle/managed-endpoint.js +282 -0
- package/bundle/managed-execution.js +207 -0
- package/bundle/managed-policy.js +20 -0
- package/bundle/managed-runtime.js +186 -0
- package/bundle/managed-search.js +57 -0
- package/bundle/managed-tools.js +17 -0
- package/bundle/managed-triggers.js +85 -0
- package/bundle/native-context.js +4 -2
- package/bundle/native-entry.js +6 -2
- package/bundle/native-human-context.js +29 -0
- package/bundle/native-model-choice.js +12 -0
- package/bundle/native-rules/continuity.md +13 -0
- package/bundle/native-rules/execution.md +36 -0
- package/bundle/native-tools.js +52 -19
- package/bundle/notification-artifacts.js +26 -10
- package/bundle/notification-endpoint.js +1 -1
- package/bundle/notification-feishu.js +2 -2
- package/bundle/notification-media-endpoint.js +4 -3
- package/bundle/notification-observations.js +45 -0
- package/bundle/onboarding-cloud-package.js +1 -1
- package/bundle/onboarding-cloud.js +1 -1
- package/bundle/onboarding-file-contract.js +3 -2
- package/bundle/onboarding-file-transfer.js +4 -4
- package/bundle/onboarding-native-probe.js +1 -1
- package/bundle/onboarding-runtime-activation.js +1 -1
- package/bundle/onboarding-runtime-extension.js +2 -2
- package/bundle/package-lock.json +18 -18
- package/bundle/package.json +16 -14
- package/bundle/peer-file-review.js +1 -1
- package/bundle/peer-files.js +8 -4
- package/bundle/recovery.js +1 -1
- package/bundle/repository-gitee.js +12 -0
- package/bundle/repository-managed.js +1 -0
- package/bundle/runtime-im-lock.json +3409 -2849
- package/bundle/runtime-im-package.json +5 -5
- package/bundle/runtime-lock.json +3409 -2849
- package/bundle/runtime-package.json +5 -5
- package/bundle/schedule-ledger.js +35 -0
- package/bundle/search-contract.js +11 -1
- package/bundle/skills/task-approval/SKILL.md +20 -1
- package/bundle/skills/task-development/SKILL.md +30 -13
- package/bundle/skills/task-direct-link/SKILL.md +39 -13
- package/bundle/skills/task-file-transfer/SKILL.md +2 -2
- package/bundle/skills/task-notify/SKILL.md +2 -0
- package/bundle/skills/task-repository/SKILL.md +10 -1
- package/bundle/skills/task-research/SKILL.md +17 -2
- package/bundle/source-assessment.js +38 -0
- package/bundle/source-gitee.js +52 -0
- package/bundle/source-intake.js +137 -0
- package/bundle/source-policy-envelope.js +29 -0
- package/bundle/source-routing-tools.js +12 -0
- package/bundle/source-routing.js +218 -0
- package/bundle/source-search.js +2 -1
- package/bundle/store.js +6 -5
- package/bundle/task-artifacts.js +51 -12
- package/bundle/task-board.js +8 -4
- package/bundle/task-catalog.js +4 -3
- package/bundle/task-context.js +2 -2
- package/bundle/task-continuity-tools.js +14 -0
- package/bundle/task-continuity.js +248 -0
- package/bundle/task-envelope.js +31 -20
- package/bundle/task-notifications.js +6 -2
- package/bundle/task-observer-model.js +25 -0
- package/bundle/task-observer.js +120 -0
- package/bundle/task-results.js +1 -1
- package/bundle/task-scheduler.js +3 -3
- package/bundle/task-trigger-time.js +17 -12
- package/bundle/task-triggers.js +5 -30
- package/bundle/task-worker.js +10 -2
- package/bundle/task-workflows.js +2 -1
- package/bundle/workbench-client.js +165 -30
- package/bundle/workbench-decisions.js +22 -5
- package/bundle/worker-tools.js +8 -1
- package/compatibility.json +47 -1
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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>
|
|
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)>
|
|
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>
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
package/bundle/link-contract.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Application protocol, independent of plugin semver, IM transports and task authority.
|
|
2
|
-
export const VERSION='0.
|
|
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
|
+
}
|