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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +1 -0
  3. package/README.md +124 -2
  4. package/THIRD_PARTY_NOTICES.md +66 -0
  5. package/ZCode-LICENSE +202 -0
  6. package/dist/index.mjs +4871 -0
  7. package/dist/native-command-plan.LICENSE.txt +221 -0
  8. package/dist/native-command-plan.json +16 -0
  9. package/dist/native-command-plan.mjs +8 -0
  10. package/dist/rpc-entry.mjs +5682 -0
  11. package/package.json +169 -4
  12. package/src/native/a2a-receipt-source.mjs +19 -0
  13. package/src/native/a2a-tools.mjs +517 -0
  14. package/src/native/activity-status.mjs +30 -0
  15. package/src/native/artifact-codec.mjs +481 -0
  16. package/src/native/artifact-store.mjs +1162 -0
  17. package/src/native/assistant-stream.mjs +76 -0
  18. package/src/native/background-command-port.mjs +95 -0
  19. package/src/native/browser-action-observation.mjs +43 -0
  20. package/src/native/browser-availability.mjs +22 -0
  21. package/src/native/browser-canvas-metrics.mjs +71 -0
  22. package/src/native/browser-dom.mjs +193 -0
  23. package/src/native/browser-playwright.mjs +1249 -0
  24. package/src/native/browser-vision.mjs +369 -0
  25. package/src/native/browser-workspace-policy.mjs +22 -0
  26. package/src/native/browser-workspace-protocol.mjs +95 -0
  27. package/src/native/browser-workspace-server.mjs +164 -0
  28. package/src/native/browser-workspace.mjs +355 -0
  29. package/src/native/captured-source.mjs +3 -0
  30. package/src/native/child-budget-allocation.mjs +87 -0
  31. package/src/native/child-capabilities.mjs +35 -0
  32. package/src/native/child-command-resource.mjs +20 -0
  33. package/src/native/child-history.mjs +188 -0
  34. package/src/native/child-merge.mjs +98 -0
  35. package/src/native/child-model-surface.mjs +19 -0
  36. package/src/native/child-observation.mjs +100 -0
  37. package/src/native/child-owner.mjs +620 -0
  38. package/src/native/child-publication.mjs +76 -0
  39. package/src/native/child-readers.mjs +366 -0
  40. package/src/native/child-tools.mjs +46 -0
  41. package/src/native/child-workspace-binding.mjs +29 -0
  42. package/src/native/child-workspace-tools.mjs +1 -0
  43. package/src/native/child-workspaces.mjs +655 -0
  44. package/src/native/children.mjs +365 -0
  45. package/src/native/command-plan-worker.mjs +14 -0
  46. package/src/native/command-plan.mjs +31 -0
  47. package/src/native/command-progress.mjs +64 -0
  48. package/src/native/command-shell.mjs +9 -0
  49. package/src/native/command-workspace-observation.mjs +36 -0
  50. package/src/native/component-tools.mjs +280 -0
  51. package/src/native/context-counter.mjs +133 -0
  52. package/src/native/context-markers.mjs +51 -0
  53. package/src/native/context-profile.mjs +72 -0
  54. package/src/native/context-status.mjs +27 -0
  55. package/src/native/conversation-command-control.mjs +133 -0
  56. package/src/native/delivery-timeline.mjs +39 -0
  57. package/src/native/dsh-background-ownership.mjs +102 -0
  58. package/src/native/dsh-managed-ownership.mjs +23 -0
  59. package/src/native/dsh-turn-contract.mjs +3 -0
  60. package/src/native/dsh-turn-ownership.mjs +54 -0
  61. package/src/native/dsh-turn-runtime.mjs +498 -0
  62. package/src/native/e2b-background-supervisor.mjs +347 -0
  63. package/src/native/e2b-background.mjs +368 -0
  64. package/src/native/e2b-command-state.mjs +40 -0
  65. package/src/native/e2b-command-tools.mjs +486 -0
  66. package/src/native/e2b-sandbox-owner.mjs +227 -0
  67. package/src/native/hook-executor.mjs +249 -0
  68. package/src/native/instruction-assembly.mjs +60 -0
  69. package/src/native/instruction-context.mjs +54 -0
  70. package/src/native/local-background-supervisor.mjs +108 -0
  71. package/src/native/local-background.mjs +207 -0
  72. package/src/native/local-command-evidence.mjs +57 -0
  73. package/src/native/local-command-policy-worker.mjs +197 -0
  74. package/src/native/local-command-policy.mjs +46 -0
  75. package/src/native/local-command-state.mjs +177 -0
  76. package/src/native/local-command-tools.mjs +302 -0
  77. package/src/native/local-execution-profile.mjs +36 -0
  78. package/src/native/local-output-policy.mjs +92 -0
  79. package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
  80. package/src/native/local-process-bin/darwin-x64/launcher +0 -0
  81. package/src/native/local-process-bin/linux-arm64/launcher +0 -0
  82. package/src/native/local-process-bin/linux-x64/launcher +0 -0
  83. package/src/native/local-process-launcher.c +183 -0
  84. package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
  85. package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
  86. package/src/native/local-process-licenses/NOTICE +21 -0
  87. package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
  88. package/src/native/local-process-manifest.json +32 -0
  89. package/src/native/local-process.mjs +246 -0
  90. package/src/native/local-resource-profile.mjs +32 -0
  91. package/src/native/memory-candidate.mjs +121 -0
  92. package/src/native/memory-extraction-run.mjs +60 -0
  93. package/src/native/memory-extraction-tools.mjs +116 -0
  94. package/src/native/memory-recall.mjs +65 -0
  95. package/src/native/memory-store.mjs +334 -0
  96. package/src/native/memory-tools.mjs +329 -0
  97. package/src/native/model-anthropic-cache.mjs +49 -0
  98. package/src/native/model-anthropic-tool-search.mjs +85 -0
  99. package/src/native/model-anthropic.mjs +753 -0
  100. package/src/native/model-chat-usage.mjs +24 -0
  101. package/src/native/model-inline-reasoning.mjs +111 -0
  102. package/src/native/model-media.mjs +174 -0
  103. package/src/native/model-normalize.mjs +228 -0
  104. package/src/native/model-openai-chat.mjs +561 -0
  105. package/src/native/model-port.mjs +361 -0
  106. package/src/native/model-provider-compat.mjs +129 -0
  107. package/src/native/model-replay.mjs +153 -0
  108. package/src/native/model-request.mjs +15 -0
  109. package/src/native/model-responses.mjs +1205 -0
  110. package/src/native/model-sse.mjs +171 -0
  111. package/src/native/model-surface-host.mjs +27 -0
  112. package/src/native/model-tool-search-policy.mjs +16 -0
  113. package/src/native/model-transport-observation.mjs +27 -0
  114. package/src/native/owned-child.mjs +114 -0
  115. package/src/native/owned-command.mjs +106 -0
  116. package/src/native/python-syntax-worker.mjs +60 -0
  117. package/src/native/python-syntax.mjs +50 -0
  118. package/src/native/read-interruption.mjs +16 -0
  119. package/src/native/result-stream.mjs +63 -0
  120. package/src/native/ruff-ast.mjs +95 -0
  121. package/src/native/run-observation.mjs +128 -0
  122. package/src/native/session-delivery-journal.mjs +96 -0
  123. package/src/native/session-delivery.mjs +434 -0
  124. package/src/native/shell-analysis.mjs +50 -0
  125. package/src/native/skill-disclosure.mjs +404 -0
  126. package/src/native/skill-events.mjs +164 -0
  127. package/src/native/skill-tools.mjs +813 -0
  128. package/src/native/source-page.mjs +73 -0
  129. package/src/native/sqlite-session-inbox.mjs +42 -0
  130. package/src/native/sqlite-store.mjs +617 -0
  131. package/src/native/tool-authority.mjs +99 -0
  132. package/src/native/tool-guidance.mjs +12 -0
  133. package/src/native/tool-observation.mjs +75 -0
  134. package/src/native/tool-states.mjs +14 -0
  135. package/src/native/verification-authority.mjs +37 -0
  136. package/src/native/web-tools.mjs +643 -0
  137. package/src/native/workspace-checkpoints.mjs +312 -0
  138. package/src/native/workspace-context.mjs +61 -0
  139. package/src/native/workspace-display.mjs +39 -0
  140. package/src/native/workspace-edit-match.mjs +19 -0
  141. package/src/native/workspace-edit-worker.mjs +15 -0
  142. package/src/native/workspace-editing.mjs +447 -0
  143. package/src/native/workspace-image.mjs +72 -0
  144. package/src/native/workspace-instructions.mjs +107 -0
  145. package/src/native/workspace-mutation-lock.mjs +74 -0
  146. package/src/native/workspace-patch-worker.mjs +17 -0
  147. package/src/native/workspace-patch.mjs +17 -0
  148. package/src/native/workspace-paths.mjs +225 -0
  149. package/src/native/workspace-state.mjs +272 -0
  150. package/src/native/workspace-subject-observer.mjs +84 -0
  151. package/src/native/workspace-tools.mjs +1740 -0
