@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.0.2

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 (151) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +1 -0
  3. package/README.md +124 -2
  4. package/THIRD_PARTY_NOTICES.md +66 -0
  5. package/ZCode-LICENSE +202 -0
  6. package/dist/index.mjs +4871 -0
  7. package/dist/native-command-plan.LICENSE.txt +221 -0
  8. package/dist/native-command-plan.json +16 -0
  9. package/dist/native-command-plan.mjs +8 -0
  10. package/dist/rpc-entry.mjs +5682 -0
  11. package/package.json +169 -4
  12. package/src/native/a2a-receipt-source.mjs +19 -0
  13. package/src/native/a2a-tools.mjs +517 -0
  14. package/src/native/activity-status.mjs +30 -0
  15. package/src/native/artifact-codec.mjs +481 -0
  16. package/src/native/artifact-store.mjs +1162 -0
  17. package/src/native/assistant-stream.mjs +76 -0
  18. package/src/native/background-command-port.mjs +95 -0
  19. package/src/native/browser-action-observation.mjs +43 -0
  20. package/src/native/browser-availability.mjs +22 -0
  21. package/src/native/browser-canvas-metrics.mjs +71 -0
  22. package/src/native/browser-dom.mjs +193 -0
  23. package/src/native/browser-playwright.mjs +1249 -0
  24. package/src/native/browser-vision.mjs +369 -0
  25. package/src/native/browser-workspace-policy.mjs +22 -0
  26. package/src/native/browser-workspace-protocol.mjs +95 -0
  27. package/src/native/browser-workspace-server.mjs +164 -0
  28. package/src/native/browser-workspace.mjs +355 -0
  29. package/src/native/captured-source.mjs +3 -0
  30. package/src/native/child-budget-allocation.mjs +87 -0
  31. package/src/native/child-capabilities.mjs +35 -0
  32. package/src/native/child-command-resource.mjs +20 -0
  33. package/src/native/child-history.mjs +188 -0
  34. package/src/native/child-merge.mjs +98 -0
  35. package/src/native/child-model-surface.mjs +19 -0
  36. package/src/native/child-observation.mjs +100 -0
  37. package/src/native/child-owner.mjs +620 -0
  38. package/src/native/child-publication.mjs +76 -0
  39. package/src/native/child-readers.mjs +366 -0
  40. package/src/native/child-tools.mjs +46 -0
  41. package/src/native/child-workspace-binding.mjs +29 -0
  42. package/src/native/child-workspace-tools.mjs +1 -0
  43. package/src/native/child-workspaces.mjs +655 -0
  44. package/src/native/children.mjs +365 -0
  45. package/src/native/command-plan-worker.mjs +14 -0
  46. package/src/native/command-plan.mjs +31 -0
  47. package/src/native/command-progress.mjs +64 -0
  48. package/src/native/command-shell.mjs +9 -0
  49. package/src/native/command-workspace-observation.mjs +36 -0
  50. package/src/native/component-tools.mjs +280 -0
  51. package/src/native/context-counter.mjs +133 -0
  52. package/src/native/context-markers.mjs +51 -0
  53. package/src/native/context-profile.mjs +72 -0
  54. package/src/native/context-status.mjs +27 -0
  55. package/src/native/conversation-command-control.mjs +133 -0
  56. package/src/native/delivery-timeline.mjs +39 -0
  57. package/src/native/dsh-background-ownership.mjs +102 -0
  58. package/src/native/dsh-managed-ownership.mjs +23 -0
  59. package/src/native/dsh-turn-contract.mjs +3 -0
  60. package/src/native/dsh-turn-ownership.mjs +54 -0
  61. package/src/native/dsh-turn-runtime.mjs +498 -0
  62. package/src/native/e2b-background-supervisor.mjs +347 -0
  63. package/src/native/e2b-background.mjs +368 -0
  64. package/src/native/e2b-command-state.mjs +40 -0
  65. package/src/native/e2b-command-tools.mjs +486 -0
  66. package/src/native/e2b-sandbox-owner.mjs +227 -0
  67. package/src/native/hook-executor.mjs +249 -0
  68. package/src/native/instruction-assembly.mjs +60 -0
  69. package/src/native/instruction-context.mjs +54 -0
  70. package/src/native/local-background-supervisor.mjs +108 -0
  71. package/src/native/local-background.mjs +207 -0
  72. package/src/native/local-command-evidence.mjs +57 -0
  73. package/src/native/local-command-policy-worker.mjs +197 -0
  74. package/src/native/local-command-policy.mjs +46 -0
  75. package/src/native/local-command-state.mjs +177 -0
  76. package/src/native/local-command-tools.mjs +302 -0
  77. package/src/native/local-execution-profile.mjs +36 -0
  78. package/src/native/local-output-policy.mjs +92 -0
  79. package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
  80. package/src/native/local-process-bin/darwin-x64/launcher +0 -0
  81. package/src/native/local-process-bin/linux-arm64/launcher +0 -0
  82. package/src/native/local-process-bin/linux-x64/launcher +0 -0
  83. package/src/native/local-process-launcher.c +183 -0
  84. package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
  85. package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
  86. package/src/native/local-process-licenses/NOTICE +21 -0
  87. package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
  88. package/src/native/local-process-manifest.json +32 -0
  89. package/src/native/local-process.mjs +246 -0
  90. package/src/native/local-resource-profile.mjs +32 -0
  91. package/src/native/memory-candidate.mjs +121 -0
  92. package/src/native/memory-extraction-run.mjs +60 -0
  93. package/src/native/memory-extraction-tools.mjs +116 -0
  94. package/src/native/memory-recall.mjs +65 -0
  95. package/src/native/memory-store.mjs +334 -0
  96. package/src/native/memory-tools.mjs +329 -0
  97. package/src/native/model-anthropic-cache.mjs +49 -0
  98. package/src/native/model-anthropic-tool-search.mjs +85 -0
  99. package/src/native/model-anthropic.mjs +753 -0
  100. package/src/native/model-chat-usage.mjs +24 -0
  101. package/src/native/model-inline-reasoning.mjs +111 -0
  102. package/src/native/model-media.mjs +174 -0
  103. package/src/native/model-normalize.mjs +228 -0
  104. package/src/native/model-openai-chat.mjs +561 -0
  105. package/src/native/model-port.mjs +361 -0
  106. package/src/native/model-provider-compat.mjs +129 -0
  107. package/src/native/model-replay.mjs +153 -0
  108. package/src/native/model-request.mjs +15 -0
  109. package/src/native/model-responses.mjs +1205 -0
  110. package/src/native/model-sse.mjs +171 -0
  111. package/src/native/model-surface-host.mjs +27 -0
  112. package/src/native/model-tool-search-policy.mjs +16 -0
  113. package/src/native/model-transport-observation.mjs +27 -0
  114. package/src/native/owned-child.mjs +114 -0
  115. package/src/native/owned-command.mjs +106 -0
  116. package/src/native/python-syntax-worker.mjs +60 -0
  117. package/src/native/python-syntax.mjs +50 -0
  118. package/src/native/read-interruption.mjs +16 -0
  119. package/src/native/result-stream.mjs +63 -0
  120. package/src/native/ruff-ast.mjs +95 -0
  121. package/src/native/run-observation.mjs +128 -0
  122. package/src/native/session-delivery-journal.mjs +96 -0
  123. package/src/native/session-delivery.mjs +434 -0
  124. package/src/native/shell-analysis.mjs +50 -0
  125. package/src/native/skill-disclosure.mjs +404 -0
  126. package/src/native/skill-events.mjs +164 -0
  127. package/src/native/skill-tools.mjs +813 -0
  128. package/src/native/source-page.mjs +73 -0
  129. package/src/native/sqlite-session-inbox.mjs +42 -0
  130. package/src/native/sqlite-store.mjs +617 -0
  131. package/src/native/tool-authority.mjs +99 -0
  132. package/src/native/tool-guidance.mjs +12 -0
  133. package/src/native/tool-observation.mjs +75 -0
  134. package/src/native/tool-states.mjs +14 -0
  135. package/src/native/verification-authority.mjs +37 -0
  136. package/src/native/web-tools.mjs +643 -0
  137. package/src/native/workspace-checkpoints.mjs +312 -0
  138. package/src/native/workspace-context.mjs +61 -0
  139. package/src/native/workspace-display.mjs +39 -0
  140. package/src/native/workspace-edit-match.mjs +19 -0
  141. package/src/native/workspace-edit-worker.mjs +15 -0
  142. package/src/native/workspace-editing.mjs +447 -0
  143. package/src/native/workspace-image.mjs +72 -0
  144. package/src/native/workspace-instructions.mjs +107 -0
  145. package/src/native/workspace-mutation-lock.mjs +74 -0
  146. package/src/native/workspace-patch-worker.mjs +17 -0
  147. package/src/native/workspace-patch.mjs +17 -0
  148. package/src/native/workspace-paths.mjs +225 -0
  149. package/src/native/workspace-state.mjs +272 -0
  150. package/src/native/workspace-subject-observer.mjs +84 -0
  151. package/src/native/workspace-tools.mjs +1740 -0
