@mogragsigma/dsh-dual-host-plugin 0.33.4 → 2.0.1

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 (99) hide show
  1. package/README.md +70 -99
  2. package/bundle/README.md +70 -99
  3. package/bundle/SETUP.md +87 -0
  4. package/bundle/approval-chat.js +2 -2
  5. package/bundle/approval-feishu.js +4 -1
  6. package/bundle/approval-ledger.js +30 -13
  7. package/bundle/approval-workbench.js +2 -0
  8. package/bundle/artifact-media.js +2 -2
  9. package/bundle/compatibility.json +43 -3
  10. package/bundle/configuration-v2.json +15 -0
  11. package/bundle/delivery-state.js +2 -0
  12. package/bundle/development-gate.js +2 -0
  13. package/bundle/distribution-lock.json +2 -2
  14. package/bundle/gateway-maintenance.js +1 -0
  15. package/bundle/global-rules/AGENTS.md +24 -20
  16. package/bundle/index.js +135 -25
  17. package/bundle/intervention-state.js +1 -1
  18. package/bundle/link-contract.js +9 -4
  19. package/bundle/managed-closeout.js +45 -0
  20. package/bundle/managed-cloud-policy.js +19 -0
  21. package/bundle/managed-command.js +71 -0
  22. package/bundle/managed-development-contract.js +43 -0
  23. package/bundle/managed-development.js +86 -0
  24. package/bundle/managed-endpoint.js +282 -0
  25. package/bundle/managed-execution.js +207 -0
  26. package/bundle/managed-policy.js +20 -0
  27. package/bundle/managed-runtime.js +186 -0
  28. package/bundle/managed-search.js +57 -0
  29. package/bundle/managed-tools.js +17 -0
  30. package/bundle/managed-triggers.js +85 -0
  31. package/bundle/native-context.js +4 -2
  32. package/bundle/native-entry.js +6 -2
  33. package/bundle/native-human-context.js +29 -0
  34. package/bundle/native-model-choice.js +12 -0
  35. package/bundle/native-rules/continuity.md +13 -0
  36. package/bundle/native-rules/execution.md +36 -0
  37. package/bundle/native-tools.js +52 -19
  38. package/bundle/notification-artifacts.js +26 -10
  39. package/bundle/notification-endpoint.js +1 -1
  40. package/bundle/notification-feishu.js +2 -2
  41. package/bundle/notification-media-endpoint.js +4 -3
  42. package/bundle/notification-observations.js +45 -0
  43. package/bundle/onboarding-cloud-package.js +1 -1
  44. package/bundle/onboarding-cloud.js +1 -1
  45. package/bundle/onboarding-file-contract.js +3 -2
  46. package/bundle/onboarding-file-transfer.js +4 -4
  47. package/bundle/onboarding-native-probe.js +1 -1
  48. package/bundle/onboarding-runtime-activation.js +1 -1
  49. package/bundle/onboarding-runtime-extension.js +2 -2
  50. package/bundle/package-lock.json +2 -2
  51. package/bundle/package.json +6 -4
  52. package/bundle/peer-file-review.js +1 -1
  53. package/bundle/peer-files.js +8 -4
  54. package/bundle/recovery.js +1 -1
  55. package/bundle/repository-gitee.js +12 -0
  56. package/bundle/repository-managed.js +1 -0
  57. package/bundle/runtime-im-lock.json +3409 -2849
  58. package/bundle/runtime-im-package.json +5 -5
  59. package/bundle/runtime-lock.json +3409 -2849
  60. package/bundle/runtime-package.json +5 -5
  61. package/bundle/schedule-ledger.js +35 -0
  62. package/bundle/search-contract.js +11 -1
  63. package/bundle/skills/task-approval/SKILL.md +20 -1
  64. package/bundle/skills/task-development/SKILL.md +30 -13
  65. package/bundle/skills/task-direct-link/SKILL.md +39 -13
  66. package/bundle/skills/task-file-transfer/SKILL.md +2 -2
  67. package/bundle/skills/task-notify/SKILL.md +2 -0
  68. package/bundle/skills/task-repository/SKILL.md +10 -1
  69. package/bundle/skills/task-research/SKILL.md +17 -2
  70. package/bundle/source-assessment.js +38 -0
  71. package/bundle/source-gitee.js +52 -0
  72. package/bundle/source-intake.js +137 -0
  73. package/bundle/source-policy-envelope.js +29 -0
  74. package/bundle/source-routing-tools.js +12 -0
  75. package/bundle/source-routing.js +218 -0
  76. package/bundle/source-search.js +2 -1
  77. package/bundle/store.js +6 -5
  78. package/bundle/task-artifacts.js +51 -12
  79. package/bundle/task-board.js +8 -4
  80. package/bundle/task-catalog.js +4 -3
  81. package/bundle/task-context.js +2 -2
  82. package/bundle/task-continuity-tools.js +14 -0
  83. package/bundle/task-continuity.js +248 -0
  84. package/bundle/task-envelope.js +31 -20
  85. package/bundle/task-notifications.js +6 -2
  86. package/bundle/task-observer-model.js +25 -0
  87. package/bundle/task-observer.js +120 -0
  88. package/bundle/task-results.js +1 -1
  89. package/bundle/task-scheduler.js +3 -3
  90. package/bundle/task-trigger-time.js +17 -12
  91. package/bundle/task-triggers.js +5 -30
  92. package/bundle/task-worker.js +10 -2
  93. package/bundle/task-workflows.js +2 -1
  94. package/bundle/workbench-client.js +165 -30
  95. package/bundle/workbench-decisions.js +22 -5
  96. package/bundle/workbench-host.js +5 -3
  97. package/bundle/worker-tools.js +8 -1
  98. package/compatibility.json +43 -3
  99. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  // Application protocol, independent of plugin semver, IM transports and task authority.
