agentworkshop 0.7.48 → 0.7.49

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 (31) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/builds/latest.json +1 -1
  3. package/.output/public/_nuxt/builds/meta/0530cee1-fff5-4025-a6f4-5708b6534608.json +1 -0
  4. package/.output/server/chunks/_/nitro.mjs +957 -955
  5. package/.output/server/package.json +1 -1
  6. package/README-zh.md +813 -811
  7. package/README.md +823 -821
  8. package/config.yml +176 -176
  9. package/docs/cli.en.md +445 -445
  10. package/docs/cli.md +410 -410
  11. package/docs/full-test-plan.md +191 -191
  12. package/docs/plugins.en.md +567 -567
  13. package/docs/plugins.md +560 -560
  14. package/docs/sdk.en.md +922 -922
  15. package/docs/sdk.md +861 -861
  16. package/package.json +172 -172
  17. package/scripts/_aw0746-stage5.mjs +159 -144
  18. package/scripts/_aw0746-stage7-twin.mjs +301 -267
  19. package/scripts/_aw0748-failclosed-probe.mjs +117 -90
  20. package/scripts/_aw0748-fresh-channel.mjs +21 -0
  21. package/scripts/_aw0748-mock-closedloop.mjs +89 -0
  22. package/scripts/_aw0748-wake-channel.mjs +46 -0
  23. package/scripts/_aw0749-readme-rows.mjs +23 -0
  24. package/scripts/_probe-lease.cjs +13 -0
  25. package/scripts/_probe-task-queue.cjs +9 -0
  26. package/server/services/workshop/aml/twin/provider-contracts.ts +34 -0
  27. package/server/services/workshop/aml/twin/provider-health.ts +8 -0
  28. package/server/services/workshop/aml/twin/provider-loader.ts +67 -0
  29. package/server/services/workshop/aml/twin/provider-registry.ts +50 -0
  30. package/server/services/workshop/runtime/manager/state.ts +21 -4
  31. package/.output/public/_nuxt/builds/meta/6f524f3b-53e1-4df9-84e6-84d8058b9f16.json +0 -1