@@ -0,0 +1,365 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical, configure, invariant, submissionOutputFormat, artifactToolDefinitions, isArtifactTool, toolDiscoveryDefinitions,
3
+ TOOL_SEARCH, DEFERRED_TOOL_CALL, TODO_READ, TODO_WRITE, RESPOND_TO_COORDINATOR, CHILD_TASK_POLICY,
4
+ COORDINATOR_RESPONSE_POLICY, todoToolDefinitions, coordinatorResponseDefinition,
5
+ CHILD_PLAN_POLICY, CHILD_PLAN_MODE_POLICY } from '@kingsoftcloud/agentkit-harness-core';
6
+ import { createNativeChildOwner, CHILD_ALLOCATION_POLICY, roleAllocationDefaults } from '@kingsoftcloud/agentkit-harness-node/native/child-owner';
7
+ import { createNativeChildResultTools } from '@kingsoftcloud/agentkit-harness-node/native/child-readers';
8
+ import { nativeChildModelSurface } from './child-model-surface.mjs';
9
+ import { createStudioNativeChildMerge } from './child-merge.mjs';
10
+ import { previousChildPublication } from '@kingsoftcloud/agentkit-harness-node/native/child-publication';
11
+ import { nativeA2aReceiptWaitSource } from './a2a-receipt-source.mjs';
12
+ import { nativeA2aToolDefinitions } from '@kingsoftcloud/agentkit-harness-node/native/a2a-tools';
13
+ import { NATIVE_CHILD_WORKSPACE_TOOLS, NATIVE_CHILD_COMMAND_TOOLS } from './child-workspace-tools.mjs';
14
+ import { validChildCommandResource } from './child-command-resource.mjs';
15
+ import { CHILD_CAPABILITY_POLICY, admitChildCapabilities } from './child-capabilities.mjs';
16
+ import { createNativeWorkspaceInstructions } from './workspace-instructions.mjs';
17
+ import { nativeWorkspaceContextBinding, validateNativeWorkspaceContext } from './workspace-context.mjs';
18
+ import { nativeInstructionTouches } from './instruction-context.mjs';
19
+
20
+ export const STUDIO_NATIVE_CHILD_CONTRACT = 'agentkit.studio-native-children/v2';
21
+ export const STUDIO_NATIVE_CHILD_SKILLS_CONTRACT = 'agentkit.studio-child-borrowed-skills/v1';
22
+ const workspaceTools = new Set(NATIVE_CHILD_WORKSPACE_TOOLS);
23
+ const commandTools = new Set(NATIVE_CHILD_COMMAND_TOOLS);
24
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
25
+ function validateInstructionWorkspace(childContext, isolated, parentContext) {
26
+ if (!childContext) return;
27
+ validateNativeWorkspaceContext(childContext);
28
+ const root = isolated?.root ?? parentContext?.workspaceRoot;
29
+ invariant(typeof root === 'string' && childContext.workspaceRoot === nativeWorkspaceContextBinding(root).workspaceRoot,
30
+ 'studio_native_child_instruction_workspace_changed');
31
+ }
32
+ function instructionPorts(reader, { context, input, store, scope, sessionId, tools }) {
33
+ if (!reader) return { context, ...(input ? { input } : {}) };
34
+ const builtinNames = new Set(tools.map(tool => tool.name));
35
+ return { context: { ...context, readSource: request => reader.readSource(request) }, input: { prepare: async request => {
36
+ const result = input ? await input.prepare(request) : { status: 'admitted', messages: [] };
37
+ if (result.status === 'rejected') return result;
38
+ const evidence = store.workspaceTouchEvidence ? store.workspaceTouchEvidence(scope, request.runId) : store.load(scope, request.runId);
39
+ const touches = nativeInstructionTouches(evidence, { sessionId, builtinNames, workspaceToolNames: workspaceTools,
40
+ workspace: reader.workspaceRoot });
41
+ return { ...result, instructionScopes: await reader.scopes(evidence.instructionScopes
42
+ ?? evidence.snapshot?.context.instructionScopes, touches, request.signal) };
43
+ } } };
44
+ }
45
+ const isDiscoveryControl = name => name === TOOL_SEARCH || name === DEFERRED_TOOL_CALL;
46
+ const gatewayToolNames = (tools, artifactPolicy, toolDiscoveryPolicy, childTaskPolicy) => tools.filter(tool => (!artifactPolicy || !isArtifactTool(tool.name))
47
+ && (!toolDiscoveryPolicy || !isDiscoveryControl(tool.name))
48
+ && (!childTaskPolicy || ![TODO_READ, TODO_WRITE, RESPOND_TO_COORDINATOR].includes(tool.name))).map(tool => tool.name);
49
+ const parentOutputSchema = contract => {
50
+ const schema = contract?.finalSubmissionSchema, key = contract?.finalSubmissionPayloadKey;
51
+ const output = key ? schema?.properties?.[key] : schema;
52
+ invariant(output?.type === 'object', 'studio_native_child_parent_output_unavailable');
53
+ return structuredClone(output);
54
+ };
55
+ const outputSchema = (role, args, contract) => {
56
+ invariant(args.output_format === undefined || ['text', 'json_object'].includes(args.output_format), 'studio_native_child_output_format_invalid');
57
+ invariant(args.output_contract === undefined || args.output_contract === 'parent' && args.output_format === 'json_object',
58
+ 'studio_native_child_parent_output_invalid');
59
+ invariant(args.output_format !== 'json_object' || role.outputSchema === null || role.outputSchema.type === 'object', 'studio_native_child_output_conflict');
60
+ if (args.output_contract === 'parent') {
61
+ const parent = parentOutputSchema(contract);
62
+ return { schema: role.outputSchema ? { type: 'object', allOf: [structuredClone(role.outputSchema), parent] } : parent,
63
+ parentOutputDigest: hash(parent) };
64
+ }
65
+ return { schema: role.outputSchema ?? (args.output_format === 'json_object' ? { type: 'object' } : null) };
66
+ };
67
+ const keys = ['name', 'instructions', 'description', 'tools', 'timeoutSeconds', 'maxTurns', 'maxTotalTokens', 'maxArtifacts',
68
+ 'maxToolCalls', 'concurrentSafe', 'outputSchema', 'dependsOn', 'failurePolicy', 'maxRetries', 'inheritSkills', 'inheritMcp', 'modelProfileId'];
69
+ const camel = key => key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
70
+ /** Revision-owned declarations use the same camel/snake aliases as SubAgentBinding.
71
+ * Under `childAllocationPolicy: 'parent-shared/v1'`, an omitted `maxTurns`/`timeoutSeconds`
72
+ * defaults to a bounded useful allowance (16 / 900s) instead of the legacy 4 / 120s, so the
73
+ * child's admission defers to the parent's actual remaining budget. Absent policy keeps
74
+ * the historical defaults so old normalized role shapes and digests are byte-identical. */
75
+ export function nativeChildRoles(spec = {}, { childAllocationPolicy } = {}) {
76
+ const defaults = roleAllocationDefaults(childAllocationPolicy);
77
+ invariant(!(spec.subAgents !== undefined && spec.sub_agents !== undefined), 'studio_native_child_roles_conflict');
78
+ const raw = spec.subAgents ?? spec.sub_agents ?? [];
79
+ invariant(Array.isArray(raw) && raw.length <= 32, 'studio_native_child_roles_invalid');
80
+ const roles = raw.map(value => {
81
+ invariant(value && typeof value === 'object' && !Array.isArray(value), 'studio_native_child_role_invalid');
82
+ const role = {};
83
+ for (const [key, item] of Object.entries(value)) {
84
+ const normalized = camel(key);
85
+ invariant(keys.includes(normalized) && !Object.hasOwn(role, normalized), 'studio_native_child_role_option_unsupported');
86
+ role[normalized] = structuredClone(item);
87
+ }
88
+ const result = { description: '', tools: [], timeoutSeconds: defaults.timeoutSeconds, maxTurns: defaults.maxTurns,
89
+ maxTotalTokens: null, maxArtifacts: null, maxToolCalls: defaults.maxToolCalls, concurrentSafe: false, outputSchema: null,
90
+ dependsOn: [], failurePolicy: 'propagate', maxRetries: 0, inheritSkills: true, inheritMcp: false, modelProfileId: null, ...role };
91
+ invariant(typeof result.name === 'string' && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(result.name)
92
+ && typeof result.instructions === 'string' && result.instructions.trim() && result.instructions.length <= 32768
93
+ && typeof result.description === 'string' && result.description.length <= 1024
94
+ && Array.isArray(result.tools) && result.tools.length <= 64 && new Set(result.tools).size === result.tools.length
95
+ && result.tools.every(name => typeof name === 'string' && name.trim())
96
+ && typeof result.timeoutSeconds === 'number' && Number.isFinite(result.timeoutSeconds) && result.timeoutSeconds > 0 && result.timeoutSeconds <= 3600
97
+ && Number.isSafeInteger(result.maxTurns) && result.maxTurns >= 1 && result.maxTurns <= 32
98
+ && (result.maxToolCalls === null || Number.isSafeInteger(result.maxToolCalls) && result.maxToolCalls >= 0 && result.maxToolCalls <= 100000)
99
+ && ['concurrentSafe', 'inheritSkills', 'inheritMcp'].every(key => typeof result[key] === 'boolean')
100
+ && (result.modelProfileId === null || typeof result.modelProfileId === 'string' && result.modelProfileId.trim())
101
+ && (result.outputSchema === null || result.outputSchema && typeof result.outputSchema === 'object' && !Array.isArray(result.outputSchema)),
102
+ 'studio_native_child_role_invalid');
103
+ invariant(result.maxTotalTokens === null && result.maxArtifacts === null && Array.isArray(result.dependsOn)
104
+ && result.dependsOn.length === 0 && result.failurePolicy === 'propagate' && result.maxRetries === 0,
105
+ 'studio_native_child_policy_unavailable');
106
+ return result;
107
+ });
108
+ invariant(new Set(roles.map(role => role.name)).size === roles.length, 'studio_native_child_role_duplicate');
109
+ return roles;
110
+ }
111
+
112
+ /** Bind every child to a declared role and its own scoped tool state. */
113
+ export async function createNativeChildren({ roles, scope, sessionId, store, tools, hasSkills, hasMcp, mcpBinding, skillBinding, skillInstructions, workspaceContext,
114
+ resolveModel, bindTools, verificationFactory, fileWorkspaces, commandResource, isClosed, onStream, a2aBinding, waitForChildReceipt, mediaSources = new Map(), artifactPolicy, artifactFactory, sourceReadPolicy, childTaskPolicy, childPlanPolicy, childAllocationPolicy, childCapabilityPolicy, outputRecoveryPolicy, toolExecutionPolicy, modelSurface, goalContractFactory = ({ goal }) => ({ objective: goal }) }) {
115
+ if (!roles.length) return null;
116
+ if (workspaceContext !== undefined) {
117
+ validateNativeWorkspaceContext(workspaceContext);
118
+ invariant(workspaceContext.instructionSource, 'studio_native_child_instruction_source_unbound');
119
+ }
120
+ invariant(typeof goalContractFactory === 'function', 'native_child_goal_contract_factory_invalid');
121
+ invariant(childAllocationPolicy === undefined || childAllocationPolicy === CHILD_ALLOCATION_POLICY, 'studio_native_child_allocation_policy_invalid');
122
+ invariant(childCapabilityPolicy === undefined || childCapabilityPolicy === CHILD_CAPABILITY_POLICY, 'studio_native_child_capability_policy_invalid');
123
+ invariant(toolExecutionPolicy === undefined || toolExecutionPolicy === 'owner-execution/v1', 'studio_native_child_execution_policy_invalid');
124
+ invariant(outputRecoveryPolicy === undefined || outputRecoveryPolicy === 'response-boundary/v2', 'studio_native_child_output_policy_invalid');
125
+ invariant(childTaskPolicy === undefined || childTaskPolicy === CHILD_TASK_POLICY, 'studio_native_child_task_policy_invalid');
126
+ invariant(childPlanPolicy === undefined || childPlanPolicy === CHILD_PLAN_POLICY, 'studio_native_child_plan_policy_invalid');
127
+ invariant(artifactPolicy == null || artifactPolicy === 'runtime-artifacts/v1' && typeof artifactFactory === 'function',
128
+ 'studio_native_child_artifact_binding_invalid');
129
+ invariant(sourceReadPolicy == null || sourceReadPolicy === 'studio-source-read/v1' && artifactPolicy === 'runtime-artifacts/v1',
130
+ 'studio_native_child_source_binding_invalid');
131
+ const byName = new Map(roles.map(role => [role.name, role]));
132
+ const bindings = new Map();
133
+ for (const role of roles) {
134
+ const inheritedSkills = role.inheritSkills && hasSkills;
135
+ invariant(!inheritedSkills || skillBinding?.contract === STUDIO_NATIVE_CHILD_SKILLS_CONTRACT
136
+ && /^[a-f0-9]{64}$/.test(skillBinding.bindingDigest) && Array.isArray(skillBinding.toolNames)
137
+ && skillBinding.toolNames.length === 4 && new Set(skillBinding.toolNames).size === 4
138
+ && typeof skillInstructions === 'string', 'studio_native_child_skill_binding_unavailable');
139
+ const inheritedMcp = role.inheritMcp && hasMcp;
140
+ invariant(!inheritedMcp || mcpBinding?.contract === 'agentkit.studio-child-borrowed-mcp/v1'
141
+ && /^[a-f0-9]{64}$/.test(mcpBinding.bindingDigest) && Array.isArray(mcpBinding.toolNames)
142
+ && mcpBinding.toolNames.length && new Set(mcpBinding.toolNames).size === mcpBinding.toolNames.length,
143
+ 'studio_native_child_mcp_binding_unavailable');
144
+ const granted = role.tools.map(name => tools.find(tool => tool.name === name));
145
+ const a2aNames = new Set(a2aBinding ? nativeA2aToolDefinitions({ receiptObservation: true }).map(tool => tool.name) : []);
146
+ const childA2a = granted.some(tool => a2aNames.has(tool?.name)) ? a2aBinding : null;
147
+ const usesCommands = granted.some(tool => commandTools.has(tool?.name));
148
+ invariant(!usesCommands || validChildCommandResource(commandResource) && fileWorkspaces && !role.concurrentSafe,
149
+ 'studio_native_child_command_resource_unavailable');
150
+ const isolatedWrite = usesCommands || granted.some(tool => tool && !tool.readOnly && workspaceTools.has(tool.name));
151
+ invariant(granted.every(tool => tool && !tool.childBudgetPolicy && (workspaceTools.has(tool.name)
152
+ || usesCommands && commandTools.has(tool.name)
153
+ || a2aNames.has(tool.name) && (!role.concurrentSafe || tool.readOnly))
154
+ && !mcpBinding?.toolNames.includes(tool.name))
155
+ && (!isolatedWrite || fileWorkspaces && !role.concurrentSafe),
156
+ 'studio_native_child_write_role_requires_isolation');
157
+ const mcpTools = inheritedMcp ? mcpBinding.toolNames.map(name => tools.find(tool => tool.name === name)) : [];
158
+ invariant(mcpTools.every(tool => tool && !granted.some(item => item.name === tool.name)
159
+ && (!role.concurrentSafe || tool.readOnly === true)), 'studio_native_child_mcp_role_unsafe');
160
+ const model = await resolveModel(role.modelProfileId);
161
+ const skillTools = inheritedSkills ? skillBinding.toolNames.map(name => tools.find(tool => tool.name === name)) : [];
162
+ invariant(skillTools.every(tool => tool?.readOnly === true && tool.concurrentSafe === true
163
+ && !granted.some(item => item.name === tool.name) && !mcpTools.some(item => item.name === tool.name)),
164
+ 'studio_native_child_skill_binding_unavailable');
165
+ const toolDiscoveryPolicy = [...granted, ...mcpTools, ...skillTools].some(tool => tool.discovery) ? 'local-tool-search/v1' : undefined;
166
+ const controls = childTaskPolicy ? [...todoToolDefinitions(!!toolDiscoveryPolicy), coordinatorResponseDefinition(!!toolDiscoveryPolicy)] : [];
167
+ invariant(![...granted, ...mcpTools, ...skillTools].some(tool => controls.some(control => control.name === tool.name)),
168
+ 'studio_native_child_control_name_conflict');
169
+ bindings.set(role.name, { model, ...(toolDiscoveryPolicy ? { toolDiscoveryPolicy } : {}), tools: [...granted, ...mcpTools, ...skillTools,
170
+ ...controls,
171
+ ...(toolDiscoveryPolicy ? toolDiscoveryDefinitions() : []),
172
+ ...(artifactPolicy ? artifactToolDefinitions() : [])], isolatedWrite,
173
+ ...(usesCommands ? { commandResource: structuredClone(commandResource) } : {}),
174
+ ...(childA2a ? { a2aBinding: structuredClone(childA2a) } : {}),
175
+ ...(inheritedSkills ? { skillBinding: structuredClone(skillBinding) } : {}),
176
+ ...(inheritedMcp ? { mcpBinding: structuredClone(mcpBinding) } : {}) });
177
+ }
178
+ const childScope = role => ({ ...scope, agentId: `${scope.agentId}:child:${role.name}` });
179
+ const merge = [...bindings.values()].some(binding => binding.isolatedWrite)
180
+ ? createStudioNativeChildMerge({ scope, sessionId, store, roles, fileWorkspaces, isClosed,
181
+ previousPublication: previousChildPublication, authorize: async () => 'allow' }) : null;
182
+ const readers = createNativeChildResultTools({ scope, childScopes: roles.map(childScope), store, roles: roles.map(role => role.name) });
183
+ const owner = createNativeChildOwner({ scope, onStream, childScopes: roles.map(childScope), store,
184
+ ...(childCapabilityPolicy ? { admitRequirements: args => admitChildCapabilities(
185
+ [...bindings].map(([role, bound]) => ({ role, tools: bound.tools.map(tool => tool.name).sort() })), args.role, args.required_tools) } : {}),
186
+ ...(childAllocationPolicy ? { childAllocationPolicy } : {}),
187
+ ...(waitForChildReceipt ? { canWaitForReceipt: record => !!nativeA2aReceiptWaitSource(record), waitForIdle: waitForChildReceipt } : {}),
188
+ resolveChild: async request => {
189
+ const role = byName.get(request.role), bound = bindings.get(request.role);
190
+ invariant(role && bound, 'studio_native_child_role_unbound');
191
+ const args = request.call.arguments;
192
+ const refs = args.media ?? [];
193
+ invariant(Array.isArray(refs) && refs.length <= 20 && refs.every(ref => ref && typeof ref === 'object'
194
+ && Object.keys(ref).length === 2 && ref.source === 'parent_user_input' && Number.isSafeInteger(ref.index)
195
+ && ref.index >= 0 && mediaSources.get(ref.index)), 'studio_native_child_media_reference_invalid');
196
+ invariant(!refs.length || bound.model.supportsImages, 'studio_native_child_model_image_unavailable');
197
+ const media = refs.map(ref => structuredClone(mediaSources.get(ref.index)));
198
+ const child = childScope(role), contract = goalContractFactory({ sessionId: request.childSessionId, goal: request.task });
199
+ if (args.continuation_of !== undefined) {
200
+ const source = store.load(child, args.continuation_of).snapshot;
201
+ const inheritedMedia = [...source.messages, ...source.queuedGuidance].some(message => message.media?.length);
202
+ invariant(!inheritedMedia || bound.model.supportsImages, 'child_followup_model_image_unavailable');
203
+ }
204
+ const { schema: output, parentOutputDigest } = outputSchema(role, args, request.parentContract);
205
+ if (output) {
206
+ contract.finalSubmissionSchema = { type: 'object', properties: { output }, required: ['output'], additionalProperties: false };
207
+ contract.finalSubmissionPayloadKey = 'output'; contract.finalOutputFormat = submissionOutputFormat(contract.finalSubmissionSchema, 'output');
208
+ }
209
+ const source = args.continuation_of === undefined ? null : store.load(child, args.continuation_of);
210
+ const childSkills = bound.skillBinding ? { ...bound.skillBinding, role: structuredClone(role), buildId: request.parent.buildId } : null;
211
+ invariant(!source || canonical(source.configuration.hostRecoveryBinding?.childSkills ?? null) === canonical(childSkills),
212
+ 'studio_native_child_skill_binding_changed');
213
+ const isolated = bound.isolatedWrite ? fileWorkspaces.bind({ scope: child, sessionId: request.childSessionId, role,
214
+ existing: Boolean(source), ...(source ? { expectedBinding: source.configuration.hostRecoveryBinding?.childWorkspace } : {}) }) : null;
215
+ invariant(!source || !bound.isolatedWrite || source.configuration.hostRecoveryBinding?.childWorkspace,
216
+ 'studio_native_child_workspace_binding_changed');
217
+ const childContext = source ? source.configuration.hostRecoveryBinding?.workspaceContext
218
+ : workspaceContext ? nativeWorkspaceContextBinding(isolated?.root ?? workspaceContext.workspaceRoot,
219
+ { instructionRoots: workspaceContext.instructionSource.globalRoots }) : undefined;
220
+ validateInstructionWorkspace(childContext, isolated, workspaceContext);
221
+ const instructionSource = childContext ? createNativeWorkspaceInstructions(childContext) : null;
222
+ const executionResource = bound.commandResource ? { ...bound.commandResource, workspaceBindingDigest: hash(isolated.binding) } : undefined;
223
+ invariant(!source || canonical(source.configuration.hostRecoveryBinding?.childExecutionResource ?? null) === canonical(executionResource ?? null),
224
+ 'studio_native_child_command_resource_changed');
225
+ const hostBindingDigest = hash({ contract: STUDIO_NATIVE_CHILD_CONTRACT, role, profile: bound.model.profile, scope: child, tools: bound.tools,
226
+ ...(outputRecoveryPolicy ? { outputRecoveryPolicy } : {}),
227
+ ...(toolExecutionPolicy ? { toolExecutionPolicy } : {}),
228
+ ...(childPlanPolicy ? { childPlanPolicy } : {}),
229
+ ...(childTaskPolicy ? { childTaskPolicy } : {}),
230
+ ...(childAllocationPolicy ? { childAllocationPolicy } : {}),
231
+ ...(artifactPolicy ? { artifactPolicy } : {}),
232
+ ...(sourceReadPolicy ? { sourceReadPolicy } : {}),
233
+ ...(childContext ? { workspaceContext: childContext } : {}),
234
+ ...(bound.mcpBinding ? { inheritedMcp: bound.mcpBinding } : {}),
235
+ ...(bound.a2aBinding ? { inheritedA2a: bound.a2aBinding } : {}),
236
+ ...(childSkills ? { inheritedSkills: childSkills } : {}),
237
+ ...(isolated ? { childWorkspace: isolated.binding } : {}),
238
+ ...(executionResource ? { childExecutionResource: executionResource } : {}),
239
+ ...(parentOutputDigest ? { parentOutputDigest } : {}) });
240
+ const ports = bindTools({ scope: child, store, sessionId: request.childSessionId,
241
+ expectedHostBindingDigest: hostBindingDigest,
242
+ toolNames: gatewayToolNames(bound.tools, artifactPolicy, bound.toolDiscoveryPolicy, childTaskPolicy),
243
+ ...(bound.a2aBinding ? { a2aBinding: bound.a2aBinding } : {}),
244
+ ...(childSkills ? { childSkills } : {}),
245
+ ...(bound.mcpBinding ? { mcpBinding: bound.mcpBinding } : {}),
246
+ ...(isolated ? { workspaceRoot: isolated.root, workspaceBinding: isolated.binding } : {}),
247
+ ...(executionResource ? { executionResource } : {}) });
248
+ const verification = await verificationFactory({ scope: child, sessionId: request.childSessionId,
249
+ ...(isolated ? { workspaceRoot: isolated.root } : {}) });
250
+ request.signal.throwIfAborted();
251
+ return { scope: child, configuration: configure({ profile: bound.model.profile, contextPolicy: bound.model.contextPolicy,
252
+ ...(instructionSource ? { contextSources: [instructionSource.source], inputPolicy: 'before-first-model/v1' } : {}),
253
+ ...(modelSurface ? { modelSurface: nativeChildModelSurface(modelSurface, { workspaceRoot: isolated?.root,
254
+ supportsImages: bound.model.supportsImages, tools: bound.tools }) } : {}),
255
+ ...(childAllocationPolicy ? { childAllocationPolicy } : {}),
256
+ ...(outputRecoveryPolicy ? { outputRecoveryPolicy } : {}),
257
+ ...(toolExecutionPolicy ? { toolExecutionPolicy } : {}),
258
+ ...(executionResource ? { backgroundNotificationPolicy: 'completed-before-model/v1' } : {}),
259
+ ...(childTaskPolicy ? { todoPolicy: 'session-todos/v1', coordinatorResponsePolicy: COORDINATOR_RESPONSE_POLICY } : {}),
260
+ ...(bound.toolDiscoveryPolicy ? { toolDiscoveryPolicy: bound.toolDiscoveryPolicy } : {}),
261
+ ...(artifactPolicy ? { artifactPolicy } : {}),
262
+ ...(sourceReadPolicy ? { sourceReadPolicy } : {}),
263
+ guidancePolicy: 'boundary-guide/v1',
264
+ ...(media.length ? { inputMessages: [{ role: 'user', kind: 'user_input', content: request.task, media }] } : {}),
265
+ instructions: [role.instructions, ...(childSkills ? [skillInstructions] : [])].filter(Boolean).join('\n\n'), contract, tools: bound.tools,
266
+ modelRecoveryPolicy: bound.model.modelRecoveryPolicy ?? 'manual',
267
+ ...(isolated || childSkills || childContext ? { hostRecoveryBinding: {
268
+ ...(childContext ? { workspaceContext: childContext } : {}),
269
+ ...(isolated ? { childWorkspace: isolated.binding } : {}), ...(childSkills ? { childSkills } : {}),
270
+ ...(executionResource ? { childExecutionResource: executionResource } : {}),
271
+ } } : {}),
272
+ hostBindingDigest,
273
+ delegationDepth: request.depth, limits: { maxModelCalls: role.maxTurns,
274
+ maxToolCalls: role.maxToolCalls ?? (bound.tools.some(tool => !artifactPolicy || !isArtifactTool(tool.name)) ? role.maxTurns * 4 : 0),
275
+ maxRepairs: Math.min(2, role.maxTurns - 1), maxSubagentDepth: request.depth, maxWallMs: role.timeoutSeconds * 1000,
276
+ // Dispatcher ceilings must admit the granted tool contracts. The
277
+ // child's separate wall/model/tool budgets still bound execution.
278
+ effectTimeoutMs: Math.max(120000, bound.model.profile.requestTimeoutMs + 10000,
279
+ ...bound.tools.map(tool => tool.timeoutMs + 1)) } }),
280
+ ports: { ...ports, ...(artifactPolicy ? { artifacts: await artifactFactory(child) } : {}),
281
+ model: bound.model.model, ...instructionPorts(instructionSource, { context: bound.model.context,
282
+ input: ports.input, store, scope: child, sessionId: request.childSessionId, tools: bound.tools }), verification } };
283
+ },
284
+ restoreChild: async ({ identity, configuration, binding }) => {
285
+ const bound = bindings.get(binding.role), role = byName.get(binding.role);
286
+ const parent = store.load(binding.parent.scope, binding.parent.runId);
287
+ const call = parent.intents.find(item => item.operationKey === binding.parentOperationKey)?.payload.calls
288
+ ?.find(item => item.callId === binding.parentToolCallId);
289
+ invariant(call && role, 'studio_native_child_restore_binding_changed');
290
+ const { parentOutputDigest } = outputSchema(role, call.arguments, parent.snapshot.taskContract ?? parent.configuration.contract);
291
+ const childSkills = bound?.skillBinding ? { ...bound.skillBinding, role: structuredClone(role), buildId: identity.buildId } : null;
292
+ invariant(canonical(configuration.hostRecoveryBinding?.childSkills ?? null) === canonical(childSkills),
293
+ 'studio_native_child_skill_binding_changed');
294
+ const isolated = bound?.isolatedWrite ? fileWorkspaces.bind({ scope: identity.scope, sessionId: identity.sessionId,
295
+ role, existing: true, expectedBinding: configuration.hostRecoveryBinding?.childWorkspace }) : null;
296
+ invariant(!bound?.isolatedWrite || configuration.hostRecoveryBinding?.childWorkspace,
297
+ 'studio_native_child_workspace_binding_changed');
298
+ const childContext = configuration.hostRecoveryBinding?.workspaceContext;
299
+ validateInstructionWorkspace(childContext, isolated, workspaceContext);
300
+ const instructionSource = childContext ? createNativeWorkspaceInstructions(childContext) : null;
301
+ const executionResource = bound?.commandResource ? { ...bound.commandResource, workspaceBindingDigest: hash(isolated.binding) } : undefined;
302
+ invariant(canonical(configuration.hostRecoveryBinding?.childExecutionResource ?? null) === canonical(executionResource ?? null)
303
+ && configuration.backgroundNotificationPolicy === (executionResource ? 'completed-before-model/v1' : undefined),
304
+ 'studio_native_child_command_resource_changed');
305
+ // configure() fills retry/output defaults before persisting the profile.
306
+ // Reconstruct the same normalized profile, rather than comparing it to
307
+ // the Host's deliberately partial input and rejecting every live child.
308
+ const profile = bound && configure({ profile: bound.model.profile, contract: configuration.contract,
309
+ hostBindingDigest: configuration.hostBindingDigest }).profile;
310
+ invariant(bound && role && canonical(configuration.tools) === canonical(bound.tools)
311
+ && configuration.outputRecoveryPolicy === outputRecoveryPolicy
312
+ && configuration.toolExecutionPolicy === toolExecutionPolicy
313
+ && configuration.inputPolicy === (instructionSource ? 'before-first-model/v1' : undefined)
314
+ && canonical(configuration.contextSources) === canonical(instructionSource ? [instructionSource.source] : [])
315
+ && (configuration.childAllocationPolicy ?? null) === (childAllocationPolicy ?? null)
316
+ && (binding.childAllocationPolicy ?? null) === (childAllocationPolicy ?? null)
317
+ && canonical(configuration.modelSurface ?? null) === canonical(nativeChildModelSurface(modelSurface, {
318
+ workspaceRoot: isolated?.root, supportsImages: bound.model.supportsImages, tools: bound.tools,
319
+ original: configuration.modelSurface }) ?? null)
320
+ && configuration.planModePolicy === (childPlanPolicy ? CHILD_PLAN_MODE_POLICY : undefined)
321
+ && configuration.planModeInitialActive === (childPlanPolicy ? binding.planMode : undefined)
322
+ && configuration.todoPolicy === (childTaskPolicy ? 'session-todos/v1' : undefined)
323
+ && configuration.coordinatorResponsePolicy === (childTaskPolicy ? COORDINATOR_RESPONSE_POLICY : undefined)
324
+ && configuration.toolDiscoveryPolicy === bound.toolDiscoveryPolicy
325
+ && (configuration.artifactPolicy ?? null) === (artifactPolicy ?? null)
326
+ && (configuration.sourceReadPolicy ?? null) === (sourceReadPolicy ?? null)
327
+ && canonical(configuration.profile) === canonical(profile)
328
+ && configuration.modelRecoveryPolicy === (bound.model.modelRecoveryPolicy ?? 'manual')
329
+ && configuration.hostBindingDigest === hash({ contract: STUDIO_NATIVE_CHILD_CONTRACT, role,
330
+ profile: bound.model.profile, scope: childScope(role), tools: bound.tools,
331
+ ...(outputRecoveryPolicy ? { outputRecoveryPolicy } : {}),
332
+ ...(toolExecutionPolicy ? { toolExecutionPolicy } : {}),
333
+ ...(childPlanPolicy ? { childPlanPolicy } : {}),
334
+ ...(childTaskPolicy ? { childTaskPolicy } : {}),
335
+ ...(childAllocationPolicy ? { childAllocationPolicy } : {}),
336
+ ...(artifactPolicy ? { artifactPolicy } : {}),
337
+ ...(sourceReadPolicy ? { sourceReadPolicy } : {}),
338
+ ...(childContext ? { workspaceContext: childContext } : {}),
339
+ ...(bound.mcpBinding ? { inheritedMcp: bound.mcpBinding } : {}),
340
+ ...(bound.a2aBinding ? { inheritedA2a: bound.a2aBinding } : {}),
341
+ ...(childSkills ? { inheritedSkills: childSkills } : {}),
342
+ ...(isolated ? { childWorkspace: isolated.binding } : {}),
343
+ ...(executionResource ? { childExecutionResource: executionResource } : {}),
344
+ ...(parentOutputDigest ? { parentOutputDigest } : {}) }), 'studio_native_child_restore_binding_changed');
345
+ const restoredPorts = bindTools({ scope: identity.scope, store, sessionId: identity.sessionId,
346
+ expectedHostBindingDigest: configuration.hostBindingDigest,
347
+ toolNames: gatewayToolNames(bound.tools, artifactPolicy, bound.toolDiscoveryPolicy, childTaskPolicy),
348
+ ...(bound.a2aBinding ? { a2aBinding: bound.a2aBinding } : {}),
349
+ ...(childSkills ? { childSkills } : {}),
350
+ ...(bound.mcpBinding ? { mcpBinding: bound.mcpBinding } : {}),
351
+ ...(isolated ? { workspaceRoot: isolated.root, workspaceBinding: isolated.binding } : {}),
352
+ ...(executionResource ? { executionResource } : {}) });
353
+ return { ...restoredPorts,
354
+ ...(artifactPolicy ? { artifacts: await artifactFactory(identity.scope) } : {}),
355
+ model: bound.model.model, ...instructionPorts(instructionSource, { context: bound.model.context,
356
+ input: restoredPorts.input, store, scope: identity.scope, sessionId: identity.sessionId, tools: bound.tools }),
357
+ verification: await verificationFactory({ scope: identity.scope, sessionId: identity.sessionId,
358
+ ...(isolated ? { workspaceRoot: isolated.root } : {}) }) };
359
+ },
360
+ });
361
+ return { owner, contract: STUDIO_NATIVE_CHILD_CONTRACT, roles: structuredClone(roles),
362
+ execute: request => ['native_start_child', 'native_delegate'].includes(request.call.name) ? owner.start(request)
363
+ : request.call.name === 'native_child_control' ? owner.control(request)
364
+ : request.call.name === 'native_child_merge' && merge ? merge.execute(request) : readers.execute(request) };
365
+ }
@@ -0,0 +1,14 @@
1
+ import { parentPort, workerData } from 'node:worker_threads';
2
+ import { isRuntimeReadOnlyBashCommand } from '../../dist/native-command-plan.mjs';
3
+
4
+ const context = { workingDirectory: workerData.cwd, workspaceRoot: workerData.workspaceRoot };
5
+ const quote = value => `'${value.replaceAll("'", "'\"'\"'")}'`;
6
+ try {
7
+ // Native's explicit env object is equivalent to shell assignment prefixes.
8
+ // Use the reference's own assignment policy, including PATH/GIT_* denials.
9
+ const allowedEnvironment = Object.entries(workerData.env ?? {}).every(([name, value]) =>
10
+ /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && typeof value === 'string' && !value.includes('\0')
11
+ && isRuntimeReadOnlyBashCommand(`${name}=${quote(value)} true`, context));
12
+ parentPort.postMessage({ contract: 'zcode-posix-readonly/v1',
13
+ allowed: allowedEnvironment && isRuntimeReadOnlyBashCommand(workerData.command, context) });
14
+ } catch { parentPort.postMessage({ contract: 'zcode-posix-readonly/v1', allowed: false }); }
@@ -0,0 +1,31 @@
1
+ import { Worker } from 'node:worker_threads';
2
+
3
+ export const PLAN_COMMAND_POLICY = 'zcode-posix-readonly/v1';
4
+
5
+ /** Read-only permission classification, not execution or safe-replay authority. */
6
+ export async function checkPlanCommand(command, { cwd, workspaceRoot, env, signal, timeoutMs = 2000 } = {}) {
7
+ signal?.throwIfAborted();
8
+ if (typeof command !== 'string' || command.length > 10000 || typeof cwd !== 'string' || typeof workspaceRoot !== 'string'
9
+ || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 10000) return false;
10
+ let worker;
11
+ try { worker = new Worker(new URL('./command-plan-worker.mjs', import.meta.url), {
12
+ workerData: { command, cwd, workspaceRoot, env }, execArgv: [],
13
+ resourceLimits: { maxOldGenerationSizeMb: 64, maxYoungGenerationSizeMb: 16, stackSizeMb: 4 },
14
+ }); } catch { return false; }
15
+ return await new Promise(resolve => {
16
+ let output, settled = false;
17
+ const finish = allowed => {
18
+ if (settled) return; settled = true;
19
+ clearTimeout(timer); signal?.removeEventListener('abort', stop); resolve(allowed);
20
+ };
21
+ const stop = () => { worker.unref(); void worker.terminate().catch(() => {}); finish(false); };
22
+ const timer = setTimeout(stop, timeoutMs);
23
+ signal?.addEventListener('abort', stop, { once: true });
24
+ worker.once('message', value => { output = value; });
25
+ worker.once('error', stop);
26
+ worker.once('exit', code => {
27
+ finish(!signal?.aborted && code === 0 && output?.contract === PLAN_COMMAND_POLICY && output.allowed === true);
28
+ });
29
+ if (signal?.aborted) stop();
30
+ });
31
+ }
@@ -0,0 +1,64 @@
1
+ import { LOCAL_COMMAND_PROGRESS_CONTRACT } from './local-command-tools.mjs';
2
+
3
+ export const COMMAND_PREVIEW_CONTRACT = 'agentkit.command-preview/v1';
4
+
5
+ /** One invocation owns this observation channel. Raw process output reaches
6
+ * only the Host's required projector, never the UI/model or a durable receipt.
7
+ * A projector may hold data or return null; final-result policies alone do not
8
+ * authorize previews. The Host must provide equivalent streaming governance.
9
+ */
10
+ export function createCommandProgressObserver({ signal, isCurrent, project, publish, maxBytes = 65536 } = {}) {
11
+ if (!(signal instanceof AbortSignal) || typeof isCurrent !== 'function' || typeof project !== 'function'
12
+ || typeof publish !== 'function' || !Number.isSafeInteger(maxBytes) || maxBytes < 4 || maxBytes > 1048576)
13
+ throw new TypeError('command_progress_host_capabilities_required');
14
+ let closed = false, draining = false, pending = null, sequence = 0, retained = 0;
15
+ let stdout = '', stderr = '', truncated = false;
16
+ const current = () => {
17
+ if (closed || signal.aborted) return false;
18
+ try {
19
+ const value = isCurrent();
20
+ if (value && typeof value.then === 'function') Promise.resolve(value).catch(() => {});
21
+ return value === true;
22
+ } catch { return false; }
23
+ };
24
+ const close = () => { closed = true; pending = null; stdout = ''; stderr = ''; signal.removeEventListener('abort', close); };
25
+ signal.addEventListener('abort', close, { once: true });
26
+ async function drain() {
27
+ if (draining) return;
28
+ draining = true;
29
+ try {
30
+ while (pending && current()) {
31
+ const snapshot = pending; pending = null;
32
+ try {
33
+ const output = await project(snapshot, { signal });
34
+ if (!current() || !output || typeof output !== 'object' || Array.isArray(output) || output.kind !== 'command_preview'
35
+ || Object.keys(output).some(key => !['kind', 'stdout', 'stderr'].includes(key))
36
+ || typeof output.stdout !== 'string' || typeof output.stderr !== 'string'
37
+ || Buffer.byteLength(output.stdout) + Buffer.byteLength(output.stderr) > maxBytes) continue;
38
+ const frame = Object.freeze({ contract: COMMAND_PREVIEW_CONTRACT, sequence: snapshot.sequence,
39
+ display: Object.freeze({ kind: 'command_preview', stdout: output.stdout, stderr: output.stderr }),
40
+ truncated: snapshot.truncated });
41
+ Promise.resolve(publish(frame)).catch(() => {});
42
+ } catch { /* projection, malformed output and publication cannot fail execution */ }
43
+ }
44
+ } finally { draining = false; }
45
+ }
46
+ return Object.freeze({
47
+ observe(frame) {
48
+ if (!current() || frame?.contract !== LOCAL_COMMAND_PROGRESS_CONTRACT
49
+ || !Number.isSafeInteger(frame.sequence) || frame.sequence <= sequence
50
+ || !['stdout', 'stderr'].includes(frame.channel) || typeof frame.text !== 'string') return;
51
+ sequence = frame.sequence;
52
+ const bytes = Buffer.from(frame.text), available = Math.max(0, maxBytes - retained);
53
+ const text = new TextDecoder('utf8', { ignoreBOM: true }).decode(bytes.subarray(0, available), { stream: true });
54
+ retained += Math.min(available, bytes.length); truncated ||= bytes.length > available;
55
+ if (frame.channel === 'stdout') stdout += text; else stderr += text;
56
+ // One bounded pending snapshot coalesces a fast producer behind a slow
57
+ // governor. No per-chunk promise queue or unbounded output buffer.
58
+ pending = Object.freeze({ contract: LOCAL_COMMAND_PROGRESS_CONTRACT, sequence,
59
+ stdout, stderr, truncated });
60
+ void drain();
61
+ },
62
+ close,
63
+ });
64
+ }
@@ -0,0 +1,9 @@
1
+ export const LOCAL_COMMAND_SHELL = 'bash-execution/v1';
2
+
3
+ /** A missing policy belongs to a persisted legacy request. Never reinterpret
4
+ * it under a new shell during supervision/recovery. No PATH-based fallback.
5
+ */
6
+ export function shellCommandArgv(command, policy = null) {
7
+ if (policy !== null && policy !== LOCAL_COMMAND_SHELL) throw Error('unsupported_command_shell');
8
+ return [policy === LOCAL_COMMAND_SHELL ? '/bin/bash' : '/bin/sh', '-c', command];
9
+ }
@@ -0,0 +1,36 @@
1
+ import { WORKSPACE_CHANGE_CONTRACT } from '@kingsoftcloud/agentkit-harness-core';
2
+
3
+ const reasons = new Map([
4
+ ['capture_limit_exceeded', 'inventory_limit'],
5
+ ['workspace_changed_during_capture', 'concurrent_change'],
6
+ ['capture_failed', 'inventory_unavailable'],
7
+ ['unsupported_workspace_path', 'unsupported_path'],
8
+ ]);
9
+
10
+ /** Inventory is a bounded observation, never a command authorization check.
11
+ * Root/session/authority errors still escape and are handled by the executor. */
12
+ export async function captureCommandWorkspace(capture, request) {
13
+ try {
14
+ const captured = await capture(request);
15
+ if (!captured || typeof captured.revision !== 'string' || !captured.revision
16
+ || !captured.entries || typeof captured.entries !== 'object' || Array.isArray(captured.entries))
17
+ return { captured: null, reason: 'inventory_unavailable' };
18
+ return { captured, reason: null };
19
+ }
20
+ catch (error) {
21
+ const reason = reasons.get(error?.code);
22
+ if (!reason) throw error;
23
+ return { captured: null, reason };
24
+ }
25
+ }
26
+
27
+ export function commandWorkspaceChange(before, after) {
28
+ if (!before.captured || !after.captured) return {
29
+ workspaceChangeObservation: { contract: WORKSPACE_CHANGE_CONTRACT, status: 'unavailable', revision: null,
30
+ reason: after.reason ?? before.reason ?? 'baseline_unavailable' },
31
+ };
32
+ const paths = [...new Set([...Object.keys(before.captured.entries), ...Object.keys(after.captured.entries)])]
33
+ .filter(path => before.captured.entries[path] !== after.captured.entries[path]).sort();
34
+ return { workspaceChangedPaths: paths,
35
+ workspaceChangeObservation: { contract: WORKSPACE_CHANGE_CONTRACT, status: 'captured', revision: after.captured.revision, reason: null } };
36
+ }