2
- export const VERSION='0.33.4';
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.1';
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
+ }
@@ -0,0 +1,43 @@
1
+ // Task-level agreement only. Native CLI remains the executor; IM is not authority.
2
+ const fail=s=>{throw Error('TASK_DEVELOPMENT_'+s);};
3
+ const object=x=>x&&typeof x==='object'&&!Array.isArray(x);
4
+ const text=(x,n=1000)=>typeof x==='string'&&x.trim()===x&&!!x&&!/[\x00-\x1f\x7f]/.test(x)&&x.length<=n;
5
+ export const relativeFile=x=>text(x,500)&&!x.startsWith('/')&&!x.includes('\\')&&x.split('/').every(p=>p&&p!=='.'&&p!=='..'&&p!=='.git'&&!p.startsWith('.env'));
6
+ export const branchName=x=>text(x,200)&&!x.startsWith('-')&&!/[\s~^:?*\[\\]/.test(x)&&!x.includes('..')&&!x.includes('@{')&&!x.endsWith('.')&&!x.endsWith('/')&&!x.endsWith('.lock');
7
+ export function developmentContract(x,role){
8
+ if(!object(x)||Object.keys(x).some(k=>!['delivery','branch','remote','base','checks','no_checks_reason','artifacts','notification'].includes(k))||!['workspace','commit','push','pr'].includes(x.delivery)||(x.delivery!=='workspace'||x.branch!==undefined)&&!branchName(x.branch))fail('CONTRACT_INVALID');
9
+ if(role==='local'&&(x.delivery!=='workspace'||x.branch!==undefined))fail('LOCAL_CLI_UNAVAILABLE');
10
+ if(!Array.isArray(x.checks)||x.checks.length>16||x.checks.some(c=>!object(c)||Object.keys(c).some(k=>!['name','command','inputs'].includes(k))||!text(c.name,80)||!text(c.command,2000)||c.inputs!==undefined&&(!Array.isArray(c.inputs)||!c.inputs.length||c.inputs.length>256||c.inputs.some(p=>!relativeFile(p))||new Set(c.inputs).size!==c.inputs.length))||new Set(x.checks.map(c=>c.name)).size!==x.checks.length||new Set(x.checks.map(c=>c.command)).size!==x.checks.length)fail('CHECKS_INVALID');
11
+ if(!x.checks.length&&!text(x.no_checks_reason,600)||x.no_checks_reason!==undefined&&!text(x.no_checks_reason,600))fail('CHECKS_REQUIRED');
12
+ if(!Array.isArray(x.artifacts)||x.artifacts.length>32||x.artifacts.some(p=>!relativeFile(p))||new Set(x.artifacts).size!==x.artifacts.length)fail('ARTIFACTS_INVALID');
13
+ if(['push','pr'].includes(x.delivery)){
14
+ // A credential-free exact remote, not an inherited origin credential grant.
15
+ if(!text(x.remote,500)||!(/^https:\/\/[\w.-]+\/[\w./-]+$/.test(x.remote)||/^git@[\w.-]+:[\w./-]+$/.test(x.remote)||/^\/(?:[^/\x00-\x1f]+\/)*[^/\x00-\x1f]+$/.test(x.remote)&&!x.remote.split('/').some(p=>p==='.'||p==='..')))fail('REMOTE_INVALID');
16
+ }else if(x.remote!==undefined)fail('REMOTE_UNEXPECTED');
17
+ if(x.delivery==='pr'&&(!branchName(x.base)||x.base===x.branch||!/^https:\/\/github\.com\/[^/]+\/[^/]+(?:\.git)?$|^git@github\.com:[^/]+\/[^/]+(?:\.git)?$/.test(x.remote)))fail('PR_PLATFORM_UNAVAILABLE');
18
+ if(x.delivery!=='pr'&&x.base!==undefined)fail('BASE_UNEXPECTED');
19
+ if(x.notification!==undefined&&(!object(x.notification)||Object.keys(x.notification).some(k=>!['target_key','binding_revision'].includes(k))||!text(x.notification.target_key,200)||!Number.isInteger(x.notification.binding_revision)||x.notification.binding_revision<1||!x.artifacts.length))fail('NOTIFICATION_INVALID');
20
+ return structuredClone(x);
21
+ }
22
+ export const developmentSchema={type:'object',properties:{delivery:{type:'string',enum:['workspace','commit','push','pr']},branch:{type:'string'},remote:{type:'string'},base:{type:'string'},checks:{type:'array',items:{type:'object',properties:{name:{type:'string'},command:{type:'string'},inputs:{type:'array',items:{type:'string'},minItems:1,maxItems:256}},required:['name','command'],additionalProperties:false},maxItems:16},no_checks_reason:{type:'string'},artifacts:{type:'array',items:{type:'string'},maxItems:32},notification:{type:'object',properties:{target_key:{type:'string'},binding_revision:{type:'integer',minimum:1}},required:['target_key','binding_revision'],additionalProperties:false}},required:['delivery','checks','artifacts'],additionalProperties:false};
23
+
24
+ export function canonicalRemote(url){return url.replace(/^git@([^:]+):/,'https://$1/').replace(/\.git$/,'').replace(/\/$/,'');}
25
+ export function managedResourceBusy(db,resource){return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='managed_slots'").get()&&db.prepare('SELECT resources FROM managed_slots').all().some(r=>JSON.parse(r.resources).includes('repository:'+canonicalRemote(resource)));}
26
+ export function legacyResourceBusy(db,resources){for(const table of ['development_requests','delivery_actions'])if(db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(table))for(const r of db.prepare(`SELECT resource FROM ${table} WHERE state IN ('active','needs_review')`).all())if(resources.includes('repository:'+canonicalRemote(r.resource)))return true;return false;}
27
+
28
+ // An explicit-command guard, not a shell interpreter or an unbypassable sandbox.
29
+ // Complex shell indirection must not be advertised as verified destination scope.
30
+ export function assertCliDestination(command,d){
31
+ if(/\bgit\s+(?:-C|--git-dir|--work-tree)|\bcd\s/.test(command))fail('CLI_WORKDIR_CHANGED');
32
+ const words=s=>(s.match(/"[^"\n]*"|'[^'\n]*'|[^\s]+/g)??[]).map(x=>/^['"]/.test(x)?x.slice(1,-1):x);
33
+ for(const match of command.matchAll(/\bgit\s+push\s+([^;&|\n]+)/g)){
34
+ const args=words(match[1]);if(['-u','--set-upstream'].includes(args[0]))args.shift();
35
+ if(args.length!==2||!['origin',d.remote].includes(args[0])||![d.branch,'HEAD:'+d.branch,'HEAD:refs/heads/'+d.branch].includes(args[1]))fail('CLI_DESTINATION_CHANGED');
36
+ }
37
+ if(/\bgit\s+push(?:\s*$|[;&|\n])/.test(command))fail('CLI_DESTINATION_REQUIRED');
38
+ for(const match of command.matchAll(/\bgh\s+pr\s+create\s+([^;&|\n]+)/g)){
39
+ const args=words(match[1]),value=flag=>args.filter(x=>x===flag).length===1?args[args.indexOf(flag)+1]:null,repo=canonicalRemote(d.remote).replace('https://github.com/','');
40
+ if(value('--repo')!==repo||value('--base')!==d.base||value('--head')!==d.branch)fail('CLI_DESTINATION_CHANGED');
41
+ }
42
+ if(/\bgh\s+pr\s+create\s*$/.test(command))fail('CLI_DESTINATION_REQUIRED');
43
+ }
@@ -0,0 +1,86 @@
1
+ import {autonomous} from './managed-cloud-policy.js';
2
+ // Bounded, read-only observations around native tools. Never executes Git writes,
3
+ // disables hooks, manufactures a PR or treats model review as independent proof.
4
+ import {execFileSync} from 'node:child_process';
5
+ import {createHash} from 'node:crypto';
6
+ import {realpathSync,readdirSync,lstatSync,openSync,fstatSync,readFileSync,closeSync,constants} from 'node:fs';
7
+ import {resolve,relative,isAbsolute} from 'node:path';
8
+ import {canonicalRemote,assertCliDestination} from './managed-development-contract.js';
9
+ import {compareText} from './delivery-preview.js';
10
+ const json=JSON.stringify,sha=x=>createHash('sha256').update(x).digest('hex');
11
+ const fail=s=>{throw Error('TASK_DEVELOPMENT_'+s);};
12
+ export function gitRead(cwd,args){try{return execFileSync('/usr/bin/git',['--no-optional-locks',...args],{cwd,encoding:'utf8',timeout:10000,maxBuffer:1048576,stdio:['ignore','pipe','pipe'],env:{...process.env,GIT_TERMINAL_PROMPT:'0',GIT_OPTIONAL_LOCKS:'0',GIT_EXTERNAL_DIFF:'',GIT_PAGER:'cat'}}).trimEnd();}catch{fail('GIT_READ_UNAVAILABLE');}}
13
+ export function repositoryFacts(cwd){return {root:realpathSync(gitRead(cwd,['rev-parse','--show-toplevel'])),common:realpathSync(resolve(cwd,gitRead(cwd,['rev-parse','--git-common-dir']))),branch:gitRead(cwd,['symbolic-ref','--short','HEAD']),head:gitRead(cwd,['rev-parse','HEAD'])};}
14
+ export function snapshotFile(root,path,max=64*1024*1024){
15
+ const full=resolve(root,path),rel=relative(root,full);if(!rel||rel==='..'||rel.startsWith('../')||isAbsolute(rel)||realpathSync(full)!==full)fail('ARTIFACT_PATH_UNSAFE');
16
+ let fd;try{fd=openSync(full,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);const s=fstatSync(fd);if(!s.isFile()||s.nlink!==1||s.size>max)fail('FILE_UNSAFE_OR_TOO_LARGE');const bytes=readFileSync(fd),end=fstatSync(fd),linked=lstatSync(full);if(realpathSync(full)!==full||linked.dev!==end.dev||linked.ino!==end.ino||end.size!==s.size||end.mtimeMs!==s.mtimeMs||bytes.length!==s.size||end.nlink!==1)fail('FILE_CHANGED');return {bytes,sha256:sha(bytes),size:bytes.length,mode:s.mode&0o111?1:0};}finally{if(fd!==undefined)closeSync(fd);}
17
+ }
18
+ export function treeSnapshot(cwd,{plain=false,artifacts=[]}={}){
19
+ const facts=plain?{root:realpathSync(cwd),common:realpathSync(cwd),branch:null,head:null}:repositoryFacts(cwd);if(facts.root!==cwd)fail('REPOSITORY_ROOT_REQUIRED');
20
+ let walked=0;const walk=(dir='')=>{if(++walked>5000||dir.split('/').length>64)fail('TREE_SCAN_LIMIT');return readdirSync(resolve(cwd,dir),{withFileTypes:true}).flatMap(e=>{if(['.git','node_modules'].includes(e.name))return [];const path=dir?dir+'/'+e.name:e.name;if(++walked>5000)fail('TREE_SCAN_LIMIT');return e.isDirectory()?walk(path):[path];});};
21
+ const baseNames=plain?walk().sort():[...new Set(gitRead(cwd,['ls-files','-z','--cached','--others','--exclude-standard']).split('\0').filter(Boolean))].sort();const names=[...new Set([...baseNames,...artifacts])].sort(),files={},issues=[];let total=0;
22
+ if(names.length>5000)fail('TREE_SCAN_LIMIT');
23
+ for(const name of names){const path=resolve(cwd,name);let s;try{s=lstatSync(path);}catch(e){if(e.code==='ENOENT')continue;throw e;}
24
+ if(!s.isFile()||s.isSymbolicLink()||s.nlink!==1){issues.push({path:name,reason:'link_or_non_regular'});continue;}
25
+ total+=s.size;if(total>64*1024*1024)fail('TREE_SCAN_LIMIT');const f=snapshotFile(cwd,name);files[name]={sha256:f.sha256,size:f.size,mode:f.mode};
26
+ }
27
+ return {...facts,files,issues,fingerprint:sha(json({files,issues})),dirty:plain?names.length>0:!!gitRead(cwd,['status','--porcelain','--untracked-files=all'])};
28
+ }
29
+ export function changedFiles(before,after){return [...new Set([...Object.keys(before.files),...Object.keys(after.files)])].sort().filter(p=>json(before.files[p])!==json(after.files[p])).map(path=>({path,kind:!before.files[path]?'added':!after.files[path]?'deleted':'modified',before:before.files[path]??null,after:after.files[path]??null}));}
30
+ export function repositoryResources(cwd,contract){const f=repositoryFacts(cwd);let remote=contract.remote;try{remote??=gitRead(cwd,['remote','get-url','--push','origin']);}catch{}return ['gitdir:'+f.common,...(remote?['repository:'+canonicalRemote(remote)]:[])];}
31
+ export class ManagedDevelopment {
32
+ constructor(endpoint){this.e=endpoint;this.db=endpoint.db;this.db.exec(`CREATE TABLE IF NOT EXISTS managed_development_baselines(task_id TEXT NOT NULL,revision INTEGER NOT NULL,record TEXT NOT NULL,PRIMARY KEY(task_id,revision)); CREATE TABLE IF NOT EXISTS managed_development_checks(task_id TEXT NOT NULL,revision INTEGER NOT NULL,name TEXT NOT NULL,record TEXT NOT NULL,PRIMARY KEY(task_id,revision,name));`);}
33
+ tree(b){return treeSnapshot(b.item.spec.cwd,{plain:!b.item.spec.development.branch,artifacts:b.item.spec.development.artifacts});}
34
+ begin(b){if(!b.item.spec.development)return;const now=this.tree(b),d=b.item.spec.development;if(now.branch!==(d.branch??null))fail('BRANCH_CHANGED');
35
+ const old=this.db.prepare('SELECT record FROM managed_development_baselines WHERE task_id=? AND revision=?').get(b.item.task_id,b.item.revision);
36
+ if(this.e.g.c.role==='local'&&d.branch){const rel=relative(now.root,now.common);if(rel==='..'||rel.startsWith('../')||isAbsolute(rel))fail('LOCAL_GIT_SCOPE_DENIED');}
37
+ if(old){b.developmentBaseline=JSON.parse(old.record);if(b.developmentBaseline.common!==now.common)fail('REPOSITORY_CHANGED');}
38
+ else{b.developmentBaseline=now;this.db.prepare('INSERT INTO managed_development_baselines VALUES (?,?,?)').run(b.item.task_id,b.item.revision,json(now));}
39
+ }
40
+ before(b,exec,cwd){const d=b.item.spec.development;if(!d)return null;
41
+ if(!autonomous(b.item.spec)&&exec.name==='bash'&&/\bgit\b[^\n;&|]*\b(?:commit|push)\b|\bgh\s+pr\s+create\b/.test(exec.arguments?.command??'')){
42
+ assertCliDestination(exec.arguments.command,d);const current=this.tree(b);this.assertRepository(b,current);if(cwd!==current.root)fail('CLI_WORKDIR_CHANGED');
43
+ // Scope-level controls, not a shell sandbox; arbitrary programs retain account permissions.
44
+ if(/\bpush\b|\bgh\s+pr\s+create\b/.test(exec.arguments.command)){this.assertChecks(b,current);if(gitRead(current.root,['remote','get-url','--push','origin'])!==d.remote)fail('REMOTE_CHANGED');}
45
+ }
46
+ const command=exec.name==='bash'?exec.arguments?.command:exec.name==='managed_command'?exec.arguments?.name:null;
47
+ // This exact stderr redirection preserves the contracted command's exit status.
48
+ // No general shell normalization: pipelines, extra commands and wrappers differ.
49
+ const check=d.checks.find(c=>c.command===command||(exec.name==='bash'&&command===c.command+' 2>&1'));if(!check||cwd!==b.item.spec.cwd)return null;
50
+ const before=this.checkVersion(b,check,this.tree(b));this.saveCheck(b,check.name,{state:'running',fingerprint:before,at:this.e.g.now()});return {check,before};
51
+ }
52
+ after(b,token,result){if(!token)return;const after=this.checkVersion(b,token.check,this.tree(b)),v=result?.value??result,code=v?.exitCode??v?.exit_code,ok=!result?.isError&&code===0&&!v.timedOut&&!v.timed_out&&!v.aborted&&!v.signal;
53
+ this.saveCheck(b,token.check.name,{state:ok&&after===token.before?'passed':ok?'changed_during_check':v?.kind==='background'||v?.kind==='promoted'?'running':typeof code==='number'?'failed':'unknown',exit_code:code??null,fingerprint:token.before,at:this.e.g.now()});
54
+ }
55
+ // Explicit relevant inputs bind the check to real bytes, not receipt/report
56
+ // bookkeeping. No directory-wide output exclusion; omitted inputs remain conservative.
57
+ checkVersion(b,check,current){
58
+ if(!autonomous(b.item.spec)||!check.inputs)return current.fingerprint;
59
+ return sha(json(check.inputs.map(path=>{const f=current.files[path];if(!f)fail('CHECK_INPUT_UNAVAILABLE');return [path,f];})));
60
+ }
61
+ saveCheck(b,name,record){record={...record,command:b.item.spec.development.checks.find(c=>c.name===name)?.command};this.db.prepare('INSERT OR REPLACE INTO managed_development_checks VALUES (?,?,?,?)').run(b.item.task_id,b.item.revision,name,json(record));}
62
+ checks(b,current){return b.item.spec.development.checks.map(c=>{const r=this.db.prepare('SELECT record FROM managed_development_checks WHERE task_id=? AND revision=? AND name=?').get(b.item.task_id,b.item.revision,c.name),stored=r?JSON.parse(r.record):null,value=stored?.command===c.command?stored:{state:'not_run'},state=value.state==='passed'&&value.fingerprint!==this.checkVersion(b,c,current)?'stale':value.state;return {name:c.name,command:c.command,...value,state,...(state!=='passed'?{required_invocation:this.e.g.c.role==='local'?{tool:'managed_command',arguments:{name:c.command}}:{tool:'bash',arguments:{description:'Run named check: '+c.name,command:c.command,workdir:b.item.spec.cwd,run_in_background:false}}}:{})};});}
63
+ assertRepository(b,current){if(current.common!==b.developmentBaseline.common||current.branch!==(b.item.spec.development.branch??null))fail('REPOSITORY_CHANGED');}
64
+ assertChecks(b,current){this.assertRepository(b,current);if(current.issues.length&&!autonomous(b.item.spec))fail('TREE_INCOMPLETE');if(this.checks(b,current).some(c=>c.state!=='passed'))fail('CHECKS_NOT_CURRENT: read managed_evidence and copy each required_invocation. Pass cwd as bash workdir; do not prepend cd, append echo/exit-code printing, wrap or pipe the command. Wait for its foreground exit after all file changes, then retry ready. Do not repeat ready without fixing the check.');}
65
+ inspect(b,{review,remote=false}={}){
66
+ if(!b.item.spec.development)fail('CONTRACT_REQUIRED');const cwd=b.item.spec.cwd,current=this.tree(b);this.assertRepository(b,current);
67
+ let patch='',patchTruncated=false;try{if(current.head){const raw=gitRead(cwd,['diff','--no-ext-diff','--no-textconv','--no-color','--unified=3',b.developmentBaseline.head,'--']);patch=raw.slice(0,32768);patchTruncated=raw.length>32768;}}catch{patchTruncated=true;}
68
+ let diff;try{diff=current.head?gitRead(cwd,['diff','--no-ext-diff','--no-textconv','--stat',b.developmentBaseline.head,'--']):'普通文件任务,无 Git 差异';}catch{diff='差异统计读取失败';}
69
+ const changes=changedFiles(b.developmentBaseline,current),details=changes.slice(0,16).map(c=>{if(c.kind!=='added'||current.issues.some(x=>x.path===c.path))return c;const bytes=snapshotFile(cwd,c.path,64*1024*1024).bytes;return {...c,preview:compareText(Buffer.alloc(0),bytes)};});
70
+ const evidence={schema:1,at:this.e.g.now(),repository:{root:current.root,branch:current.branch,head:current.head,baseline_head:b.developmentBaseline.head},fingerprint:current.fingerprint,preexisting_changes:b.developmentBaseline.dirty,changes:details,truncated:changes.length>16||diff.length>12000||patchTruncated,git_diff:patch,git_diff_includes_preexisting:true,git_diff_stat:diff.slice(0,12000),tree_issues:current.issues,checks:this.checks(b,current),no_checks_reason:b.item.spec.development.no_checks_reason??null,review:review?{source:'worker_self_review',summary:review}:null,delivery:{mode:b.item.spec.development.delivery,state:'not_checked'},user_accepted:false,published:false,deployed:false};
71
+ if(remote)evidence.delivery=this.delivery(b,current);return evidence;
72
+ }
73
+ delivery(b,current){const d=b.item.spec.development;this.assertChecks(b,current);
74
+ if(d.delivery==='workspace')return {mode:d.delivery,state:'workspace_ready'};
75
+ if(current.dirty||current.head===b.developmentBaseline.head)fail('COMMIT_NOT_VERIFIED');
76
+ if(d.delivery==='commit')return {mode:d.delivery,state:'commit_verified',commit:current.head};
77
+ // A readback can fail/timeout; never turns into another push or PR creation.
78
+ const line=gitRead(current.root,['ls-remote','--exit-code',d.remote,'refs/heads/'+d.branch]);if(line.split(/\s+/)[0]!==current.head)fail('REMOTE_NOT_VERIFIED');
79
+ if(d.delivery==='push')return {mode:d.delivery,state:'remote_commit_verified',commit:current.head};
80
+ const repo=canonicalRemote(d.remote).replace('https://github.com/','');let pr;
81
+ try{const raw=execFileSync('gh',['pr','view',d.branch,'--repo',repo,'--json','url,headRefName,headRefOid,baseRefName,state,isDraft'],{cwd:current.root,encoding:'utf8',timeout:15000,maxBuffer:32768,stdio:['ignore','pipe','pipe'],env:{...process.env,GH_PROMPT_DISABLED:'1'}});pr=JSON.parse(raw);}catch{fail('PR_READBACK_UNAVAILABLE');}
82
+ if(pr.headRefName!==d.branch||pr.headRefOid!==current.head||pr.baseRefName!==d.base||pr.state!=='OPEN'||pr.isDraft!==false||typeof pr.url!=='string'||!pr.url.startsWith('https://github.com/'+repo+'/pull/')||!/\/pull\/\d+$/.test(pr.url))fail('PR_NOT_VERIFIED');
83
+ return {mode:d.delivery,state:'pr_verified',commit:current.head,url:pr.url};
84
+ }
85
+ ready(b,review){if(!b.item.spec.development)return null;if(typeof review!=='string'||!review.trim()||review.length>2000)fail('REVIEW_REQUIRED');const current=this.tree(b);this.assertChecks(b,current);return this.inspect(b,{review,remote:true});}
86
+ }