@@ -0,0 +1,50 @@
1
+ import { sha256 } from './contracts'
2
+ import { checkProviderHealth, normalizeProviderHealth, providerHashOf, providerHealthAllowsUse, validateProviderDefinition } from './provider-health'
3
+ import type { PhysicsProviderManifest, ProviderHealth, ProviderLifecycleState, ProviderValidationResult, ScenePack, SolverAdapter, SolverAdapterManifest, TwinPhysicsProvider, TwinPluginContribution, TwinProviderDrainResult, TwinProviderFilter, TwinProviderLease, TwinProviderRegistrationResult, TwinProviderRetireResult, TwinProviderSummary, TwinProviderUnregistrationResult, TwinRegistrationSource } from './provider-contracts'
4
+ export interface ResolveProviderOptions { generation?:number, allowDraining?:boolean, includeFailed?:boolean }
5
+ interface ProviderRecord { provider:TwinPhysicsProvider, manifest:PhysicsProviderManifest, providerHash:string, pluginName:string, pluginVersion?:string, generation:number, state:ProviderLifecycleState, health:ProviderHealth, inFlight:number, registeredAt:string, drainingAt?:string, retiredAt?:string }
6
+ interface GenerationRecord { pluginName:string, pluginVersion?:string, generation:number, state:ProviderLifecycleState, registeredAt:string, drainingAt?:string, retiredAt?:string, providers:Map<string,ProviderRecord>, scenePacks:Map<string,ScenePack>, solverAdapters:Map<string,SolverAdapter> }
7
+ export class TwinProviderRegistryError extends Error{readonly code:string;constructor(code:string,message:string){super(message);this.name='TwinProviderRegistryError';this.code=code}}
8
+ function pluginOf(s: TwinRegistrationSource|string='core'){return typeof s==='string'?s:String(s.pluginName??s.plugin??s.name??'core')}
9
+ function verOf(s: TwinRegistrationSource|string='core'){return typeof s==='string'?undefined:s.pluginVersion??s.version}
10
+ function key(id:string,v:string){return `${id}@${v}`}
11
+ function normalizePack(raw:any):ScenePack{return{...raw,sceneKind:raw.sceneKind??raw.scenePackId??'unknown-scene',sceneSchemaVersion:String(raw.sceneSchemaVersion??raw.version??'1.0.0')}}
12
+ export class TwinProviderRegistry{
13
+ private generations=new Map<string,GenerationRecord>();private next=new Map<string,number>();private leases=0
14
+ private allocate(plugin:string){const n=(this.next.get(plugin)??0)+1;this.next.set(plugin,n);return n}
15
+ registerTwinPluginContribution(pluginName:string,c:TwinPluginContribution,generation?:number):TwinProviderRegistrationResult{const plugin=String(pluginName||'').trim();if(!plugin)throw new TwinProviderRegistryError('PLUGIN_NAME_MISSING','pluginName required');const g=generation??this.allocate(plugin);const gk=`${plugin}\0${g}`;let gen=this.generations.get(gk);if(gen?.state==='RETIRED'||gen?.state==='DRAINING')throw new TwinProviderRegistryError('GENERATION_UNAVAILABLE',`generation ${plugin}@${g} unavailable`);if(!gen){gen={pluginName:plugin,generation:g,state:'VALIDATED',registeredAt:new Date().toISOString(),providers:new Map(),scenePacks:new Map(),solverAdapters:new Map()};this.generations.set(gk,gen)}let added=0;for(const provider of [...(c.providers??[]),...(c.physicsProviders??[])]){const check=validateProviderDefinition(provider);if(!check.valid||!check.manifest||!check.providerHash)throw new TwinProviderRegistryError('PROVIDER_INVALID',check.errors.join(';')||'invalid provider');const k=key(check.manifest.providerId,check.manifest.version);const old=gen.providers.get(k);if(old){if(old.providerHash!==check.providerHash)throw new TwinProviderRegistryError('PROVIDER_CONFLICT',k);continue}for(const [otherKey,otherGen] of this.generations){if(otherKey===gk)continue;const oldOther=otherGen.providers.get(k);if(oldOther&&oldOther.state!=='RETIRED'&&oldOther.pluginName!==plugin)throw new TwinProviderRegistryError('PROVIDER_IDENTITY_CONFLICT',`${k} already registered by ${oldOther.pluginName}`)}gen.providers.set(k,{provider,manifest:check.manifest,providerHash:check.providerHash,pluginName:plugin,pluginVersion:verOf((c as any).source),generation:g,state:'READY',health:normalizeProviderHealth(undefined),inFlight:0,registeredAt:gen.registeredAt});added++}for(const raw of c.scenePacks??[]){const pack=normalizePack(raw);gen.scenePacks.set(`${pack.sceneKind}@${pack.sceneSchemaVersion}`,pack)}for(const adapter of c.solverAdapters??[]){const m=(adapter as any).manifest as SolverAdapterManifest;if(!m?.adapterId||typeof (adapter as any).solve!=='function')throw new TwinProviderRegistryError('SOLVER_ADAPTER_INVALID','solver adapter requires manifest.adapterId and solve()');gen.solverAdapters.set(`${m.adapterId}@${m.version}`,adapter)}gen.state='READY';for(const [k,old] of this.generations){if(old.pluginName===plugin&&k!==gk&&old.state!=='RETIRED'){this.markDraining(old)}}return{ok:true,pluginName:plugin,generation:g,providerIds:[...gen.providers.values()].map(r=>r.manifest.providerId),sceneKinds:[...gen.scenePacks.values()].map(p=>p.sceneKind),solverAdapterIds:[...gen.solverAdapters.values()].map(a=>(a as any).manifest.adapterId),state:gen.state,idempotent:added===0}}
16
+ unregisterTwinPluginContribution(pluginName:string,generation?:number):TwinProviderUnregistrationResult{const drained:number[]=[],retired:number[]=[];for(const g of this.generations.values())if(g.pluginName===pluginName&&(generation==null||g.generation===generation)){if(g.state!=='RETIRED'){this.markDraining(g);drained.push(g.generation)}if(g.state==='RETIRED')retired.push(g.generation)}return{ok:true,pluginName:pluginName,generation,drainedGenerations:drained,retiredGenerations:retired}}
17
+ registerPhysicsProvider(provider:TwinPhysicsProvider,source:TwinRegistrationSource|string='core',generation?:number){return this.registerTwinPluginContribution(pluginOf(source),{providers:[provider]},generation)}
18
+ registerScenePack(pack:ScenePack,source:TwinRegistrationSource|string='core',generation?:number){return this.registerTwinPluginContribution(pluginOf(source),{scenePacks:[pack]},generation)}
19
+ registerSolverAdapter(adapter:SolverAdapter,source:TwinRegistrationSource|string='core',generation?:number){return this.registerTwinPluginContribution(pluginOf(source),{solverAdapters:[adapter]},generation)}
20
+ resolveProvider(providerId:string,versionOrOptions?:string|ResolveProviderOptions,maybeOptions?:ResolveProviderOptions):TwinProviderLease|undefined{const version=typeof versionOrOptions==='string'?versionOrOptions:undefined;const options=(typeof versionOrOptions==='object'?versionOrOptions:maybeOptions)??{};const rows=[...this.generations.values()].flatMap(g=>[...g.providers.values()]).filter(r=>r.manifest.providerId===providerId&&(!version||r.manifest.version===version)&&(!options.generation||r.generation===options.generation)&&r.state!=='RETIRED'&&(!options.includeFailed&&r.state==='FAILED'?false:true)&&(!options.allowDraining&&r.state==='DRAINING'?false:true)).sort((a,b)=>b.generation-a.generation);const r=rows[0];if(!r)return;return this.lease(r)}
21
+ resolve(providerId:string,versionOrOptions?:string|ResolveProviderOptions,maybeOptions?:ResolveProviderOptions){return this.resolveProvider(providerId,versionOrOptions,maybeOptions)}
22
+ acquireProvider(providerId:string,versionOrOptions?:string|ResolveProviderOptions,maybeOptions?:ResolveProviderOptions){return this.resolveProvider(providerId,versionOrOptions,maybeOptions)}
23
+ listProviders(filter:TwinProviderFilter={}):TwinProviderSummary[]{return[...this.generations.values()].flatMap(g=>[...g.providers.values()]).filter(r=>(!filter.providerId||r.manifest.providerId===filter.providerId)&&(!filter.version||r.manifest.version===filter.version)&&(!filter.pluginName||r.pluginName===filter.pluginName)&&(!filter.generation||r.generation===filter.generation)&&(!filter.states?.length||filter.states.includes(r.state))&&(filter.includeDraining||r.state!=='DRAINING')&&(filter.includeRetired||r.state!=='RETIRED')&&(filter.includeFailed||r.state!=='FAILED')).map(r=>({...r,manifest:r.manifest}))}
24
+ listTwinProviders(filter:TwinProviderFilter={}){return this.listProviders(filter)}
25
+ listScenePacks(pluginName?:string){return[...this.generations.values()].filter(g=>(!pluginName||g.pluginName===pluginName)&&g.state!=='RETIRED').flatMap(g=>[...g.scenePacks.values()])}
26
+ listSolverAdapters(pluginName?:string){return[...this.generations.values()].filter(g=>(!pluginName||g.pluginName===pluginName)&&g.state!=='RETIRED').flatMap(g=>[...g.solverAdapters.values()])}
27
+ getProviderHealth(providerId:string,version?:string){return this.resolveProvider(providerId,version,{allowDraining:true,includeFailed:true})?.manifest?this.find(providerId,version)?.health:undefined}
28
+ async checkProviderHealth(providerId:string,version?:string){const r=this.find(providerId,version);if(!r)throw new TwinProviderRegistryError('PROVIDER_NOT_FOUND',providerId);r.health=await checkProviderHealth(r.provider);if(!providerHealthAllowsUse(r.health))r.state='FAILED';return r.health}
29
+ validateProvider(providerId:string,version?:string):ProviderValidationResult{const r=this.find(providerId,version);if(!r)return{valid:false,errors:['PROVIDER_NOT_FOUND'],warnings:[]};return validateProviderDefinition(r.provider)}
30
+ drainGeneration(pluginName:string,generation:number):TwinProviderDrainResult{const g=this.generations.get(`${pluginName}\0${generation}`);if(!g)throw new TwinProviderRegistryError('GENERATION_NOT_FOUND',`${pluginName}@${generation}`);this.markDraining(g);return{ok:true,pluginName,generation,state:g.state,inFlight:[...g.providers.values()].reduce((a,r)=>a+r.inFlight,0),retired:g.state==='RETIRED',providerIds:[...g.providers.values()].map(r=>r.manifest.providerId)}}
31
+ drain(pluginName:string,generation:number){return this.drainGeneration(pluginName,generation)}
32
+ retireProvider(providerId:string,version:string,generation?:number):TwinProviderRetireResult{const r=this.find(providerId,version,generation);if(!r)throw new TwinProviderRegistryError('PROVIDER_NOT_FOUND',`${providerId}@${version}`);r.state=r.inFlight?'DRAINING':'RETIRED';if(r.state==='RETIRED')r.retiredAt=new Date().toISOString();return{ok:true,providerId,version,generation:r.generation,state:r.state,inFlight:r.inFlight,retired:r.state==='RETIRED'}}
33
+ retire(providerId:string,version:string,generation?:number){return this.retireProvider(providerId,version,generation)}
34
+ reloadGeneration(pluginName:string,c:TwinPluginContribution,generation?:number){return this.registerTwinPluginContribution(pluginName,c,generation??this.allocate(pluginName))}
35
+ reloadProvider(pluginName:string,c:TwinPluginContribution,generation?:number){return this.reloadGeneration(pluginName,c,generation)}
36
+ private find(id:string,version?:string,generation?:number){return[...this.generations.values()].flatMap(g=>[...g.providers.values()]).filter(r=>r.manifest.providerId===id&&(!version||r.manifest.version===version)&&(!generation||r.generation===generation)).sort((a,b)=>b.generation-a.generation)[0]}
37
+ private markDraining(g:GenerationRecord){if(g.state==='RETIRED')return;g.state='DRAINING';g.drainingAt??=new Date().toISOString();for(const r of g.providers.values()){if(!r.inFlight){r.state='RETIRED';r.retiredAt??=new Date().toISOString()}else r.state='DRAINING'}if([...g.providers.values()].every(r=>r.state==='RETIRED')){g.state='RETIRED';g.retiredAt??=new Date().toISOString()}}
38
+ private lease(r:ProviderRecord):TwinProviderLease{r.inFlight++;const refId=`twin-provider-ref-${++this.leases}`;let released=false;return{refId,provider:r.provider,manifest:r.manifest,lineage:{providerId:r.manifest.providerId,providerVersion:r.manifest.version,providerHash:r.providerHash,pluginName:r.pluginName,generation:r.generation},generation:r.generation,get released(){return released},release:()=>{if(released)return;released=true;r.inFlight=Math.max(0,r.inFlight-1);if(!r.inFlight&&r.state==='DRAINING'){r.state='RETIRED';r.retiredAt??=new Date().toISOString()}}}}
39
+ }
40
+ const singleton=new TwinProviderRegistry();export function getTwinProviderRegistry(){return singleton}
41
+ export const registerTwinPluginContribution=(p:string,c:TwinPluginContribution,g?:number)=>singleton.registerTwinPluginContribution(p,c,g)
42
+ export const unregisterTwinPluginContribution=(p:string,g?:number)=>singleton.unregisterTwinPluginContribution(p,g)
43
+ export const listTwinProviders=(f:TwinProviderFilter={})=>singleton.listProviders(f)
44
+ export const resolveTwinProvider=(p:string,v?:string|ResolveProviderOptions,o?:ResolveProviderOptions)=>singleton.resolveProvider(p,v,o)
45
+ export const acquireTwinProvider=resolveTwinProvider
46
+ export const reloadTwinProviderGeneration=(p:string,c:TwinPluginContribution,g?:number)=>singleton.reloadGeneration(p,c,g)
47
+ export const drainTwinProviderGeneration=(p:string,g:number)=>singleton.drainGeneration(p,g)
48
+ export const retireTwinProvider=(p:string,v:string,g?:number)=>singleton.retireProvider(p,v,g)
49
+ export function attachTwinPluginBridge(registry:TwinProviderRegistry=singleton){const global=globalThis as any;const existing=global.__twinPluginExt;const api:any={registerPhysicsProvider:(payload:any,source:any)=>registry.registerPhysicsProvider(payload,source),registerScenePack:(payload:any,source:any)=>registry.registerScenePack({...(payload as any),sceneKind:(payload as any).sceneKind??(payload as any).scenePackId??'unknown',sceneSchemaVersion:(payload as any).sceneSchemaVersion??(payload as any).version??'1.0.0'},source),registerObjectiveProfile:()=>({ok:true}),registerTrainingAdapter:()=>({ok:true}),registerSolverAdapter:(payload:any,source:any)=>registry.registerSolverAdapter(payload,source),listProviders:(f:any)=>registry.listProviders(f),listTwinProviders:(f:any)=>registry.listProviders(f),getProviderHealth:(p:string,v?:string)=>registry.getProviderHealth(p,v),resolveProvider:(p:string,v?:string)=>registry.resolveProvider(p,v),validateProvider:(p:string,v?:string)=>registry.validateProvider(p,v),retireProvider:(p:string,v?:string)=>v?registry.retireProvider(p,v):undefined,unregisterPlugin:(s:any)=>registry.unregisterTwinPluginContribution(String(s?.plugin??s?.pluginName??s?.name??s)),removePlugin:(s:any)=>registry.unregisterTwinPluginContribution(String(s?.plugin??s?.pluginName??s?.name??s))};if(existing&&typeof existing.attach==='function'){existing.attach(api);return existing}const bridge=existing??{pending:[],register:(kind:string,payload:any,source:any)=>{const fn=api[kind];return fn?fn(payload,source):undefined},registerEntry:(e:any)=>bridge.register(e.kind,e.payload,e.source),attach:(r:any)=>{bridge.registry=r},call:(m:string,...a:any[])=>bridge.registry?.[m]?.(...a),invoke:(m:string,...a:any[])=>bridge.call(m,...a)};global.__twinPluginExt=bridge;if(Array.isArray(bridge.pending)&&bridge.pending.length){const pending=bridge.pending.splice(0);for(const e of pending){const fn=api[e.kind];if(fn)fn(e.payload,e.source)}}return bridge}
50
+ attachTwinPluginBridge()
@@ -221,12 +221,18 @@ export abstract class ManagerState extends ManagerContracts {
221
221
  }
