@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.
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,76 @@
1
+ import { canonical, terminal } from '@kingsoftcloud/agentkit-harness-core';
2
+ import { loadSessionChild, accountedChildDelivery } from './owned-child.mjs';
3
+ import { ChildFilePublication } from './child-workspaces.mjs';
4
+
5
+ export const MERGE_SCHEMA = 'native.child-file-merge/v2';
6
+ const bindingContract = 'agentkit.native-child-workspace-binding/v1';
7
+ const sha = /^sha256:[0-9a-f]{64}$/;
8
+ const same = (left, right) => canonical(left) === canonical(right);
9
+ function workspaceBinding(record) {
10
+ const value = record.configuration?.hostRecoveryBinding?.childWorkspace;
11
+ return value?.contract === bindingContract && /^[0-9a-f]{64}$/.test(value.isolationId)
12
+ && sha.test(value.baseRevision) ? value : null;
13
+ }
14
+
15
+ /** Only actual prepared tool effects with canonical committed success grant a
16
+ * publication. A saved inventory, model text and an uncertain receipt do not. */
17
+ function confirmedPublication({ store, parent, source }) {
18
+ const visited = new Set();
19
+ for (let cursor = parent; cursor && !visited.has(cursor.runId);) {
20
+ visited.add(cursor.runId);
21
+ const record = store.load(parent.scope, cursor.runId);
22
+ if (!same(record.snapshot.scope, parent.scope) || record.snapshot.sessionId !== parent.sessionId) return null;
23
+ for (const event of [...record.events].reverse()) {
24
+ if (event.kind !== 'effect.prepared' || event.payload.kind !== 'tool') continue;
25
+ const intent = record.intents.find(item => item.operationKey === event.payload.operationKey && item.kind === 'tool');
26
+ const calls = intent?.payload.calls;
27
+ const receipt = record.receipts.find(item => item.operationKey === intent?.operationKey && item.status === 'committed');
28
+ const results = receipt?.result.results;
29
+ if (!Array.isArray(calls) || !Array.isArray(results) || calls.length !== results.length) continue;
30
+ for (let index = calls.length - 1; index >= 0; index--) {
31
+ const call = calls[index], result = results[index];
32
+ if (call?.name !== 'native_child_merge' || !same(call.arguments, { child_run_id: source.runId })
33
+ || result?.status !== 'ok') continue;
34
+ const content = result.content;
35
+ if (content?.schema_version !== MERGE_SCHEMA || content.ok !== true || content.child_run_id !== source.runId
36
+ || content.child_revision !== source.workspaceRevision) continue;
37
+ const grant = content.publication;
38
+ if (!sha.test(source.workspaceRevision) || !grant || grant.revision !== source.workspaceRevision
39
+ || !sha.test(grant.inventory_digest)) throw Error('child_publication_receipt_invalid');
40
+ return new ChildFilePublication(grant.revision, grant.inventory_digest);
41
+ }
42
+ }
43
+ if (!record.snapshot.parentRunId) return null;
44
+ const previous = store.load(parent.scope, record.snapshot.parentRunId).snapshot;
45
+ if (!same(previous.scope, parent.scope) || previous.sessionId !== parent.sessionId || !terminal(previous.phase)) return null;
46
+ cursor = previous;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /** Resolve only this child's real continuation ancestors, in this parent's
52
+ * Session. Current-child ownership and immutable configuration are re-proved
53
+ * even when a caller supplies a record-shaped object. */
54
+ export function previousChildPublication({ store, childScopes, parent, child }) {
55
+ const actual = loadSessionChild({ store, childScopes, parent, childRunId: child.snapshot.runId });
56
+ if (!actual || !same(actual.child.snapshot, child.snapshot) || !same(actual.child.configuration, child.configuration)
57
+ || !accountedChildDelivery(actual.parent, actual.child)) return null;
58
+ const binding = workspaceBinding(child);
59
+ if (!binding) return null;
60
+ const visited = new Set([child.snapshot.runId]);
61
+ let current = child;
62
+ while (current.snapshot.parentRunId) {
63
+ const sourceId = current.snapshot.parentRunId;
64
+ if (visited.has(sourceId) || current.configuration.childOwnerBinding?.continuationOf !== sourceId) return null;
65
+ visited.add(sourceId);
66
+ const resolved = loadSessionChild({ store, childScopes, parent, childRunId: sourceId });
67
+ if (!resolved || !accountedChildDelivery(resolved.parent, resolved.child)) return null;
68
+ const source = resolved.child;
69
+ if (!same(source.snapshot.scope, child.snapshot.scope) || source.snapshot.sessionId !== child.snapshot.sessionId
70
+ || !same(workspaceBinding(source), binding)) return null;
71
+ const publication = confirmedPublication({ store, parent, source: source.snapshot });
72
+ if (publication) return publication;
73
+ current = source;
74
+ }
75
+ return null;
76
+ }
@@ -0,0 +1,366 @@
1
+ // Scoped, paged read-only tools for already-accounted direct children.
2
+ //
3
+ // Ports the pure-read semantics of ksadk/harness/subagent_adapters/native_child_result.py
4
+ // (NativeChildResultPort + NativeChildTasksPort) at pinned commit 355059d.
5
+ //
6
+ // The read capability owns no Kernel, model, factory, lease or execution method.
7
+ // An accounted identity is a locator, never a semantic completion verdict.
8
+ // Results do not verify the parent task; they are untrusted data.
9
+ //
10
+ // Validation follows child-owner.mjs's toolInvocation pattern: the parent must
11
+ // have a running tool intent matching the request operationKey (including
12
+ // parallel :call:<index> delegation), the frozen definition must match, the
13
+ // executionFence must equal the current lease fence, the lease must be valid,
14
+ // and taskContract.allowedToolNames must permit the tool.
15
+
16
+ import { createHash } from 'node:crypto';
17
+ import { NativeError, isNativeError, canonical, copy, terminal, scopeKey, matchesToolExecutionContract } from '@kingsoftcloud/agentkit-harness-core';
18
+
19
+ import { resolveChildToolIntent as resolveIntent, loadOwnedDirectChild, accountedChildDelivery } from './owned-child.mjs';
20
+
21
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
22
+ const textHash = value => createHash('sha256').update(value).digest('hex');
23
+ const same = (left, right) => canonical(left) === canonical(right);
24
+ const check = (value, code) => { if (!value) throw new NativeError(code); };
25
+ const identityFields = ['scope', 'runId', 'sessionId', 'turnId', 'buildId', 'parentRunId', 'taskChainId'];
26
+ const identity = value => Object.fromEntries(identityFields.map(key => [key, copy(value[key])]));
27
+
28
+ export function nativeChildReaderDefinitions() {
29
+ const resultDefinition = {
30
+ name: 'native_child_result',
31
+ description: "Read a child's status and page its original settled delivery. Active or uncertain children return metadata with result_available=false. This query starts no work and does not verify the parent task.",
32
+ parameters: {
33
+ type: 'object', additionalProperties: false, required: ['child_run_id'],
34
+ properties: {
35
+ child_run_id: { type: 'string', minLength: 1, maxLength: 256 },
36
+ offset_chars: { type: 'integer', minimum: 0, default: 0 },
37
+ limit_chars: { type: 'integer', minimum: 1, maximum: 16000, default: 4000 },
38
+ },
39
+ },
40
+ readOnly: true, concurrentSafe: true, permissions: [], timeoutMs: 1000, recoveryPolicy: 'safe-read/v1',
41
+ contextRole: 'none', observationRole: 'none', progressObservation: false, contextRetention: 'normal',
42
+ };
43
+
44
+ const tasksDefinition = {
45
+ name: 'native_child_tasks',
46
+ description: "List saved direct child tasks, their current status and delivery locators. Use this when previous child results or IDs are no longer in context. For later pages, use the returned inventory_digest and next_offset. Starts no work. Read a delivery with native_child_result.",
47
+ parameters: {
48
+ type: 'object', additionalProperties: false,
49
+ properties: {
50
+ offset: { type: 'integer', minimum: 0, default: 0 },
51
+ limit: { type: 'integer', minimum: 1, maximum: 16, default: 8 },
52
+ inventory_digest: { type: 'string', pattern: '^[0-9a-f]{64}$' },
53
+ },
54
+ },
55
+ readOnly: true, concurrentSafe: true, permissions: [], timeoutMs: 1000, recoveryPolicy: 'safe-read/v1',
56
+ contextRole: 'none', observationRole: 'none', progressObservation: false, contextRetention: 'normal',
57
+ };
58
+
59
+ return [resultDefinition, tasksDefinition];
60
+ }
61
+
62
+ export function createNativeChildResultTools({ scope, childScopes = [scope], store, roles, now = Date.now }) {
63
+ const parentScope = copy(scope);
64
+ scopeKey(parentScope);
65
+ const scopes = copy(childScopes);
66
+ check(Array.isArray(scopes) && scopes.length > 0 && scopes.every(item => {
67
+ scopeKey(item);
68
+ return ['tenantId', 'workspaceId', 'userId'].every(key => item[key] === parentScope[key]);
69
+ }), 'invalid_child_reader_scopes');
70
+ check(store, 'invalid_child_reader_store');
71
+ const roleIds = copy(roles);
72
+ check(Array.isArray(roleIds) && roleIds.length > 0 && roleIds.every(role => typeof role === 'string' && role.trim())
73
+ && new Set(roleIds).size === roleIds.length, 'invalid_child_reader_roles');
74
+
75
+ const definitions = nativeChildReaderDefinitions();
76
+ const definitionsByName = new Map(definitions.map(d => [d.name, d]));
77
+
78
+ // --- Internal: validate parent running tool intent -----------------------
79
+ // Mirrors child-owner.mjs toolInvocation: the request must be bound to a
80
+ // live parent tool effect — running intent, matching call, frozen definition,
81
+ // signal, executionFence, valid lease, and taskContract tool scope.
82
+
83
+ function validateRequest(request) {
84
+ check(same(request.scope, parentScope), 'child_reader_scope_mismatch');
85
+ const def = definitionsByName.get(request.call.name);
86
+ check(def, 'child_reader_definition_mismatch');
87
+ check(matchesToolExecutionContract(request.definition, def), 'child_reader_definition_mismatch');
88
+
89
+ let parentRecord;
90
+ try { parentRecord = store.load(parentScope, request.runId); }
91
+ catch (error) { if (error.code === 'run_not_found') throw new NativeError('child_reader_parent_unavailable'); throw error; }
92
+
93
+ // Validate full RunIdentity against the parent snapshot (not self-comparison)
94
+ check(same(identity(request), identity(parentRecord.snapshot)), 'child_reader_identity_mismatch');
95
+
96
+ const resolved = resolveIntent(parentRecord, request.operationKey);
97
+ check(resolved, 'child_reader_intent_not_owned');
98
+ const { intent, calls, index, base } = resolved;
99
+
100
+ // Intent must be a running tool; pendingIntent stays at the base key
101
+ check(intent.kind === 'tool' && intent.status === 'running'
102
+ && parentRecord.snapshot.pendingIntent === base,
103
+ 'child_reader_intent_not_owned');
104
+
105
+ // Validate the call matches the intent's call at the resolved index
106
+ const expectedCall = calls?.[index];
107
+ check(expectedCall && same(expectedCall, request.call), 'child_reader_call_not_owned');
108
+
109
+ // Validate the frozen definition matches (from effective config tools)
110
+ const effectiveConfig = parentRecord.snapshot.taskContract
111
+ ? { ...parentRecord.configuration, contract: parentRecord.snapshot.taskContract }
112
+ : parentRecord.configuration;
113
+ const frozenDef = effectiveConfig.tools.find(tool => tool.name === request.call.name);
114
+ check(frozenDef && same(frozenDef, request.definition), 'child_reader_definition_not_frozen');
115
+
116
+ // Validate executionFence and lease
117
+ check(intent.claimedFence === request.executionFence
118
+ && parentRecord.lease?.fence === request.executionFence
119
+ && parentRecord.lease.expiresAt > now(), 'child_reader_fence_lost');
120
+
121
+ // Validate taskContract.allowedToolNames if present on the effective config
122
+ const allowedToolNames = effectiveConfig.contract.allowedToolNames;
123
+ if (allowedToolNames !== undefined && allowedToolNames !== null) {
124
+ check(Array.isArray(allowedToolNames) && allowedToolNames.includes(request.call.name), 'child_reader_tool_not_allowed');
125
+ }
126
+
127
+ // Signal must not be aborted
128
+ request.signal.throwIfAborted();
129
+
130
+ return parentRecord;
131
+ }
132
+
133
+ // --- Internal: check terminal-safe delivery ------------------------------
134
+
135
+ function isTerminalSafe(record) {
136
+ const snapshot = record.snapshot;
137
+ if (!terminal(snapshot.phase)) return false;
138
+ if (snapshot.phase === 'interrupted') return false;
139
+ // Check for uncertain receipts that make the delivery unsafe
140
+ for (const receipt of record.receipts) {
141
+ if (receipt.status === 'uncertain') {
142
+ const intent = record.intents.find(item => item.operationKey === receipt.operationKey);
143
+ if (intent?.kind !== 'model' && !(receipt.result.retryScheduled === true
144
+ && ['safe-read/v1', 'workspace-observation/v1'].includes(receipt.result.recoveryPolicy))) return false;
145
+ }
146
+ }
147
+ return true;
148
+ }
149
+
150
+ // --- Internal: unsafe (non-model, non-safe-read) receipt -----------------
151
+ // Same uncertain-receipt predicate as isTerminalSafe and child-owner.mjs
152
+ // unsafeReceipt, exposed as a standalone check for non-terminal children.
153
+
154
+ function hasUnsafeReceipt(record) {
155
+ for (const receipt of record.receipts) {
156
+ if (receipt.status === 'uncertain') {
157
+ const intent = record.intents.find(item => item.operationKey === receipt.operationKey);
158
+ if (intent?.kind !== 'model' && !(receipt.result.retryScheduled === true
159
+ && ['safe-read/v1', 'workspace-observation/v1'].includes(receipt.result.recoveryPolicy))) return true;
160
+ }
161
+ }
162
+ return false;
163
+ }
164
+
165
+ // --- Internal: load a direct child by run ID -----------------------------
166
+
167
+ const loadDirectChild = (parent, childRunId) => loadOwnedDirectChild({ store, childScopes: scopes, parent, childRunId, roles: roleIds });
168
+
169
+ // --- native_child_result execute -----------------------------------------
170
+
171
+ async function executeResult(request) {
172
+ const parentRecord = validateRequest(request);
173
+ const parent = parentRecord.snapshot;
174
+ const args = request.call.arguments ?? {};
175
+ const childRunId = args.child_run_id;
176
+ check(typeof childRunId === 'string' && childRunId.trim() && childRunId.length <= 256, 'child_result_query_invalid');
177
+ const offsetChars = args.offset_chars ?? 0;
178
+ const limitChars = args.limit_chars ?? 4000;
179
+ check(Number.isSafeInteger(offsetChars) && offsetChars >= 0, 'child_result_query_invalid');
180
+ check(Number.isSafeInteger(limitChars) && limitChars >= 1 && limitChars <= 16000, 'child_result_query_invalid');
181
+
182
+ const childRecord = loadDirectChild(parent, childRunId);
183
+ if (!childRecord) return { status: 'error', content: { error_code: 'child_result_not_available' } };
184
+
185
+ const child = childRecord.snapshot;
186
+ const binding = childRecord.configuration.childOwnerBinding;
187
+
188
+ // Only read terminal-safe deliveries
189
+ const reservation = parent.childBudgetReservations.find(item => item.childRunId === childRunId);
190
+ const settled = accountedChildDelivery(parentRecord, childRecord);
191
+ const available = Boolean(reservation.settledUsageDigest && settled && isTerminalSafe(childRecord));
192
+ const finalText = available ? child.finalText : '';
193
+ const charCount = [...finalText].length;
194
+ if (offsetChars > charCount)
195
+ return { status: 'error', content: { error_code: 'child_result_offset_invalid' } };
196
+
197
+ const chars = [...finalText];
198
+ const sliced = chars.slice(offsetChars, offsetChars + limitChars).join('');
199
+ const stop = offsetChars + [...sliced].length;
200
+
201
+ return {
202
+ status: 'ok',
203
+ content: {
204
+ schema_version: 'native.child-result/v1',
205
+ child_run_id: child.runId,
206
+ child_session_id: child.sessionId,
207
+ parent_run_id: parent.runId,
208
+ parent_tool_call_id: binding.parentToolCallId,
209
+ parent_operation_key: binding.parentOperationKey,
210
+ role: binding.role,
211
+ snapshot_revision: child.revision,
212
+ event_seq: child.eventSeq,
213
+ goal_digest: textHash(child.goal ?? ''),
214
+ contract_digest: hash(child.taskContract ?? childRecord.configuration.contract),
215
+ phase: child.phase,
216
+ terminal: terminal(child.phase),
217
+ result_available: available,
218
+ outcome: child.outcome,
219
+ final_text: sliced,
220
+ final_text_digest: textHash(finalText),
221
+ offset_chars: offsetChars,
222
+ next_offset_chars: stop,
223
+ complete: stop === charCount,
224
+ total_chars: charCount,
225
+ parent_acceptance: 'not_evaluated',
226
+ execution_started: false,
227
+ resource_usage_accounted_again: false,
228
+ },
229
+ };
230
+ }
231
+
232
+ // --- native_child_tasks execute ------------------------------------------
233
+
234
+ async function executeTasks(request) {
235
+ const parentRecord = validateRequest(request);
236
+ const parent = parentRecord.snapshot;
237
+ const args = request.call.arguments ?? {};
238
+ const offset = args.offset ?? 0;
239
+ const limit = args.limit ?? 8;
240
+ const inventoryDigest = args.inventory_digest ?? null;
241
+ check(Number.isSafeInteger(offset) && offset >= 0, 'child_tasks_query_invalid');
242
+ check(Number.isSafeInteger(limit) && limit >= 1 && limit <= 16, 'child_tasks_query_invalid');
243
+ if (inventoryDigest !== null)
244
+ check(typeof inventoryDigest === 'string' && /^[0-9a-f]{64}$/.test(inventoryDigest), 'child_tasks_query_invalid');
245
+
246
+ // Build direct child inventory — only children loadable via loadDirectChild
247
+ const children = [];
248
+ const descendants = new Set();
249
+ for (const childId of parent.childRunIds ?? []) {
250
+ const childRecord = loadDirectChild(parent, childId);
251
+ if (childRecord) {
252
+ children.push(childRecord);
253
+ for (const descId of childRecord.snapshot.childRunIds ?? []) descendants.add(descId);
254
+ }
255
+ }
256
+ const directIds = new Set(children.map(r => r.snapshot.runId));
257
+ // If any child ID is neither a direct child nor a descendant of a direct child, inventory is unavailable
258
+ for (const childId of parent.childRunIds ?? []) {
259
+ if (!directIds.has(childId) && !descendants.has(childId))
260
+ return { status: 'error', content: { error_code: 'child_tasks_not_available' } };
261
+ }
262
+
263
+ // Delivery changes independently of the immutable startup reservation.
264
+ // Control availability comes from current durable ownership, not findings.
265
+ const promotionEnabled = parentRecord.configuration.childPromotionPolicy === 'owned-child-promotion/v1';
266
+ const promoted = new Set(parent.promotedChildRunIds ?? []);
267
+ const controlState = children.map(record => {
268
+ const child = record.snapshot;
269
+ const reservation = (parent.childBudgetReservations ?? []).find(item => item.childRunId === child.runId);
270
+ const foreground = reservation?.deliveryMode === 'foreground' && !promoted.has(child.runId);
271
+ const live = !terminal(parent.phase) && parent.phase !== 'finalizing' && !parent.childTerminalPhase
272
+ && !parentRecord.commands.some(row => !row.consumed && ['cancel', 'policy_revoked'].includes(row.command.kind))
273
+ && !terminal(child.phase) && !hasUnsafeReceipt(record)
274
+ && reservation?.settledUsageDigest === null;
275
+ return {
276
+ child_run_id: child.runId,
277
+ delivery: foreground ? 'foreground' : 'background',
278
+ can_promote: foreground && promotionEnabled && live,
279
+ can_cancel: live,
280
+ can_send_input: live,
281
+ };
282
+ });
283
+ const controlByRunId = new Map(controlState.map(item => [item.child_run_id, item]));
284
+
285
+ // Keep the v2 inventory identity stable while live metadata changes.
286
+ const digestInput = canonical({
287
+ inventory_policy: 'direct-child-inventory/v2',
288
+ scope: parentScope,
289
+ parent_run_id: parent.runId,
290
+ child_run_ids: children.map(r => r.snapshot.runId),
291
+ });
292
+ const computedDigest = textHash(digestInput);
293
+
294
+ if ((offset && inventoryDigest === null) || (inventoryDigest !== null && inventoryDigest !== computedDigest))
295
+ return { status: 'error', content: { error_code: 'child_tasks_inventory_changed' } };
296
+ if (offset > children.length)
297
+ return { status: 'error', content: { error_code: 'child_tasks_offset_invalid' } };
298
+
299
+ const tasks = children.slice(offset, offset + limit).map(record => {
300
+ const child = record.snapshot;
301
+ const binding = record.configuration.childOwnerBinding;
302
+ const goal = child.goal ?? '';
303
+ const finalText = child.finalText ?? '';
304
+ const control = controlByRunId.get(child.runId);
305
+ return {
306
+ child_run_id: child.runId,
307
+ child_session_id: child.sessionId,
308
+ role: binding.role,
309
+ parent_tool_call_id: binding.parentToolCallId,
310
+ snapshot_revision: child.revision,
311
+ phase: child.phase,
312
+ terminal: terminal(child.phase),
313
+ outcome: child.outcome,
314
+ delivery: control?.delivery ?? 'background',
315
+ can_promote: control?.can_promote ?? false,
316
+ can_cancel: control?.can_cancel ?? false,
317
+ can_send_input: control?.can_send_input ?? false,
318
+ goal_preview: [...goal].slice(0, 256).join(''),
319
+ goal_preview_trust: 'untrusted',
320
+ goal_preview_complete: [...goal].length <= 256,
321
+ goal_digest: textHash(goal),
322
+ final_text_chars: [...finalText].length,
323
+ final_text_digest: textHash(finalText),
324
+ result_tool: 'native_child_result',
325
+ parent_acceptance: 'not_evaluated',
326
+ };
327
+ });
328
+
329
+ const stop = offset + tasks.length;
330
+ return {
331
+ status: 'ok',
332
+ content: {
333
+ schema_version: 'native.child-tasks/v2',
334
+ parent_run_id: parent.runId,
335
+ parent_snapshot_revision: parent.revision,
336
+ inventory_digest: computedDigest,
337
+ tasks,
338
+ total_tasks: children.length,
339
+ offset,
340
+ next_offset: stop,
341
+ complete: stop === children.length,
342
+ parent_acceptance: 'not_evaluated',
343
+ execution_started: false,
344
+ resource_usage_accounted_again: false,
345
+ },
346
+ };
347
+ }
348
+
349
+ // --- Execute dispatcher --------------------------------------------------
350
+
351
+ async function execute(request) {
352
+ try {
353
+ if (request.call.name === 'native_child_result') return await executeResult(request);
354
+ if (request.call.name === 'native_child_tasks') return await executeTasks(request);
355
+ return { status: 'error', content: { error_code: 'child_reader_unknown_tool' } };
356
+ } catch (error) {
357
+ request.signal?.throwIfAborted();
358
+ if (error.name === 'AbortError') throw error;
359
+ if (isNativeError(error) && /^(child_|invalid_child_)/.test(error.code))
360
+ return { status: 'error', content: { error_code: error.code } };
361
+ return { status: 'uncertain', content: { error_code: 'child_reader_store_unavailable' } };
362
+ }
363
+ }
364
+
365
+ return Object.freeze({ definitions, execute });
366
+ }
@@ -0,0 +1,46 @@
1
+ import { invariant } from '@kingsoftcloud/agentkit-harness-core';
2
+ import { CHILD_CAPABILITY_POLICY, childRequiredToolsSchema } from './child-capabilities.mjs';
3
+ export { CHILD_CAPABILITY_POLICY, childCapabilityDirectory, admitChildCapabilities, childRequiredToolsSchema } from './child-capabilities.mjs';
4
+
5
+ export const NATIVE_CHILD_TOOLS_CONTRACT = 'agentkit.native-child-tools/v1';
6
+ export function nativeChildToolDefinitions(roles, startTimeoutMs = 120000, { childCapabilityPolicy, capabilities } = {}) {
7
+ invariant(childCapabilityPolicy === undefined || childCapabilityPolicy === CHILD_CAPABILITY_POLICY, 'invalid_child_capability_policy');
8
+ invariant(Array.isArray(roles) && roles.length > 0 && roles.length <= 32
9
+ && roles.every(role => typeof role === 'string' && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(role))
10
+ && new Set(roles).size === roles.length, 'native_child_roles_invalid');
11
+ const base = { readOnly: false, concurrentSafe: false, permissions: [], timeoutMs: 120000,
12
+ contextRole: 'none', observationRole: 'none', progressObservation: false, contextRetention: 'normal' };
13
+ invariant(Number.isSafeInteger(startTimeoutMs) && startTimeoutMs >= 120000, 'native_child_timeout_invalid');
14
+ const definitions = [{ ...base, timeoutMs: startTimeoutMs, name: 'native_start_child', childBudgetPolicy: 'start/v1',
15
+ description: 'Start a bounded approved child, in the background by default. background=false waits for its delivery. continuation_of reuses a terminal, collected child Session from this parent Session with a fresh Run and budget; collect it first. An early startup receipt reserves budget and is not findings.',
16
+ parameters: { type: 'object', additionalProperties: false, required: ['role', 'task'], properties: {
17
+ role: { type: 'string', enum: [...roles] }, task: { type: 'string', minLength: 1, maxLength: 32000 },
18
+ background: { type: 'boolean', default: true }, model_call_limit: { type: 'integer', minimum: 1, default: 4 },
19
+ tool_call_limit: { type: 'integer', minimum: 0, default: 8 },
20
+ output_format: { type: 'string', enum: ['text', 'json_object'] }, output_contract: { type: 'string', enum: ['parent'] },
21
+ continuation_of: { type: 'string', minLength: 1, maxLength: 256 },
22
+ media: { type: 'array', maxItems: 20, items: { type: 'object', additionalProperties: false, required: ['source', 'index'],
23
+ properties: { source: { type: 'string', enum: ['parent_user_input'] }, index: { type: 'integer', minimum: 0 } } },
24
+ description: 'Original content-part indices of images in the current admitted parent user input. Pass references only.' },
25
+ } } }, { ...base, name: 'native_child_control', childBudgetPolicy: 'settle/v1',
26
+ description: 'Send guidance, stop, or wait up to 30 seconds for a child owned by this parent. A running response is not completion. Sealed terminal results settle actual usage once.',
27
+ parameters: { type: 'object', additionalProperties: false, required: ['child_run_id', 'action'], properties: {
28
+ child_run_id: { type: 'string', minLength: 1, maxLength: 256 }, action: { type: 'string', enum: ['wait', 'send', 'stop'] },
29
+ message: { type: 'string', minLength: 1, maxLength: 32000 }, wait_seconds: { type: 'number', minimum: 0, maximum: 30, default: 15 },
30
+ } } }];
31
+ const delegate = structuredClone(definitions[0]);
32
+ delegate.name = 'native_delegate'; delegate.childBudgetPolicy = 'delegate/v1';
33
+ delegate.description = 'Run a bounded approved child and wait for its original delivery. continuation_of reuses a terminal, collected child Session with a fresh Run and budget. Its findings do not verify the parent task. background=true returns a budget reservation instead.';
34
+ delegate.parameters.properties.background.default = false;
35
+ for (const key of ['model_call_limit', 'tool_call_limit']) delete delegate.parameters.properties[key];
36
+ if (childCapabilityPolicy) {
37
+ invariant(Array.isArray(capabilities) && capabilities.length === roles.length
38
+ && roles.every(role => capabilities.some(item => item.role === role && Array.isArray(item.tools))), 'invalid_child_capability_directory');
39
+ for (const definition of [definitions[0], delegate]) {
40
+ definition.parameters.properties.required_tools = childRequiredToolsSchema();
41
+ definition.parameters.required.push('required_tools');
42
+ definition.description += '\nApproved role capabilities: ' + JSON.stringify(capabilities);
43
+ }
44
+ }
45
+ return [...definitions, delegate];
46
+ }
@@ -0,0 +1,29 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { join } from 'node:path';
3
+ import { canonical, invariant, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
4
+ import { DirectoryChildWorkspaces } from '@kingsoftcloud/agentkit-harness-node/native/child-workspaces';
5
+
6
+ export const STUDIO_NATIVE_CHILD_WORKSPACE_BINDING = 'agentkit.native-child-workspace-binding/v1';
7
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
8
+
9
+ /** Trusted Host inputs only. No model path or model-provided isolation id. */
10
+ export function createStudioNativeChildWorkspaces({ workspaceRoot, stateDir }) {
11
+ const files = new DirectoryChildWorkspaces({ source: workspaceRoot, storage: join(stateDir, 'child-workspaces') });
12
+ const sourceRootDigest = hash(files.source), storageRootDigest = hash(files.storage);
13
+ return {
14
+ contract: STUDIO_NATIVE_CHILD_WORKSPACE_BINDING,
15
+ bindingDigest: hash({ contract: STUDIO_NATIVE_CHILD_WORKSPACE_BINDING, sourceRootDigest, storageRootDigest }),
16
+ files,
17
+ bind({ scope, sessionId, role, existing = false, expectedBinding }) {
18
+ scopeKey(scope);
19
+ invariant(typeof sessionId === 'string' && sessionId.trim(), 'studio_native_child_workspace_session_missing');
20
+ const isolationId = hash({ contract: STUDIO_NATIVE_CHILD_WORKSPACE_BINDING, scope, sessionId, role, source: files.source });
21
+ const workspace = existing ? files.load(isolationId) : files.prepare(isolationId);
22
+ const binding = { contract: STUDIO_NATIVE_CHILD_WORKSPACE_BINDING, isolationId, baseRevision: workspace.baseRevision,
23
+ sourceRootDigest, storageRootDigest, roleDigest: hash(role) };
24
+ invariant(expectedBinding === undefined || canonical(expectedBinding) === canonical(binding),
25
+ 'studio_native_child_workspace_binding_changed');
26
+ return { workspace, root: workspace.root, binding, observer: files.observer(workspace) };
27
+ },
28
+ };
29
+ }
@@ -0,0 +1 @@
1
+ export { NATIVE_CHILD_WORKSPACE_TOOLS, NATIVE_CHILD_COMMAND_TOOLS } from '@kingsoftcloud/agentkit-harness-core/child-workspace-tools';