@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.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.
- package/LICENSE +201 -0
- package/NOTICE +1 -0
- package/README.md +124 -2
- package/THIRD_PARTY_NOTICES.md +66 -0
- package/ZCode-LICENSE +202 -0
- package/dist/index.mjs +4871 -0
- package/dist/native-command-plan.LICENSE.txt +221 -0
- package/dist/native-command-plan.json +16 -0
- package/dist/native-command-plan.mjs +8 -0
- package/dist/rpc-entry.mjs +5682 -0
- package/package.json +169 -4
- package/src/native/a2a-receipt-source.mjs +19 -0
- package/src/native/a2a-tools.mjs +517 -0
- package/src/native/activity-status.mjs +30 -0
- package/src/native/artifact-codec.mjs +481 -0
- package/src/native/artifact-store.mjs +1162 -0
- package/src/native/assistant-stream.mjs +76 -0
- package/src/native/background-command-port.mjs +95 -0
- package/src/native/browser-action-observation.mjs +43 -0
- package/src/native/browser-availability.mjs +22 -0
- package/src/native/browser-canvas-metrics.mjs +71 -0
- package/src/native/browser-dom.mjs +193 -0
- package/src/native/browser-playwright.mjs +1249 -0
- package/src/native/browser-vision.mjs +369 -0
- package/src/native/browser-workspace-policy.mjs +22 -0
- package/src/native/browser-workspace-protocol.mjs +95 -0
- package/src/native/browser-workspace-server.mjs +164 -0
- package/src/native/browser-workspace.mjs +355 -0
- package/src/native/captured-source.mjs +3 -0
- package/src/native/child-budget-allocation.mjs +87 -0
- package/src/native/child-capabilities.mjs +35 -0
- package/src/native/child-command-resource.mjs +20 -0
- package/src/native/child-history.mjs +188 -0
- package/src/native/child-merge.mjs +98 -0
- package/src/native/child-model-surface.mjs +19 -0
- package/src/native/child-observation.mjs +100 -0
- package/src/native/child-owner.mjs +620 -0
- package/src/native/child-publication.mjs +76 -0
- package/src/native/child-readers.mjs +366 -0
- package/src/native/child-tools.mjs +46 -0
- package/src/native/child-workspace-binding.mjs +29 -0
- package/src/native/child-workspace-tools.mjs +1 -0
- package/src/native/child-workspaces.mjs +655 -0
- package/src/native/children.mjs +365 -0
- package/src/native/command-plan-worker.mjs +14 -0
- package/src/native/command-plan.mjs +31 -0
- package/src/native/command-progress.mjs +64 -0
- package/src/native/command-shell.mjs +9 -0
- package/src/native/command-workspace-observation.mjs +36 -0
- package/src/native/component-tools.mjs +280 -0
- package/src/native/context-counter.mjs +133 -0
- package/src/native/context-markers.mjs +51 -0
- package/src/native/context-profile.mjs +72 -0
- package/src/native/context-status.mjs +27 -0
- package/src/native/conversation-command-control.mjs +133 -0
- package/src/native/delivery-timeline.mjs +39 -0
- package/src/native/dsh-background-ownership.mjs +102 -0
- package/src/native/dsh-managed-ownership.mjs +23 -0
- package/src/native/dsh-turn-contract.mjs +3 -0
- package/src/native/dsh-turn-ownership.mjs +54 -0
- package/src/native/dsh-turn-runtime.mjs +498 -0
- package/src/native/e2b-background-supervisor.mjs +347 -0
- package/src/native/e2b-background.mjs +368 -0
- package/src/native/e2b-command-state.mjs +40 -0
- package/src/native/e2b-command-tools.mjs +486 -0
- package/src/native/e2b-sandbox-owner.mjs +227 -0
- package/src/native/hook-executor.mjs +249 -0
- package/src/native/instruction-assembly.mjs +60 -0
- package/src/native/instruction-context.mjs +54 -0
- package/src/native/local-background-supervisor.mjs +108 -0
- package/src/native/local-background.mjs +207 -0
- package/src/native/local-command-evidence.mjs +57 -0
- package/src/native/local-command-policy-worker.mjs +197 -0
- package/src/native/local-command-policy.mjs +46 -0
- package/src/native/local-command-state.mjs +177 -0
- package/src/native/local-command-tools.mjs +302 -0
- package/src/native/local-execution-profile.mjs +36 -0
- package/src/native/local-output-policy.mjs +92 -0
- package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
- package/src/native/local-process-bin/darwin-x64/launcher +0 -0
- package/src/native/local-process-bin/linux-arm64/launcher +0 -0
- package/src/native/local-process-bin/linux-x64/launcher +0 -0
- package/src/native/local-process-launcher.c +183 -0
- package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
- package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
- package/src/native/local-process-licenses/NOTICE +21 -0
- package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
- package/src/native/local-process-manifest.json +32 -0
- package/src/native/local-process.mjs +246 -0
- package/src/native/local-resource-profile.mjs +32 -0
- package/src/native/memory-candidate.mjs +121 -0
- package/src/native/memory-extraction-run.mjs +60 -0
- package/src/native/memory-extraction-tools.mjs +116 -0
- package/src/native/memory-recall.mjs +65 -0
- package/src/native/memory-store.mjs +334 -0
- package/src/native/memory-tools.mjs +329 -0
- package/src/native/model-anthropic-cache.mjs +49 -0
- package/src/native/model-anthropic-tool-search.mjs +85 -0
- package/src/native/model-anthropic.mjs +753 -0
- package/src/native/model-chat-usage.mjs +24 -0
- package/src/native/model-inline-reasoning.mjs +111 -0
- package/src/native/model-media.mjs +174 -0
- package/src/native/model-normalize.mjs +228 -0
- package/src/native/model-openai-chat.mjs +561 -0
- package/src/native/model-port.mjs +361 -0
- package/src/native/model-provider-compat.mjs +129 -0
- package/src/native/model-replay.mjs +153 -0
- package/src/native/model-request.mjs +15 -0
- package/src/native/model-responses.mjs +1205 -0
- package/src/native/model-sse.mjs +171 -0
- package/src/native/model-surface-host.mjs +27 -0
- package/src/native/model-tool-search-policy.mjs +16 -0
- package/src/native/model-transport-observation.mjs +27 -0
- package/src/native/owned-child.mjs +114 -0
- package/src/native/owned-command.mjs +106 -0
- package/src/native/python-syntax-worker.mjs +60 -0
- package/src/native/python-syntax.mjs +50 -0
- package/src/native/read-interruption.mjs +16 -0
- package/src/native/result-stream.mjs +63 -0
- package/src/native/ruff-ast.mjs +95 -0
- package/src/native/run-observation.mjs +128 -0
- package/src/native/session-delivery-journal.mjs +96 -0
- package/src/native/session-delivery.mjs +434 -0
- package/src/native/shell-analysis.mjs +50 -0
- package/src/native/skill-disclosure.mjs +404 -0
- package/src/native/skill-events.mjs +164 -0
- package/src/native/skill-tools.mjs +813 -0
- package/src/native/source-page.mjs +73 -0
- package/src/native/sqlite-session-inbox.mjs +42 -0
- package/src/native/sqlite-store.mjs +617 -0
- package/src/native/tool-authority.mjs +99 -0
- package/src/native/tool-guidance.mjs +12 -0
- package/src/native/tool-observation.mjs +75 -0
- package/src/native/tool-states.mjs +14 -0
- package/src/native/verification-authority.mjs +37 -0
- package/src/native/web-tools.mjs +643 -0
- package/src/native/workspace-checkpoints.mjs +312 -0
- package/src/native/workspace-context.mjs +61 -0
- package/src/native/workspace-display.mjs +39 -0
- package/src/native/workspace-edit-match.mjs +19 -0
- package/src/native/workspace-edit-worker.mjs +15 -0
- package/src/native/workspace-editing.mjs +447 -0
- package/src/native/workspace-image.mjs +72 -0
- package/src/native/workspace-instructions.mjs +107 -0
- package/src/native/workspace-mutation-lock.mjs +74 -0
- package/src/native/workspace-patch-worker.mjs +17 -0
- package/src/native/workspace-patch.mjs +17 -0
- package/src/native/workspace-paths.mjs +225 -0
- package/src/native/workspace-state.mjs +272 -0
- package/src/native/workspace-subject-observer.mjs +84 -0
- 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
|
+
}
|