222
222
  }
223
223
 
224
+ /**
225
+ * 按需装配频道(lead 运行时 + SchedulerLoop,幂等)。实现位于装配层
226
+ * `ManagerRuntimeWiring.ensureChannelActive`;基类只声明,调用点在任务事件钩子里。
227
+ */
228
+ protected abstract ensureChannelActive(channelId: string): void
229
+
224
230
  protected getTaskEngine(): TaskEngine {
225
231
  if (!this.taskEngine) {
226
232
  const factory = this.deps.taskEngineFactory ?? (r => new TaskEngineImpl(r, {
227
233
  onTaskChange: (e) => {
228
- // 全字段转发(含 progress):TaskEngine 内部进度变化(applyEvent 分块折算 /
229
- // complete 置 100)须经总线 → WS task.progress 实时同步,否则前端实体进度滞后
234
+ // 全字段转发(含 progress):TaskEngine 内部进度变化(applyEvent 分块折算 /
235
+ // complete 置 100)须经总线 → WS task.progress 实时同步,否则前端实体进度滞后
230
236
  this.buses.get(e.channelId)?.notifyTask({
231
237
  taskId: e.taskId,
232
238
  state: e.state,
@@ -237,8 +243,19 @@ export abstract class ManagerState extends ManagerContracts {
237
243
  reassignFrom: e.reassignFrom,
238
244
  reason: e.reason,
239
245
  })
240
- // 事件驱动调度:任务状态变化即唤醒该频道调度循环(空闲退避即刻恢复快节奏)
241
- this.channels.get(e.channelId)?.scheduler?.wake()
246
+ // 事件驱动调度:任务状态变化即唤醒该频道调度循环(空闲退避即刻恢复快节奏)。
247
+ //
248
+ // ⚠️ 只 wake 不够:lead 运行时因空闲被清扫卸载后 `cr.scheduler` 会被置空
249
+ // (见 runtime-wiring.attachScheduler 注释),此后新建的根任务只落库,
250
+ // `scheduler?.wake()` 变成空操作 —— 任务永久停在 SUBMITTED,直到
251
+ // ROOT_TIMEOUT(~18 分钟)被判 FAILED(实测复现:mock Channel 第一条根任务
252
+ // COMPLETED,后续两条一直 SUBMITTED;omp Channel 同样如此)。
253
+ // `reviveScheduler` 只在"lead 正在被装配"的窗口里被调用,没有别的事件会重建
254
+ // 调度循环,所以这里补上**重新挂载**的分支:没有 scheduler 就按需装配 lead +
255
+ // 调度循环(ensureChannelActive 幂等;频道停用/无 lead 时内部直接返回)。
256
+ const cr = this.channels.get(e.channelId)
257
+ if (cr?.scheduler) cr.scheduler.wake()
258
+ else if (e.state === 'SUBMITTED' || e.state === 'ASSIGNED') ((this as unknown) as { ensureChannelActive: (channelId: string) => void }).ensureChannelActive(e.channelId)
242
259
  },
243
260
  }))
244
261
  this.taskEngine = factory({ tasks: this.deps.repos.tasks, messages: this.deps.repos.messages })
@@ -1 +0,0 @@
1
- {"id":"6f524f3b-53e1-4df9-84e6-84d8058b9f16","timestamp":1790323631296,"prerendered":[]}