@@ -0,0 +1,21 @@
1
+ The Node local-process adapter uses a small executable built from the adjacent
2
+ local-process-launcher.c (the repository's Apache-2.0 license). It performs only
3
+ POSIX resource-limit, fork/watchdog and exec operations; it is not a language
4
+ interpreter or an execution policy.
5
+
6
+ Darwin executables dynamically use the operating system's libSystem; no Apple
7
+ runtime library is bundled. Linux executables are built with Alpine GCC 14.2.0
8
+ and statically link musl 1.2.5. The Linux owner watchdog enforces
9
+ maxProcesses per process group because RLIMIT_NPROC is scoped to the whole real
10
+ user on shared hosts. The included musl COPYRIGHT is from the upstream 1.2.5
11
+ source release. GCC runtime terms are included for compiler runtime parts:
12
+ COPYING3 plus the GCC Runtime Library Exception (COPYING.RUNTIME).
13
+
14
+ Upstream license sources:
15
+ https://musl.libc.org/releases/musl-1.2.5.tar.gz (COPYRIGHT)
16
+ https://github.com/gcc-mirror/gcc/blob/releases/gcc-14.2.0/COPYING3
17
+ https://github.com/gcc-mirror/gcc/blob/releases/gcc-14.2.0/COPYING.RUNTIME
18
+
19
+ Compiler identification, base-image digest and each executable's SHA-256 are
20
+ recorded in local-process-manifest.json. Rebuilds are a maintainer operation;
21
+ installation and execution never invoke a compiler, package manager or Python.
@@ -0,0 +1,193 @@
1
+ musl as a whole is licensed under the following standard MIT license:
2
+
3
+ ----------------------------------------------------------------------
4
+ Copyright © 2005-2020 Rich Felker, et al.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ ----------------------------------------------------------------------
25
+
26
+ Authors/contributors include:
27
+
28
+ A. Wilcox
29
+ Ada Worcester
30
+ Alex Dowad
31
+ Alex Suykov
32
+ Alexander Monakov
33
+ Andre McCurdy
34
+ Andrew Kelley
35
+ Anthony G. Basile
36
+ Aric Belsito
37
+ Arvid Picciani
38
+ Bartosz Brachaczek
39
+ Benjamin Peterson
40
+ Bobby Bingham
41
+ Boris Brezillon
42
+ Brent Cook
43
+ Chris Spiegel
44
+ Clément Vasseur
45
+ Daniel Micay
46
+ Daniel Sabogal
47
+ Daurnimator
48
+ David Carlier
49
+ David Edelsohn
50
+ Denys Vlasenko
51
+ Dmitry Ivanov
52
+ Dmitry V. Levin
53
+ Drew DeVault
54
+ Emil Renner Berthing
55
+ Fangrui Song
56
+ Felix Fietkau
57
+ Felix Janda
58
+ Gianluca Anzolin
59
+ Hauke Mehrtens
60
+ He X
61
+ Hiltjo Posthuma
62
+ Isaac Dunham
63
+ Jaydeep Patil
64
+ Jens Gustedt
65
+ Jeremy Huntwork
66
+ Jo-Philipp Wich
67
+ Joakim Sindholt
68
+ John Spencer
69
+ Julien Ramseier
70
+ Justin Cormack
71
+ Kaarle Ritvanen
72
+ Khem Raj
73
+ Kylie McClain
74
+ Leah Neukirchen
75
+ Luca Barbato
76
+ Luka Perkov
77
+ M Farkas-Dyck (Strake)
78
+ Mahesh Bodapati
79
+ Markus Wichmann
80
+ Masanori Ogino
81
+ Michael Clark
82
+ Michael Forney
83
+ Mikhail Kremnyov
84
+ Natanael Copa
85
+ Nicholas J. Kain
86
+ orc
87
+ Pascal Cuoq
88
+ Patrick Oppenlander
89
+ Petr Hosek
90
+ Petr Skocik
91
+ Pierre Carrier
92
+ Reini Urban
93
+ Rich Felker
94
+ Richard Pennington
95
+ Ryan Fairfax
96
+ Samuel Holland
97
+ Segev Finer
98
+ Shiz
99
+ sin
100
+ Solar Designer
101
+ Stefan Kristiansson
102
+ Stefan O'Rear
103
+ Szabolcs Nagy
104
+ Timo Teräs
105
+ Trutz Behn
106
+ Valentin Ochs
107
+ Will Dietz
108
+ William Haddon
109
+ William Pitcock
110
+
111
+ Portions of this software are derived from third-party works licensed
112
+ under terms compatible with the above MIT license:
113
+
114
+ The TRE regular expression implementation (src/regex/reg* and
115
+ src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
116
+ under a 2-clause BSD license (license text in the source files). The
117
+ included version has been heavily modified by Rich Felker in 2012, in
118
+ the interests of size, simplicity, and namespace cleanliness.
119
+
120
+ Much of the math library code (src/math/* and src/complex/*) is
121
+ Copyright © 1993,2004 Sun Microsystems or
122
+ Copyright © 2003-2011 David Schultz or
123
+ Copyright © 2003-2009 Steven G. Kargl or
124
+ Copyright © 2003-2009 Bruce D. Evans or
125
+ Copyright © 2008 Stephen L. Moshier or
126
+ Copyright © 2017-2018 Arm Limited
127
+ and labelled as such in comments in the individual source files. All
128
+ have been licensed under extremely permissive terms.
129
+
130
+ The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008
131
+ The Android Open Source Project and is licensed under a two-clause BSD
132
+ license. It was taken from Bionic libc, used on Android.
133
+
134
+ The AArch64 memcpy and memset code (src/string/aarch64/*) are
135
+ Copyright © 1999-2019, Arm Limited.
136
+
137
+ The implementation of DES for crypt (src/crypt/crypt_des.c) is
138
+ Copyright © 1994 David Burren. It is licensed under a BSD license.
139
+
140
+ The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
141
+ originally written by Solar Designer and placed into the public
142
+ domain. The code also comes with a fallback permissive license for use
143
+ in jurisdictions that may not recognize the public domain.
144
+
145
+ The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
146
+ Valentin Ochs and is licensed under an MIT-style license.
147
+
148
+ The x86_64 port was written by Nicholas J. Kain and is licensed under
149
+ the standard MIT terms.
150
+
151
+ The mips and microblaze ports were originally written by Richard
152
+ Pennington for use in the ellcc project. The original code was adapted
153
+ by Rich Felker for build system and code conventions during upstream
154
+ integration. It is licensed under the standard MIT terms.
155
+
156
+ The mips64 port was contributed by Imagination Technologies and is
157
+ licensed under the standard MIT terms.
158
+
159
+ The powerpc port was also originally written by Richard Pennington,
160
+ and later supplemented and integrated by John Spencer. It is licensed
161
+ under the standard MIT terms.
162
+
163
+ All other files which have no copyright comments are original works
164
+ produced specifically for use as part of this library, written either
165
+ by Rich Felker, the main author of the library, or by one or more
166
+ contibutors listed above. Details on authorship of individual files
167
+ can be found in the git version control history of the project. The
168
+ omission of copyright and license comments in each file is in the
169
+ interest of source tree size.
170
+
171
+ In addition, permission is hereby granted for all public header files
172
+ (include/* and arch/*/bits/*) and crt files intended to be linked into
173
+ applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
174
+ the copyright notice and permission notice otherwise required by the
175
+ license, and to use these files without any requirement of
176
+ attribution. These files include substantial contributions from:
177
+
178
+ Bobby Bingham
179
+ John Spencer
180
+ Nicholas J. Kain
181
+ Rich Felker
182
+ Richard Pennington
183
+ Stefan Kristiansson
184
+ Szabolcs Nagy
185
+
186
+ all of whom have explicitly granted such permission.
187
+
188
+ This file previously contained text expressing a belief that most of
189
+ the files covered by the above exception were sufficiently trivial not
190
+ to be subject to copyright, resulting in confusion over whether it
191
+ negated the permissions granted in the license. In the spirit of
192
+ permissive licensing, and of not having licensing issues being an
193
+ obstacle to adoption, that text has been removed.
@@ -0,0 +1,32 @@
1
+ {
2
+ "schemaVersion": "agentkit.local-process-binary/v1",
3
+ "sourceSha256": "aec24ff7327319074bd7dd66fcde657f55179556f8a7e954303ea3f9eb135f0e",
4
+ "builds": {
5
+ "darwin-arm64": {
6
+ "file": "local-process-bin/darwin-arm64/launcher",
7
+ "sha256": "202cb67e15c09285123b60a9b72c4a8b5824e49e3d8c30746eae2ffb681c22e8",
8
+ "bytes": 50944,
9
+ "compiler": "Apple clang version 21.0.0 (clang-2100.3.34.2)"
10
+ },
11
+ "darwin-x64": {
12
+ "file": "local-process-bin/darwin-x64/launcher",
13
+ "sha256": "756085384f20c8690b43ea4b304b5acd10ecb438f12c51cf1d78ad545d594fe3",
14
+ "bytes": 17640,
15
+ "compiler": "Apple clang version 21.0.0 (clang-2100.3.34.2)"
16
+ },
17
+ "linux-x64": {
18
+ "file": "local-process-bin/linux-x64/launcher",
19
+ "sha256": "15205c3219e9771855c0c64812639a589f5d58d2cb6cb5ede9c07a416ecb64b1",
20
+ "bytes": 62840,
21
+ "compiler": "cc (Alpine 14.2.0) 14.2.0",
22
+ "image": "docker.m.daocloud.io/library/alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412"
23
+ },
24
+ "linux-arm64": {
25
+ "file": "local-process-bin/linux-arm64/launcher",
26
+ "sha256": "c59bf87805d0887cd142f04eff5047146f5ec5d594d400042bda3ce40984b670",
27
+ "bytes": 132376,
28
+ "compiler": "cc (Alpine 14.2.0) 14.2.0",
29
+ "image": "docker.m.daocloud.io/library/alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412"
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,246 @@
1
+ // Node owns the bounded lifetime; a packaged, verified POSIX exec substrate
2
+ // applies resource controls and an owner-pipe watchdog. This is a risk control for
3
+ // same-container commands, not an OS security sandbox or an execution policy.
4
+ import { spawn } from 'node:child_process';
5
+ import { createHash } from 'node:crypto';
6
+ import { readFileSync, lstatSync, constants } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { join } from 'node:path';
9
+ import { constants as osConstants } from 'node:os';
10
+ import { StringDecoder } from 'node:string_decoder';
11
+ import { performance } from 'node:perf_hooks';
12
+ import { localEnvironmentPolicy, localOutputPolicy, resolveEnvironmentPolicy, resolveOutputPolicy } from './local-output-policy.mjs';
13
+
14
+ export const LOCAL_PROCESS_CONTRACT = 'agentkit.local-process-control/v1';
15
+ const directory = fileURLToPath(new URL('./', import.meta.url));
16
+ const defaults = Object.freeze({ cpuSeconds: 120, addressSpaceBytes: 1024 ** 3, maxProcesses: 64,
17
+ maxOpenFiles: 256, maxFileBytes: 64 * 1024 ** 2, maxOutputBytes: 1024 ** 2, wallSeconds: 900, extraEnvNames: [] });
18
+ const limitNames = ['cpuSeconds', 'addressSpaceBytes', 'maxProcesses', 'maxOpenFiles', 'maxFileBytes'];
19
+ export class LocalProcessError extends Error { constructor(code) { super(code); this.name = 'LocalProcessError'; this.code = code; } }
20
+ const check = (value, code) => { if (!value) throw new LocalProcessError(code); };
21
+ const hash = value => createHash('sha256').update(value).digest('hex');
22
+ const wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
23
+ async function boundedWait(promise, milliseconds) {
24
+ let timer;
25
+ try { await Promise.race([promise, new Promise(resolve => { timer = setTimeout(resolve, milliseconds); })]); }
26
+ finally { clearTimeout(timer); }
27
+ }
28
+ const namePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
29
+ export function localControlSettings(input = {}) {
30
+ check(input && typeof input === 'object' && !Array.isArray(input)
31
+ && Object.keys(input).every(key => Object.hasOwn(defaults, key)), 'invalid_local_process_settings');
32
+ const settings = { ...defaults, ...input };
33
+ for (const [name, value] of Object.entries(settings)) if (name !== 'extraEnvNames')
34
+ check(Number.isSafeInteger(value) && value > 0, `invalid_local_process_limit:${name}`);
35
+ check(settings.wallSeconds <= Number.MAX_SAFE_INTEGER / 1000, 'invalid_local_process_wall_limit');
36
+ check(Array.isArray(settings.extraEnvNames) && settings.extraEnvNames.every(name => typeof name === 'string'
37
+ && namePattern.test(name) && !name.startsWith('KSADK_LOCAL_PROCESS_')), 'invalid_local_process_environment_allowlist');
38
+ settings.extraEnvNames = Object.freeze([...new Set(settings.extraEnvNames)]);
39
+ return Object.freeze(settings);
40
+ }
41
+ export function buildScriptEnvironment(source, { settings = localControlSettings(), required = {}, environmentPolicy = null } = {}) {
42
+ const policy = localControlSettings(settings), env = resolveEnvironmentPolicy(policy, localEnvironmentPolicy(environmentPolicy)), result = Object.create(null);
43
+ for (const name of ['PATH', 'LANG', 'LC_ALL', 'TZ', 'SYSTEMROOT', ...env.names, ...policy.extraEnvNames])
44
+ if (Object.hasOwn(source, name)) result[name] = String(source[name]);
45
+ result.PATH ??= '/bin:/usr/bin'; result.LANG ??= 'C.UTF-8'; result.CI = '1';
46
+ for (const [name, value] of Object.entries(required)) { check(namePattern.test(name), 'invalid_local_process_environment'); result[name] = String(value); }
47
+ return result;
48
+ }
49
+ export function supportsLocalProcessPlatform({ platform = process.platform, arch = process.arch } = {}) {
50
+ return ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64'].includes(`${platform}-${arch}`);
51
+ }
52
+ export function resolveLocalProcessLauncher({ platform = process.platform, arch = process.arch } = {}) {
53
+ const target = `${platform}-${arch}`;
54
+ check(supportsLocalProcessPlatform({ platform, arch }), 'unsupported_local_process_platform');
55
+ const manifest = JSON.parse(readFileSync(join(directory, 'local-process-manifest.json'), 'utf8'));
56
+ check(manifest.schemaVersion === 'agentkit.local-process-binary/v1'
57
+ && manifest.sourceSha256 === hash(readFileSync(join(directory, 'local-process-launcher.c'))), 'local_process_source_digest_mismatch');
58
+ const entry = manifest.builds?.[target], relative = `local-process-bin/${target}/launcher`;
59
+ check(entry?.file === relative, 'local_process_launcher_missing');
60
+ const filename = join(directory, relative), stat = lstatSync(filename);
61
+ check(stat.isFile() && (stat.mode & 0o111) !== 0, 'local_process_launcher_not_executable');
62
+ const bytes = readFileSync(filename, { flag: constants.O_RDONLY | constants.O_NOFOLLOW });
63
+ check(bytes.length === entry.bytes && hash(bytes) === entry.sha256, 'local_process_launcher_digest_mismatch');
64
+ return filename;
65
+ }
66
+ function controlReport(text, settings) {
67
+ try {
68
+ const report = JSON.parse(text);
69
+ check(report.schemaVersion === LOCAL_PROCESS_CONTRACT && ['applied', 'partial', 'failed'].includes(report.status)
70
+ && report.applied && typeof report.applied === 'object' && !Array.isArray(report.applied)
71
+ && Array.isArray(report.unsupported) && Array.isArray(report.failed), 'invalid_local_process_control_report');
72
+ check(Object.entries(report.applied).every(([key, value]) => limitNames.includes(key)
73
+ && Number.isSafeInteger(value) && value >= 0 && value <= settings[key]), 'invalid_local_process_control_report');
74
+ check(report.unsupported.every(key => limitNames.includes(key)) && report.failed.every(key => [...limitNames, 'owner_watchdog'].includes(key)),
75
+ 'invalid_local_process_control_report');
76
+ if (report.status !== 'failed') {
77
+ const allowedUnsupported = process.platform === 'darwin' ? ['addressSpaceBytes', 'maxProcesses'] : [];
78
+ check(Number.isSafeInteger(report.watcherPid) && report.watcherPid > 0 && !report.failed.length
79
+ && report.unsupported.length === allowedUnsupported.length && allowedUnsupported.every(key => report.unsupported.includes(key))
80
+ && limitNames.every(key => Object.hasOwn(report.applied, key) !== report.unsupported.includes(key))
81
+ && report.status === (allowedUnsupported.length ? 'partial' : 'applied'), 'invalid_local_process_control_report');
82
+ }
83
+ const { watcherPid, ...publicReport } = report; return publicReport;
84
+ } catch { return { schemaVersion: LOCAL_PROCESS_CONTRACT, status: 'failed', applied: {}, unsupported: [], failed: ['control_report'] }; }
85
+ }
86
+
87
+ /** argv/env are already Host-authorized. Shell/AST/workspace policy belongs to
88
+ * the caller; no retries and no durable handle ownership is inferred here.
89
+ * outputPolicy is opt-in (persisted-stream/v1); when absent, legacy
90
+ * maxOutputBytes-then-kill semantics and result shape are preserved exactly. */
91
+ export async function runBoundedProcess({ argv, cwd, env, settings: input = {}, timeoutMs = null, signal, onOutput, onSpawn, outputPolicy = null } = {}) {
92
+ const settings = localControlSettings(input); signal?.throwIfAborted();
93
+ check(Array.isArray(argv) && argv.length > 0 && argv.every(value => typeof value === 'string' && !value.includes('\0'))
94
+ && argv[0].length > 0 && typeof cwd === 'string' && cwd.length > 0, 'invalid_local_process_command');
95
+ check(env && typeof env === 'object' && !Array.isArray(env) && Object.entries(env).every(([name, value]) =>
96
+ namePattern.test(name) && typeof value === 'string' && !value.includes('\0')), 'invalid_local_process_environment');
97
+ check(timeoutMs === null || Number.isFinite(timeoutMs) && timeoutMs > 0, 'invalid_local_process_timeout');
98
+ const outPolicy = resolveOutputPolicy(settings, localOutputPolicy(outputPolicy));
99
+ const executable = resolveLocalProcessLauncher();
100
+ const started = performance.now(), duration = Math.min(timeoutMs ?? Infinity, settings.wallSeconds * 1000);
101
+ const child = spawn(executable, [...limitNames.map(key => String(settings[key])), '3', '4', '--', ...argv], {
102
+ cwd, env, detached: true, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
103
+ });
104
+ const output = { stdout: [], stderr: [] }, lengths = { stdout: 0, stderr: 0 };
105
+ const truncated = { stdout: false, stderr: false }, decoders = { stdout: new StringDecoder('utf8'), stderr: new StringDecoder('utf8') };
106
+ const report = []; let reportBytes = 0, timedOut = false, canceled = false, outputLimitExceeded = false;
107
+ const cleanupErrors = new Set(); let exitCode = null, exitSignal = null, exited = false, closed = false, spawnError = null, timer;
108
+ let stop; const stopping = new Promise(resolve => { stop = resolve; });
109
+ let exitDone; const exit = new Promise(resolve => { exitDone = resolve; });
110
+ let closeDone; const close = new Promise(resolve => { closeDone = resolve; });
111
+ child.once('error', error => { spawnError = error; exitDone(); stop(); });
112
+ child.once('exit', (code, signal) => { exited = true; exitCode = code; exitSignal = signal; exitDone(); stop(); });
113
+ child.once('close', () => { closed = true; closeDone(); });
114
+ child.once('spawn', () => { try { onSpawn?.({ pid: child.pid }); } catch { cleanupErrors.add('spawn_observer_failed'); stop(); } });
115
+ // persisted-stream/v1: the full capped UTF-8 output is streamed out via onOutput
116
+ // to the existing supervisor-owned files; result.stdout/stderr hold ONLY a bounded
117
+ // preview. No full 64MiB buffer is held in memory here. The supervisor fsyncs the
118
+ // streamed files before its terminal receipt and never overwrites them with the
119
+ // preview. Only the hard persisted cap, timeout, cancellation, or real resource
120
+ // limits stop the process; preview overflow never kills a successful process.
121
+ const persistedStream = outPolicy.mode === 'persistedStream';
122
+ const preview = persistedStream ? { stdout: [], stderr: [], lengths: { stdout: 0, stderr: 0 },
123
+ truncated: { stdout: false, stderr: false } } : null;
124
+ const stream = persistedStream ? { lengths: { stdout: 0, stderr: 0 }, emitted: { stdout: 0, stderr: 0 }, truncated: { stdout: false, stderr: false },
125
+ decoders: { stdout: new StringDecoder('utf8'), stderr: new StringDecoder('utf8') } } : null;
126
+ const utf8Prefix = (text, limit) => new StringDecoder('utf8').write(Buffer.from(text).subarray(0, limit));
127
+ const emitPersisted = (channel, text) => {
128
+ // Invalid source bytes may expand into replacement characters. Bound the
129
+ // encoded observation too, so normalization cannot exceed the persisted cap.
130
+ const remaining = outPolicy.persistedBytes - stream.emitted[channel];
131
+ const bounded = utf8Prefix(text, remaining);
132
+ stream.emitted[channel] += Buffer.byteLength(bounded);
133
+ if (bounded) { try { onOutput?.(channel, bounded); } catch { /* supervisor records its own write failure */ } }
134
+ if (bounded !== text) { stream.truncated[channel] = true; outputLimitExceeded = true; stop(); }
135
+ };
136
+ for (const channel of ['stdout', 'stderr']) {
137
+ child[channel].on('error', () => { cleanupErrors.add(`${channel}_read_error`); stop(); });
138
+ child[channel].on('data', chunk => {
139
+ if (!persistedStream) {
140
+ const remaining = settings.maxOutputBytes - lengths[channel], kept = chunk.subarray(0, Math.max(0, remaining));
141
+ if (kept.length) {
142
+ output[channel].push(kept); lengths[channel] += kept.length;
143
+ const text = decoders[channel].write(kept);
144
+ if (text) { try { onOutput?.(channel, text); } catch { /* observation never changes execution */ } }
145
+ }
146
+ if (chunk.length > remaining) { truncated[channel] = true; outputLimitExceeded = true; stop(); }
147
+ return;
148
+ }
149
+ // Hard persisted cap: stream all bytes up to this bound via onOutput (to the
150
+ // owned files). Beyond it the process is stopped — this is the real boundary.
151
+ const pRemaining = outPolicy.persistedBytes - stream.lengths[channel];
152
+ const pKept = chunk.subarray(0, Math.max(0, pRemaining));
153
+ const overcap = chunk.length > pRemaining;
154
+ if (pKept.length) {
155
+ stream.lengths[channel] += pKept.length;
156
+ // Stream complete characters only; the StringDecoder holds a trailing
157
+ // partial multibyte sequence rather than emitting it. We never flush that
158
+ // held tail as replacement bytes (end()), so the file holds at most
159
+ // persistedBytes of valid UTF-8 — a cap-cut multibyte sequence is dropped,
160
+ // not written as a 3-byte replacement char beyond the cap.
161
+ const text = stream.decoders[channel].write(pKept);
162
+ if (text) emitPersisted(channel, text);
163
+ }
164
+ // Bounded in-memory preview: keep only up to previewBytes for the receipt,
165
+ // captured from pKept BEFORE the hard-cap return so an overcap first chunk
166
+ // still yields a non-empty preview. Preview overflow is honest (truncated
167
+ // flag) but never stops the process.
168
+ const vRemaining = outPolicy.previewBytes - preview.lengths[channel];
169
+ if (vRemaining > 0) {
170
+ const vKept = pKept.subarray(0, Math.min(pKept.length, vRemaining));
171
+ if (vKept.length) { preview[channel].push(vKept); preview.lengths[channel] += vKept.length; }
172
+ if (pKept.length > vRemaining) preview.truncated[channel] = true;
173
+ } else if (chunk.length) preview.truncated[channel] = true;
174
+ if (overcap) { stream.truncated[channel] = true; outputLimitExceeded = true; stop(); }
175
+ });
176
+ }
177
+ child.stdio[3].on('error', () => { cleanupErrors.add('control_report_read_error'); stop(); });
178
+ child.stdio[3].on('data', chunk => {
179
+ reportBytes += chunk.length;
180
+ if (reportBytes <= 4096) report.push(chunk); else { cleanupErrors.add('control_report_oversize'); stop(); }
181
+ });
182
+ child.stdio[4].on('error', () => { cleanupErrors.add('owner_lease_error'); stop(); });
183
+ const abort = () => { canceled = true; stop(); };
184
+ signal?.addEventListener('abort', abort, { once: true }); if (signal?.aborted) abort();
185
+ const deadline = () => {
186
+ const remaining = duration - (performance.now() - started);
187
+ if (remaining <= 0) { timedOut = true; stop(); }
188
+ else timer = setTimeout(deadline, Math.min(2_147_483_647, Math.max(1, remaining)));
189
+ };
190
+ deadline();
191
+ const groupSignal = signal => {
192
+ if (!child.pid) return;
193
+ try { process.kill(-child.pid, signal); }
194
+ catch (error) { if (error.code !== 'ESRCH') cleanupErrors.add(`group_${signal}:${error.code ?? 'error'}`); }
195
+ };
196
+ try {
197
+ await stopping; clearTimeout(timer); signal?.removeEventListener('abort', abort);
198
+ if (child.pid) {
199
+ groupSignal('SIGTERM'); await wait(200); groupSignal('SIGKILL');
200
+ if (!exited) await boundedWait(exit, 1000);
201
+ if (!exited && !spawnError) { cleanupErrors.add('process_reap_timeout'); try { child.kill('SIGKILL'); } catch { /* recorded below */ } }
202
+ }
203
+ // The only owner writer closes after the last group signal. The watchdog
204
+ // also handles hard caller death, when no finally block can execute.
205
+ child.stdio[4].destroy();
206
+ if (!closed) await boundedWait(close, 200);
207
+ if (!closed && !spawnError) cleanupErrors.add('process_stdio_close_timeout');
208
+ } finally {
209
+ clearTimeout(timer); signal?.removeEventListener('abort', abort);
210
+ for (const stream of child.stdio) stream?.destroy();
211
+ if (!exited) child.unref();
212
+ }
213
+ if (!persistedStream) {
214
+ for (const channel of ['stdout', 'stderr']) {
215
+ const tail = decoders[channel].end();
216
+ if (tail) { try { onOutput?.(channel, tail); } catch { /* observation never changes execution */ } }
217
+ }
218
+ }
219
+ if (persistedStream) for (const channel of ['stdout', 'stderr']) {
220
+ // Do not synthesize a replacement for a sequence cut by the hard cap.
221
+ // Natural invalid tails are normalized within the same encoded-byte bound.
222
+ if (!stream.truncated[channel]) emitPersisted(channel, stream.decoders[channel].end());
223
+ }
224
+ if (spawnError) throw new LocalProcessError(`local_process_spawn_failed:${spawnError.code ?? 'error'}`);
225
+ const controls = controlReport(Buffer.concat(report).toString('utf8'), settings);
226
+ const code = exitCode ?? (exitSignal && osConstants.signals[exitSignal] ? -osConstants.signals[exitSignal] : null);
227
+ if (!persistedStream)
228
+ return { stdout: Buffer.concat(output.stdout).toString('utf8'), stderr: Buffer.concat(output.stderr).toString('utf8'),
229
+ exitCode: outputLimitExceeded ? 122 : code, timedOut, canceled, outputLimitExceeded,
230
+ stdoutTruncated: truncated.stdout, stderrTruncated: truncated.stderr, cleanupError: [...cleanupErrors].join(',') || null, controls };
231
+ // persisted-stream/v1: stdout/stderr hold ONLY the bounded preview. The full
232
+ // capped output lives in the supervisor-owned files (streamed via onOutput and
233
+ // located by task_id/cursors). Honest truncation flags distinguish preview vs
234
+ // hard persisted bounds; outputPersistedTruncated marks the real boundary.
235
+ const previewText = channel => {
236
+ const decoded = Buffer.concat(preview[channel]).toString('utf8'), bounded = utf8Prefix(decoded, outPolicy.previewBytes);
237
+ if (bounded !== decoded) preview.truncated[channel] = true;
238
+ return bounded;
239
+ };
240
+ return { stdout: previewText('stdout'), stderr: previewText('stderr'),
241
+ exitCode: outputLimitExceeded ? 122 : code, timedOut, canceled, outputLimitExceeded,
242
+ stdoutTruncated: preview.truncated.stdout, stderrTruncated: preview.truncated.stderr,
243
+ stdoutPersistedTruncated: stream.truncated.stdout, stderrPersistedTruncated: stream.truncated.stderr,
244
+ previewBytes: outPolicy.previewBytes, persistedBytes: outPolicy.persistedBytes,
245
+ cleanupError: [...cleanupErrors].join(',') || null, controls };
246
+ }
@@ -0,0 +1,32 @@
1
+ import { localControlSettings } from './local-process.mjs';
2
+
3
+ export const LOCAL_RESOURCE_PROFILE = 'agentkit.local-resource-profile/v1';
4
+ const profiles = Object.freeze({
5
+ 'bounded/v1': Object.freeze({}),
6
+ // Repository packs and build artifacts routinely exceed the restricted
7
+ // per-file allowance. Keep a finite development allowance; explicit Host
8
+ // settings always win. Other limits retain their existing values.
9
+ 'local-development/v1': Object.freeze({ maxFileBytes: 1024 ** 3 }),
10
+ });
11
+ export function validLocalResourceProfile(profile) {
12
+ return typeof profile === 'string' && Object.hasOwn(profiles, profile);
13
+ }
14
+
15
+ /** Host admission selects this policy, never a model/tool argument. */
16
+ export function localResourceSettings({ profile = 'bounded/v1', settings = {} } = {}) {
17
+ if (!validLocalResourceProfile(profile)) throw Error('invalid_local_resource_profile');
18
+ // Validate raw settings before merging: unknown keys/invalid overrides must
19
+ // not be hidden by profile defaults.
20
+ localControlSettings(settings);
21
+ return localControlSettings({ ...profiles[profile], ...settings });
22
+ }
23
+
24
+ /** These are requested limits. Per-command receipts remain authoritative for
25
+ * OS hard-limit clamping, unsupported controls and actual process outcomes. */
26
+ export function localResourceFacts(profile, settings) {
27
+ if (!validLocalResourceProfile(profile)) throw Error('invalid_local_resource_profile');
28
+ const selected = localControlSettings(settings);
29
+ return { contract: LOCAL_RESOURCE_PROFILE, profile, requestedLimits: Object.fromEntries(
30
+ ['cpuSeconds', 'addressSpaceBytes', 'maxProcesses', 'maxOpenFiles', 'maxFileBytes', 'wallSeconds']
31
+ .map(key => [key, selected[key]])) };
32
+ }
@@ -0,0 +1,121 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical, invariant } from '@kingsoftcloud/agentkit-harness-core';
3
+
4
+ // KsADK memory/extraction.py native_user_memory_candidate, reference 355059d.
5
+ // Only whole original human statements enter this path. Transcript extraction
6
+ // and model/tool assertions intentionally have no candidate authority here.
7
+ export const NATIVE_USER_EXTRACTION_POLICY = 'native-original-user-profile/v2';
8
+ export const NATIVE_MEMORY_CANDIDATE = 'agentkit.native-memory-candidate/v1';
9
+ export const memoryDigest = value => {
10
+ // UTF-8 replacement of a lone UTF-16 surrogate would alias a different
11
+ // original input. Python's strict UTF-8 encoder rejects it as well.
12
+ invariant(typeof value === 'string' && value.isWellFormed(), 'native_memory_text_invalid_unicode');
13
+ return 'sha256:' + createHash('sha256').update(value).digest('hex');
14
+ };
15
+ const identify = value => memoryDigest(canonical(value)).slice(7);
16
+ const explicitPatterns = [/记住[::]?\s*(.+)/iu, /别忘了[::]?\s*(.+)/iu,
17
+ /remember\s+(?:that\s+)?(.+)/iu, /请记[::]?\s*(.+)/iu];
18
+ const sessionRetention = /\b(?:for|during|within|in)\s+(?:only\s+)?(?:this|the\s+current)\s+(?:conversation|chat|session|turn)\b|(?:本次|这次|此次|当前|这个|本)(?:会话|对话|聊天)|(?:本轮|这轮)/iu;
19
+ const correction = /(?<prefix>(?:我|本人)?喜欢(?<action>吃|喝|用|看|听|玩))(?:的)?(?:是)?\s*(?<new>.+?)\s*(?:,|,)?\s*(?:而)?不是\s*(?<old>.+?)(?:[。.!!]|$)/iu;
20
+ const generalCorrection = /(?:以后|今后)?(?<subject>[一-龥A-Za-z0-9]{1,12})?(?:请)?(?:不要|别)(?<old>.{1,20}?)[,,]?(?:请)?(?:改成|改为|换成|改用)(?<new>.+?)(?:[。.!!]|$)/iu;
21
+ const hobby = /(?:我|本人)?的?爱好(?<correction>其实|现在|改)?(?:是|改成|变成)\s*(?<value>.+?)(?:[。.!!]|$)/iu;
22
+ const implicitPatterns = [/(?:我|本人)?的?偏好(?:是|为)\s*(.+?)(?:[。.!!]|$)/iu,
23
+ /(?:我|本人)?(?:平时)?(?:喜欢|习惯)(吃|喝|用|看|听|玩)\s*(.+?)(?:[。.!!]|$)/iu];
24
+ const full = (pattern, text) => { const match = pattern.exec(text); return match?.index === 0 && match[0].length === text.length ? match : null; };
25
+ const edgePunctuation = value => value.trim().replace(/^[。.,,]+|[。.,,]+$/gu, '');
26
+ const correctedContent = value => value.trim().replace(/^[。.,, ]+|[。.,, ]+$/gu, '');
27
+ const sessionOnly = text => sessionRetention.test(text.trim().split(/[。!?!?\r\n]|\.(?=\s|$)/u, 1)[0]);
28
+
29
+ export function deriveMemoryProfileSlotKey(content) {
30
+ if (hobby.test(content)) return 'profile.preference.hobby';
31
+ const action = /(?:我|本人)?喜欢(?<action>吃|喝|用|看|听|玩)/iu.exec(content)?.groups.action;
32
+ return action ? 'profile.preference.' + ({ 吃: 'food', 喝: 'drink', 用: 'tool', 看: 'viewing', 听: 'listening', 玩: 'activity' })[action] : '';
33
+ }
34
+
35
+ export function isExplicitMemoryInstruction(value) {
36
+ const text = value.trim();
37
+ if (sessionOnly(text)) return false;
38
+ return explicitPatterns.some(pattern => full(pattern, text)) || !!full(correction, text)
39
+ || !!full(generalCorrection, text) || !!full(hobby, text)?.groups.correction;
40
+ }
41
+
42
+ /** Stable candidate identity is versioned; it need not reproduce Python JSON bytes. */
43
+ export function nativeUserMemoryCandidate({ text, sourceEventId, scope, scopeId }) {
44
+ invariant(typeof text === 'string' && text.isWellFormed() && typeof sourceEventId === 'string' && sourceEventId.isWellFormed()
45
+ && ['user', 'agent', 'workspace', 'org'].includes(scope) && typeof scopeId === 'string', 'native_memory_candidate_invalid');
46
+ text = text.trim();
47
+ const explicit = isExplicitMemoryInstruction(text), hobbyMatch = full(hobby, text);
48
+ if (!sourceEventId.trim() || !(explicit || implicitPatterns.some(pattern => full(pattern, text)) || hobbyMatch)) return null;
49
+ let content, operation = 'add', reason, importance;
50
+ const changedHobby = hobby.exec(text), general = generalCorrection.exec(text), preference = correction.exec(text);
51
+ // Preserve the reference's implicit hobby fallback after its legacy extractor
52
+ // declined a session-scoped statement. This is still only an observation.
53
+ if (sessionOnly(text)) {
54
+ if (!hobbyMatch || hobbyMatch.groups.correction) return null;
55
+ content = '我的爱好是' + hobbyMatch.groups.value.trim(); reason = 'implicit_user_preference'; importance = .65;
56
+ } else if (changedHobby?.groups.correction) {
57
+ const value = correctedContent(changedHobby.groups.value);
58
+ if (!value) return null;
59
+ content = '我的爱好是' + value; operation = 'update'; reason = 'explicit_user_correction'; importance = .9;
60
+ } else if (general) {
61
+ const subject = (general.groups.subject ?? '').trim(), value = correctedContent(general.groups.new);
62
+ if (!value) return null;
63
+ content = subject + '改为' + value; operation = 'update'; reason = 'explicit_user_correction'; importance = .8;
64
+ } else if (preference) {
65
+ const value = correctedContent(preference.groups.new);
66
+ if (!value) return null;
67
+ content = preference.groups.prefix.trim() + value; operation = 'update'; reason = 'explicit_user_correction'; importance = .9;
68
+ } else {
69
+ const instruction = explicitPatterns.map(pattern => pattern.exec(text)).find(Boolean);
70
+ if (instruction) {
71
+ content = edgePunctuation(instruction[1] || text);
72
+ if (!content) return null;
73
+ reason = 'explicit_user_request'; importance = .8;
74
+ } else {
75
+ const implicit = implicitPatterns.map(pattern => pattern.exec(text)).find(Boolean);
76
+ if (implicit) content = edgePunctuation(implicit[0]);
77
+ else if (hobbyMatch && !hobbyMatch.groups.correction) content = '我的爱好是' + hobbyMatch.groups.value.trim();
78
+ else return null;
79
+ reason = 'implicit_user_preference'; importance = .65;
80
+ }
81
+ }
82
+ if (!explicit && reason !== 'implicit_user_preference') return null;
83
+ return { contract: NATIVE_MEMORY_CANDIDATE, extractionPolicy: NATIVE_USER_EXTRACTION_POLICY,
84
+ candidateId: 'candidate_' + identify([NATIVE_MEMORY_CANDIDATE, NATIVE_USER_EXTRACTION_POLICY, scope, scopeId, sourceEventId, operation, content]),
85
+ operation, memoryType: 'profile', scope, scopeId, content, confidence: 1, importance,
86
+ sourceEventIds: [sourceEventId], conflictsWith: [], sensitiveLabels: [], reason,
87
+ slotKey: deriveMemoryProfileSlotKey(content), sensitivity: 'none', expiresAt: '' };
88
+ }
89
+
90
+ export function nativeMemoryScopeBindings(principal, scopes = ['user']) {
91
+ invariant(principal && ['tenantId', 'workspaceId', 'agentId', 'userId'].every(key => typeof principal[key] === 'string'
92
+ && principal[key].trim()), 'native_memory_principal_invalid');
93
+ const { tenantId, workspaceId, agentId, userId } = principal;
94
+ const bindings = { tenant: ['org', [tenantId]], workspace: ['workspace', [tenantId, workspaceId]],
95
+ agent: ['agent', [tenantId, workspaceId, agentId]], user: ['user', [tenantId, workspaceId, agentId, userId]] };
96
+ invariant(Array.isArray(scopes) && scopes.every(scope => Object.hasOwn(bindings, scope)), 'native_memory_scope_invalid');
97
+ return [...new Set(scopes)].map(name => ({ scope: bindings[name][0],
98
+ scopeId: 'native-memory-v1:' + identify([name, ...bindings[name][1]]) }));
99
+ }
100
+
101
+ const secretPatterns = [[/api[_-]?key\s*[:=]\s*\S+/iu, 'api_key'], [/secret[_-]?key\s*[:=]\s*\S+/iu, 'secret_key'],
102
+ [/access[_-]?key\s*[:=]\s*\S+|AKIA[0-9A-Z]{16}/iu, 'access_key'], [/cookie\s*[:=]\s*\S+/iu, 'cookie'],
103
+ [/authorization\s*[:=]\s*bearer\s+\S+/iu, 'auth_header'], [/https?:\/\/\S+?(?:X-Amz-Signature|X-Amz-Security-Token|signed)=/iu, 'signed_url'],
104
+ [/(postgres|mysql|mongodb|redis):\/\/\S+:\S+@\S+/iu, 'dsn'], [/sk-[A-Za-z0-9]{20,}/iu, 'token']];
105
+ export function nativeMemorySensitiveLabels(content, supplied = []) {
106
+ return [...new Set([...supplied.filter(label => label && label !== 'none'),
107
+ ...secretPatterns.filter(([pattern]) => pattern.test(content)).map(([, label]) => label)])].sort();
108
+ }
109
+
110
+ /** Candidate decisions grant no execution authority; provider commits separately. */
111
+ export function evaluateNativeMemoryCandidate(candidate, { existing = null, observations = 1 } = {}) {
112
+ const labels = nativeMemorySensitiveLabels(candidate.content, candidate.sensitiveLabels);
113
+ if (labels.length || candidate.sensitivity === 'high') return { decision: 'reject', operation: 'ignore', reason: 'sensitive_label_rejected:' + (labels.join(',') || 'explicit') };
114
+ if (/model_guess|transient|current_plan/iu.test(candidate.reason)) return { decision: 'reject', operation: 'ignore', reason: 'not_durable' };
115
+ const explicit = /explicit|user_request/iu.test(candidate.reason);
116
+ const threshold = explicit ? .6 : observations < 2 ? Infinity : .85;
117
+ if (candidate.confidence < threshold) return { decision: 'pending', operation: candidate.operation,
118
+ reason: `below_threshold:${candidate.confidence.toFixed(2)}<${Number.isFinite(threshold) ? threshold.toFixed(2) : 'inf'}` };
119
+ return { decision: 'commit', operation: existing ? 'update' : candidate.operation,
120
+ reason: existing ? 'conflict_supersede' : 'threshold_met' };
121
+ }