@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.
- 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,133 @@
|
|
|
1
|
+
// Host-only local-background cleanup controller. This is a private Host
|
|
2
|
+
// capability for conversation rewind: it cancels removed-branch local background
|
|
3
|
+
// commands while preserving retained rows exactly. It is not an advertised
|
|
4
|
+
// model tool, never falls back to an environment backend, never launches
|
|
5
|
+
// processes, fakes tools, mutates Native Run ledgers or marks notified. Retry
|
|
6
|
+
// is idempotent via the existing durable cancel file and terminal record.
|
|
7
|
+
import { canonical, copy, NativeError, backgroundCommandRef, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
|
|
8
|
+
import { ownedCommandReceipt } from './owned-command.mjs';
|
|
9
|
+
import { LocalCommandStore } from './local-background.mjs';
|
|
10
|
+
import { TERMINAL } from './local-command-state.mjs';
|
|
11
|
+
import { lstatSync, realpathSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
export const CONVERSATION_COMMAND_CONTROL_CONTRACT = 'agentkit.native-conversation-command-control/v1';
|
|
14
|
+
const STOP_WAIT_MS = 5000;
|
|
15
|
+
const ACCEPTED_BACKEND = 'local_process';
|
|
16
|
+
// wait() already rejects 'unknown'; this set is the explicit fail-closed guard.
|
|
17
|
+
const KNOWN_TERMINAL = new Set([...TERMINAL].filter(status => status !== 'unknown'));
|
|
18
|
+
|
|
19
|
+
const check = (condition, code) => { if (!condition) throw new NativeError(code); };
|
|
20
|
+
const same = (a, b) => canonical(a) === canonical(b);
|
|
21
|
+
|
|
22
|
+
export function createNativeConversationCommandControl({ workspaceRoot, stateRoot, scope, store, sessionId, isClosed = () => false }) {
|
|
23
|
+
check(typeof workspaceRoot === 'string' && !!workspaceRoot, 'conversation_command_workspace_root_invalid');
|
|
24
|
+
check(typeof stateRoot === 'string' && !!stateRoot, 'conversation_command_state_root_invalid');
|
|
25
|
+
check(typeof sessionId === 'string' && !!sessionId.trim(), 'conversation_command_session_id_invalid');
|
|
26
|
+
check(typeof isClosed === 'function', 'conversation_command_is_closed_invalid');
|
|
27
|
+
check(store && typeof store.load === 'function' && typeof store.sessionHead === 'function', 'conversation_command_store_invalid');
|
|
28
|
+
scopeKey(scope);
|
|
29
|
+
const pinnedScope = copy(scope);
|
|
30
|
+
// The owner binding is digest([realpath(workspace), 'local_process', sessionId]);
|
|
31
|
+
// commandId(operationKey) is digest([owner, operationKey]). Verifying the ref's
|
|
32
|
+
// taskId against this binding is what ties a head row to this local store.
|
|
33
|
+
// Match the launch transport's physical paths (macOS /var is an alias).
|
|
34
|
+
// The existing state directory itself must not be a substituted symlink.
|
|
35
|
+
check(!lstatSync(stateRoot).isSymbolicLink(), 'background_state_symlink');
|
|
36
|
+
const pinnedWorkspace = realpathSync(workspaceRoot), pinnedState = realpathSync(stateRoot);
|
|
37
|
+
const localStore = new LocalCommandStore({ stateRoot: pinnedState, workspaceRoot: pinnedWorkspace, sessionId });
|
|
38
|
+
|
|
39
|
+
async function settle({ record, retainedRefs, requestDigest, signal, assertCurrent }) {
|
|
40
|
+
check(record && typeof record === 'object' && record.snapshot && typeof record.snapshot === 'object',
|
|
41
|
+
'conversation_command_record_invalid');
|
|
42
|
+
check(signal instanceof AbortSignal, 'conversation_command_signal_required');
|
|
43
|
+
check(typeof assertCurrent === 'function', 'conversation_command_assert_current_required');
|
|
44
|
+
check(typeof requestDigest === 'string' && !!requestDigest, 'conversation_command_request_digest_invalid');
|
|
45
|
+
check(Array.isArray(retainedRefs), 'conversation_command_retained_refs_invalid');
|
|
46
|
+
signal.throwIfAborted();
|
|
47
|
+
const originalRecord = canonical(record), originalRefs = canonical(retainedRefs);
|
|
48
|
+
const pinnedRecord = copy(record);
|
|
49
|
+
check(same(pinnedRecord.snapshot.scope, pinnedScope), 'conversation_command_scope_mismatch');
|
|
50
|
+
check(pinnedRecord.snapshot.sessionId === sessionId, 'conversation_command_session_mismatch');
|
|
51
|
+
// assertCurrent is synchronous and must return exactly true, never a thenable.
|
|
52
|
+
const guard = () => {
|
|
53
|
+
signal.throwIfAborted();
|
|
54
|
+
check(!isClosed(), 'conversation_command_control_closed');
|
|
55
|
+
check(realpathSync(workspaceRoot) === pinnedWorkspace && realpathSync(stateRoot) === pinnedState,
|
|
56
|
+
'conversation_command_workspace_changed');
|
|
57
|
+
check(canonical(record) === originalRecord && canonical(retainedRefs) === originalRefs,
|
|
58
|
+
'conversation_command_input_changed');
|
|
59
|
+
const head = store.sessionHead(pinnedScope, sessionId);
|
|
60
|
+
check(head && head.runId === pinnedRecord.snapshot.runId, 'conversation_command_head_mismatch');
|
|
61
|
+
check(canonical(store.load(pinnedScope, head.runId)) === originalRecord, 'conversation_command_record_mismatch');
|
|
62
|
+
const result = assertCurrent();
|
|
63
|
+
if (result && typeof result.then === 'function') {
|
|
64
|
+
Promise.resolve(result).catch(() => {});
|
|
65
|
+
throw new NativeError('conversation_command_authority_thenable');
|
|
66
|
+
}
|
|
67
|
+
check(result === true, 'conversation_command_authority_lost');
|
|
68
|
+
signal.throwIfAborted();
|
|
69
|
+
check(canonical(record) === originalRecord && canonical(retainedRefs) === originalRefs
|
|
70
|
+
&& store.sessionHead(pinnedScope, sessionId)?.runId === pinnedRecord.snapshot.runId
|
|
71
|
+
&& canonical(store.load(pinnedScope, pinnedRecord.snapshot.runId)) === originalRecord,
|
|
72
|
+
'conversation_command_input_changed');
|
|
73
|
+
};
|
|
74
|
+
// Validate retained refs (shape + no duplicate taskIds).
|
|
75
|
+
const retained = retainedRefs.map(item => backgroundCommandRef(item));
|
|
76
|
+
check(new Set(retained.map(ref => ref.taskId)).size === retained.length, 'conversation_command_duplicate_ref');
|
|
77
|
+
guard();
|
|
78
|
+
signal.throwIfAborted();
|
|
79
|
+
|
|
80
|
+
// Verify ALL head rows' launch provenance before any external stop. This
|
|
81
|
+
// covers retained rows too: a retained row with a broken receipt fails the
|
|
82
|
+
// whole settlement rather than leaving a partial stop behind.
|
|
83
|
+
const headRows = pinnedRecord.snapshot.backgroundCommands;
|
|
84
|
+
check(Array.isArray(headRows), 'conversation_command_record_invalid');
|
|
85
|
+
check(new Set(headRows.map(row => row.ref.taskId)).size === headRows.length, 'conversation_command_duplicate_ref');
|
|
86
|
+
const verified = [];
|
|
87
|
+
for (const row of headRows) {
|
|
88
|
+
const ref = backgroundCommandRef(row.ref);
|
|
89
|
+
const receipt = ownedCommandReceipt({ scope: pinnedScope, store, current: pinnedRecord, ref });
|
|
90
|
+
check(receipt.backend === ACCEPTED_BACKEND, 'conversation_command_unsupported_backend');
|
|
91
|
+
check(localStore.commandId(ref.operationKey) === ref.taskId, 'conversation_command_task_id_mismatch');
|
|
92
|
+
verified.push({ row, ref });
|
|
93
|
+
}
|
|
94
|
+
// Foreign retained refs: every retainedRef must match a verified head row.
|
|
95
|
+
for (const ref of retained) {
|
|
96
|
+
check(verified.some(item => same(item.ref, ref)), 'conversation_command_foreign_ref');
|
|
97
|
+
}
|
|
98
|
+
// Partition head rows in original snapshot order.
|
|
99
|
+
const retainedRows = [], removedRows = [];
|
|
100
|
+
for (const item of verified) {
|
|
101
|
+
if (retained.some(ref => same(ref, item.ref))) retainedRows.push(item);
|
|
102
|
+
else removedRows.push(item);
|
|
103
|
+
}
|
|
104
|
+
// Stop removed rows with a bounded wait; preserve retained rows (never stop).
|
|
105
|
+
const stopped = [];
|
|
106
|
+
for (const item of removedRows) {
|
|
107
|
+
signal.throwIfAborted();
|
|
108
|
+
const handle = localStore.connect(item.ref.taskId);
|
|
109
|
+
guard(); // before the external mutation (durable cancel file)
|
|
110
|
+
handle.requestCancel();
|
|
111
|
+
let output;
|
|
112
|
+
try {
|
|
113
|
+
output = await handle.wait({ timeoutMs: STOP_WAIT_MS, signal });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
signal.throwIfAborted();
|
|
116
|
+
guard(); // after the await, even on the error path
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
guard(); // after the await
|
|
120
|
+
check(KNOWN_TERMINAL.has(output.status), 'conversation_command_stop_status_unknown');
|
|
121
|
+
stopped.push({ ref: copy(item.ref), status: output.status });
|
|
122
|
+
}
|
|
123
|
+
guard();
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
contract: CONVERSATION_COMMAND_CONTROL_CONTRACT,
|
|
126
|
+
requestDigest,
|
|
127
|
+
retained: retainedRows.map(item => copy(item.row)),
|
|
128
|
+
stopped,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Object.freeze({ contract: CONVERSATION_COMMAND_CONTROL_CONTRACT, settle });
|
|
133
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonical, invariant } from '@kingsoftcloud/agentkit-harness-core';
|
|
3
|
+
import { toolContext } from '@kingsoftcloud/agentkit-harness-core/tool-context';
|
|
4
|
+
|
|
5
|
+
const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
|
|
6
|
+
export const nativeDeliveryId = (scope, runId, key) => `ak-native-${hash({ identity: hash({ scope, runId }), key })}`;
|
|
7
|
+
|
|
8
|
+
/** Terminal delivery can close a call whose result never entered the transcript.
|
|
9
|
+
* Keep the same committed observation for delivery and later conversation cuts. */
|
|
10
|
+
export function nativeDeliveryClosure(record, callId) {
|
|
11
|
+
const intent = record.intents.find(item => item.kind === 'tool' && item.payload.calls?.some(call => call.callId === callId));
|
|
12
|
+
const receipt = intent && record.receipts.find(item => item.operationKey === intent.operationKey);
|
|
13
|
+
const index = intent?.payload.calls.findIndex(call => call.callId === callId);
|
|
14
|
+
const observed = receipt?.status === 'committed' && index >= 0 ? receipt.result.results?.[index] : null;
|
|
15
|
+
if (observed) return { context: observed.context === undefined ? { content: canonical(observed.content) } : toolContext(observed.context),
|
|
16
|
+
isError: observed.status !== 'ok' };
|
|
17
|
+
const unknown = intent?.status === 'running' && (!receipt || receipt.status === 'uncertain');
|
|
18
|
+
return { context: { content: canonical({ status: unknown ? 'uncertain' : 'not_dispatched',
|
|
19
|
+
reason: unknown ? 'effect_outcome_unknown' : record.snapshot.reason ?? 'run_ended' }) }, isError: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Rebuild the original immutable message positions before replaying output
|
|
23
|
+
* retirements. Both delivery and conversation cuts must use the same timeline. */
|
|
24
|
+
export function nativeDeliveryTimeline(record) {
|
|
25
|
+
const messages = structuredClone(record.snapshot.messages), retirements = new Map();
|
|
26
|
+
for (const event of record.events.filter(event => event.seq <= record.snapshot.eventSeq).toReversed()) {
|
|
27
|
+
if (event.kind !== 'output.restarted') continue;
|
|
28
|
+
const { retainedMessageCount: retained, retiredMessages: retired, notice } = event.payload;
|
|
29
|
+
invariant(Number.isSafeInteger(retained) && retained >= 0 && retained < messages.length && Array.isArray(retired)
|
|
30
|
+
&& retired.every(message => message && ['user', 'assistant'].includes(message.role)
|
|
31
|
+
&& typeof message.content === 'string' && ['continuation_fragment', 'continuation_observation'].includes(message.kind)
|
|
32
|
+
&& Object.keys(message).every(key => ['role', 'kind', 'content'].includes(key)))
|
|
33
|
+
&& canonical({ role: messages[retained].role, kind: messages[retained].kind ?? null, content: messages[retained].content })
|
|
34
|
+
=== canonical(notice), 'native_delivery_output_restart_invalid');
|
|
35
|
+
retirements.set(event.seq, { retained, retired, notice: structuredClone(messages[retained]) });
|
|
36
|
+
messages.splice(retained, 1, ...structuredClone(retired));
|
|
37
|
+
}
|
|
38
|
+
return { messages, retirements };
|
|
39
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { BACKGROUND_NOTIFICATION_ADMISSION, NATIVE_JSON_CODEC, NativeError, admitBackgroundNotification,
|
|
3
|
+
canonical, configure, invariant, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
|
|
4
|
+
import { nativeDshOwnershipHost } from './dsh-turn-ownership.mjs';
|
|
5
|
+
|
|
6
|
+
export const NATIVE_DSH_BACKGROUND_TURN_CONTRACT = 'agentkit.native-dsh-background-turn/v1';
|
|
7
|
+
export const NATIVE_DSH_BACKGROUND_ADMISSION = 'agentkit.native-dsh-background-admission/v1';
|
|
8
|
+
export const NATIVE_DSH_IDLE_ADMISSION = 'dsh.external-turn-start/v1';
|
|
9
|
+
const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
|
|
10
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
11
|
+
const digest = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
12
|
+
const fields = (value, names) => value && typeof value === 'object' && !Array.isArray(value)
|
|
13
|
+
&& Object.keys(value).sort().join(',') === names;
|
|
14
|
+
const identityOf = snapshot => Object.fromEntries(['scope', 'runId', 'sessionId', 'turnId', 'buildId', 'parentRunId', 'taskChainId']
|
|
15
|
+
.map(key => [key, snapshot[key]]));
|
|
16
|
+
|
|
17
|
+
/** Serialize facts about an already admitted notice; no ports or new user input.
|
|
18
|
+
* Native notice IDs remain stable across crashes before DSH ownership is flushed.
|
|
19
|
+
* The numeric DSH turn is a separate delivery position, never a replacement ID.
|
|
20
|
+
*/
|
|
21
|
+
export function nativeDshBackgroundOwnership({ record, turn }) {
|
|
22
|
+
const admission = { contract: NATIVE_DSH_BACKGROUND_ADMISSION, jsonCodec: NATIVE_JSON_CODEC,
|
|
23
|
+
identity: identityOf(record.snapshot), configuration: record.configuration,
|
|
24
|
+
configDigest: record.snapshot.configDigest, notification: record.snapshot.backgroundNotification };
|
|
25
|
+
const ownership = { contract: 'dsh.external-turn/v1', engine: NATIVE_DSH_BACKGROUND_TURN_CONTRACT,
|
|
26
|
+
turn, binding: canonical(admission) };
|
|
27
|
+
readNativeDshBackgroundOwnership(ownership, { sessionId: record.snapshot.sessionId, turn });
|
|
28
|
+
return ownership;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Parse detached recovery facts. Matching these facts never grants execution authority. */
|
|
32
|
+
export function readNativeDshBackgroundOwnership(ownership, { sessionId, turn }) {
|
|
33
|
+
invariant(fields(ownership, 'binding,contract,engine,turn') && ownership.contract === 'dsh.external-turn/v1'
|
|
34
|
+
&& ownership.engine === NATIVE_DSH_BACKGROUND_TURN_CONTRACT && ownership.turn === turn
|
|
35
|
+
&& Number.isSafeInteger(turn) && turn > 0 && text(ownership.binding), 'native_dsh_background_ownership_invalid');
|
|
36
|
+
const admission = parseNativeJson(ownership.binding);
|
|
37
|
+
invariant(fields(admission, 'configDigest,configuration,contract,identity,jsonCodec,notification')
|
|
38
|
+
&& admission.contract === NATIVE_DSH_BACKGROUND_ADMISSION && admission.jsonCodec === NATIVE_JSON_CODEC
|
|
39
|
+
&& canonical(admission) === ownership.binding, 'native_dsh_background_admission_invalid');
|
|
40
|
+
const { identity, configuration, notification } = admission;
|
|
41
|
+
invariant(fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
|
|
42
|
+
&& text(identity.sessionId) && identity.sessionId === sessionId && text(identity.buildId)
|
|
43
|
+
&& text(identity.taskChainId) && text(identity.parentRunId), 'native_dsh_background_identity_invalid');
|
|
44
|
+
scopeKey(identity.scope);
|
|
45
|
+
invariant(fields(notification, 'admissionElapsedMs,contract,requestDigest,sourceConfigDigest,sourceRunId,taskId')
|
|
46
|
+
&& notification.contract === BACKGROUND_NOTIFICATION_ADMISSION && text(notification.sourceRunId)
|
|
47
|
+
&& notification.sourceRunId !== identity.runId && digest(notification.taskId) && digest(notification.requestDigest)
|
|
48
|
+
&& digest(notification.sourceConfigDigest) && Number.isSafeInteger(notification.admissionElapsedMs)
|
|
49
|
+
&& notification.admissionElapsedMs >= 0, 'native_dsh_background_origin_invalid');
|
|
50
|
+
invariant(identity.runId === 'wake_' + hash({ contract: BACKGROUND_NOTIFICATION_ADMISSION, scope: identity.scope,
|
|
51
|
+
sourceRunId: notification.sourceRunId, taskId: notification.taskId }) && identity.turnId === identity.runId,
|
|
52
|
+
'native_dsh_background_identity_invalid');
|
|
53
|
+
invariant(hash(configuration) === admission.configDigest && canonical(configure(configuration)) === canonical(configuration)
|
|
54
|
+
&& configuration.backgroundNotificationPolicy === 'completed-before-model/v1' && configuration.inputMessages === null,
|
|
55
|
+
'native_dsh_background_configuration_invalid');
|
|
56
|
+
return admission;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertStoredAdmission(admission, record) {
|
|
60
|
+
invariant(canonical(identityOf(record.snapshot)) === canonical(admission.identity)
|
|
61
|
+
&& record.snapshot.configDigest === admission.configDigest && hash(record.configuration) === admission.configDigest
|
|
62
|
+
&& canonical(record.configuration) === canonical(admission.configuration)
|
|
63
|
+
&& canonical(record.snapshot.backgroundNotification ?? null) === canonical(admission.notification),
|
|
64
|
+
'native_dsh_background_stored_admission_changed');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Recheck the real ledger and original launch before trusted Host port rebinding.
|
|
68
|
+
* This only takes the core admission's duplicate path: a missing record cannot
|
|
69
|
+
* poll, recreate a notice, start a model or relaunch a command. A live notice
|
|
70
|
+
* must remain the session head; terminal notices can still be delivered again.
|
|
71
|
+
*/
|
|
72
|
+
export async function verifyNativeDshBackgroundOwnership(ownership, { store, scope, sessionId, turn, signal }) {
|
|
73
|
+
invariant(signal instanceof AbortSignal, 'native_dsh_background_signal_required');
|
|
74
|
+
signal.throwIfAborted();
|
|
75
|
+
scopeKey(scope);
|
|
76
|
+
const admission = readNativeDshBackgroundOwnership(ownership, { sessionId, turn });
|
|
77
|
+
invariant(canonical(admission.identity.scope) === canonical(scope), 'native_dsh_background_scope_mismatch');
|
|
78
|
+
const { identity, notification } = admission;
|
|
79
|
+
assertStoredAdmission(admission, store.load(scope, identity.runId));
|
|
80
|
+
const duplicate = await admitBackgroundNotification({ store, scope, sourceRunId: notification.sourceRunId,
|
|
81
|
+
taskId: notification.taskId, digest: hash, signal,
|
|
82
|
+
observe: async () => { throw new NativeError('native_dsh_background_admission_missing'); } });
|
|
83
|
+
signal.throwIfAborted();
|
|
84
|
+
invariant(duplicate.created === false && duplicate.snapshot.runId === identity.runId,
|
|
85
|
+
'native_dsh_background_admission_missing');
|
|
86
|
+
const record = store.load(scope, identity.runId);
|
|
87
|
+
assertStoredAdmission(admission, record);
|
|
88
|
+
const source = store.load(scope, notification.sourceRunId);
|
|
89
|
+
invariant(source.snapshot.sessionId === sessionId && source.snapshot.buildId === identity.buildId
|
|
90
|
+
&& source.snapshot.taskChainId === identity.taskChainId && canonical(source.snapshot.scope) === canonical(scope),
|
|
91
|
+
'native_dsh_background_source_identity_changed');
|
|
92
|
+
signal.throwIfAborted();
|
|
93
|
+
return { admission, record, source };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Require both the maintained provider version and its actual idle-start API. */
|
|
97
|
+
export function nativeDshIdleOwnershipHost(agent) {
|
|
98
|
+
const host = nativeDshOwnershipHost(agent);
|
|
99
|
+
invariant(host.idleAdmissionContract === NATIVE_DSH_IDLE_ADMISSION && typeof host.start === 'function',
|
|
100
|
+
'native_dsh_idle_ownership_host_required');
|
|
101
|
+
return host;
|
|
102
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { canonical, invariant, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
|
|
2
|
+
|
|
3
|
+
export const NATIVE_DSH_MANAGED_TURN_CONTRACT = 'agentkit.native-managed-dsh-turn/v1';
|
|
4
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
5
|
+
const fields = (value, keys) => value && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
&& Object.keys(value).sort().join(',') === keys;
|
|
7
|
+
|
|
8
|
+
/** Persisted delivery identity, never an execution grant. The trusted Host must
|
|
9
|
+
* separately own the SDK turn and rebind the original Native configuration. */
|
|
10
|
+
export function readNativeDshManagedOwnership(owner, { sessionId, turn } = {}) {
|
|
11
|
+
invariant(fields(owner, 'binding,contract,engine,turn') && owner.contract === 'dsh.external-turn/v1'
|
|
12
|
+
&& owner.engine === NATIVE_DSH_MANAGED_TURN_CONTRACT && Number.isSafeInteger(owner.turn) && owner.turn > 0
|
|
13
|
+
&& (turn === undefined || turn === owner.turn) && text(owner.binding), 'NATIVE_MANAGED_OWNER_INVALID');
|
|
14
|
+
const value = parseNativeJson(owner.binding), identity = value.identity;
|
|
15
|
+
invariant(fields(value, 'configDigest,contract,identity') && value.contract === NATIVE_DSH_MANAGED_TURN_CONTRACT
|
|
16
|
+
&& canonical(value) === owner.binding && /^[a-f0-9]{64}$/u.test(value.configDigest)
|
|
17
|
+
&& fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
|
|
18
|
+
&& ['runId', 'sessionId', 'turnId', 'buildId', 'taskChainId'].every(key => text(identity[key]))
|
|
19
|
+
&& (identity.parentRunId === null || text(identity.parentRunId))
|
|
20
|
+
&& (sessionId === undefined || identity.sessionId === sessionId), 'NATIVE_MANAGED_OWNER_INVALID');
|
|
21
|
+
scopeKey(identity.scope);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonical, configure, invariant, NATIVE_JSON_CODEC, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
|
|
3
|
+
import { NATIVE_DSH_TURN_CONTRACT } from './dsh-turn-contract.mjs';
|
|
4
|
+
|
|
5
|
+
export const NATIVE_DSH_ADMISSION_CONTRACT = 'agentkit.native-dsh-admission/v1';
|
|
6
|
+
export const NATIVE_DSH_OWNERSHIP_HOST = 'dsh.external-turn-host/v1';
|
|
7
|
+
const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
|
|
8
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
9
|
+
const fields = (value, names) => value && typeof value === 'object' && !Array.isArray(value)
|
|
10
|
+
&& Object.keys(value).sort().join(',') === names;
|
|
11
|
+
|
|
12
|
+
/** Complete admission data, never live ports, credentials or execution authority. */
|
|
13
|
+
export function nativeDshOwnership({ identity, configuration, input, source }) {
|
|
14
|
+
const admission = { contract: NATIVE_DSH_ADMISSION_CONTRACT, jsonCodec: NATIVE_JSON_CODEC,
|
|
15
|
+
identity, configuration, configDigest: hash(configuration), messages: input.messages, source };
|
|
16
|
+
const ownership = { contract: 'dsh.external-turn/v1', engine: NATIVE_DSH_TURN_CONTRACT,
|
|
17
|
+
turn: Number(identity.turnId), binding: canonical(admission) };
|
|
18
|
+
readNativeDshOwnership(ownership, { sessionId: identity.sessionId, turn: ownership.turn });
|
|
19
|
+
return ownership;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Validate persisted facts before a trusted Host resolves the original preset. */
|
|
23
|
+
export function readNativeDshOwnership(ownership, { sessionId, turn }) {
|
|
24
|
+
invariant(fields(ownership, 'binding,contract,engine,turn') && ownership.contract === 'dsh.external-turn/v1'
|
|
25
|
+
&& ownership.engine === NATIVE_DSH_TURN_CONTRACT && ownership.turn === turn
|
|
26
|
+
&& Number.isSafeInteger(turn) && turn > 0 && text(ownership.binding), 'native_dsh_ownership_invalid');
|
|
27
|
+
const admission = parseNativeJson(ownership.binding);
|
|
28
|
+
invariant(fields(admission, 'configDigest,configuration,contract,identity,jsonCodec,messages,source')
|
|
29
|
+
&& admission.contract === NATIVE_DSH_ADMISSION_CONTRACT && admission.jsonCodec === NATIVE_JSON_CODEC
|
|
30
|
+
&& canonical(admission) === ownership.binding, 'native_dsh_admission_invalid');
|
|
31
|
+
const { identity, configuration, source, messages } = admission;
|
|
32
|
+
invariant(fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
|
|
33
|
+
&& text(identity.sessionId) && identity.sessionId === sessionId && identity.turnId === String(turn)
|
|
34
|
+
&& text(identity.buildId) && text(identity.taskChainId)
|
|
35
|
+
&& (identity.parentRunId === null || text(identity.parentRunId)), 'native_dsh_admission_identity_invalid');
|
|
36
|
+
scopeKey(identity.scope);
|
|
37
|
+
invariant(identity.runId === `native-dsh-${hash({ contract: NATIVE_DSH_TURN_CONTRACT,
|
|
38
|
+
scope: identity.scope, sessionId, turn })}`, 'native_dsh_admission_identity_invalid');
|
|
39
|
+
invariant(fields(source, 'model,provider') && text(source.model) && text(source.provider)
|
|
40
|
+
&& Array.isArray(messages) && messages.length > 0
|
|
41
|
+
&& messages.every(message => message?.role === 'user' && text(message.id) && Array.isArray(message.content)),
|
|
42
|
+
'native_dsh_admission_input_invalid');
|
|
43
|
+
invariant(hash(configuration) === admission.configDigest
|
|
44
|
+
&& canonical(configure(configuration)) === canonical(configuration), 'native_dsh_admission_configuration_invalid');
|
|
45
|
+
return admission;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The Host must provide the maintained lifecycle capability before any dispatch. */
|
|
49
|
+
export function nativeDshOwnershipHost(agent) {
|
|
50
|
+
const host = agent?.externalTurns;
|
|
51
|
+
invariant(host?.contract === NATIVE_DSH_OWNERSHIP_HOST && typeof host.claim === 'function'
|
|
52
|
+
&& typeof host.pending === 'function' && typeof host.resume === 'function', 'native_dsh_ownership_host_required');
|
|
53
|
+
return host;
|
|
54
|
+
}
|