arcane-os 0.65.0 → 0.67.0

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 (40) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/ai/browser-decisions.mjs +3 -2
  4. package/browser-runtime/ai/core-image.mjs +84 -34
  5. package/browser-runtime/ai/core-local.mjs +195 -43
  6. package/browser-runtime/ai/core-onnx.mjs +71 -12
  7. package/browser-runtime/ai/decision-runtime.mjs +14 -24
  8. package/docs/publishing.md +39 -0
  9. package/docs/reference/README.md +8 -1
  10. package/docs/reference/ai/browser-decisions.md +7 -4
  11. package/docs/reference/core-native-packaging.md +11 -4
  12. package/docs/reference/core-repositories.md +214 -11
  13. package/docs/reference/core-runtime.md +32 -5
  14. package/docs/reference/git-text-snapshot.md +15 -1
  15. package/docs/reference/inventory/package-api.json +41 -13
  16. package/docs/reference/inventory/runtime-modules.json +5 -4
  17. package/docs/reference/local-ai.md +119 -2
  18. package/docs/reference/local-image-generation.md +15 -4
  19. package/docs/reference/local-whisper.md +7 -1
  20. package/docs/reference/mcp-stdio.md +1 -1
  21. package/docs/reference/model-assets.md +50 -3
  22. package/docs/reference/native-decisions.md +181 -0
  23. package/docs/reference/native-speech.md +6 -0
  24. package/docs/reference/sdk-api.md +151 -16
  25. package/package.json +5 -2
  26. package/src/core/repositories.mjs +37 -14
  27. package/src/core/runtime.mjs +11 -0
  28. package/src/core/services/decisions.mjs +144 -0
  29. package/src/core/services/local-ai.mjs +31 -9
  30. package/src/core/services/model-assets.mjs +98 -13
  31. package/src/git-identity.mjs +96 -0
  32. package/src/git-text-snapshot.mjs +5 -3
  33. package/src/local-ai/decision-tokenizer-worker.mjs +31 -0
  34. package/src/local-ai/decision-tokenizer.mjs +109 -0
  35. package/src/local-ai/decisions.mjs +273 -0
  36. package/src/local-ai/dev.mjs +8 -2
  37. package/src/local-ai/onnx-worker.mjs +67 -7
  38. package/src/local-ai/onnx.mjs +23 -12
  39. package/src/native/portable-layout.mjs +8 -3
  40. package/src/repository.mjs +144 -4
@@ -1,10 +1,142 @@
1
1
  import {runProcess} from './process.mjs';
2
2
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
3
+ import path from 'node:path';
4
+ import Is from 'strong-type';
5
+
6
+ const is = new Is(false);
3
7
 
4
8
  async function git(args,{run=runProcess,...options}){
5
9
  return run('git',args,options);
6
10
  }
7
11
 
12
+ /** Private shared reader for Git text protocols, retaining native diagnostics. */
13
+ export async function readGitText(args, {
14
+ cwd, signal, onEvent, run = runProcess, allowNonzero = false,
15
+ notTextCode = 'ARCANE_GIT_CONFIGURATION_NOT_TEXT', label = 'Git configuration'
16
+ } = {}) {
17
+ const chunks = [];
18
+ try {
19
+ const result = await run('git', args, {
20
+ cwd, signal, onEvent, allowNonzero,
21
+ outputEncoding: {stdout: null}, captureOutput: {stdout: false}, emitOutputEvents: {stdout: false},
22
+ onOutput: function observeOutput({stream, chunk}) {
23
+ if (stream === 'stdout') chunks.push(chunk);
24
+ }
25
+ });
26
+ try { throwIfAborted(signal); }
27
+ catch (error) { error.details = result; throw error; }
28
+ const rawStdout = Buffer.concat(chunks);
29
+ let output = null;
30
+ if (result.code === 0) {
31
+ try { output = new TextDecoder('utf-8', {fatal: true, ignoreBOM: true}).decode(rawStdout); }
32
+ catch (cause) {
33
+ throw new ArcaneError(notTextCode, `${label} cannot be represented as UTF-8 text.`, {cause, details: result});
34
+ }
35
+ }
36
+ return {result, output, rawStdout};
37
+ } catch (error) {
38
+ const rawStdout = Buffer.concat(chunks);
39
+ let attached = false;
40
+ let attachmentError;
41
+ try {
42
+ if (error !== null && (is.object(error) || is.function(error)) && !('rawStdout' in error)) {
43
+ Object.defineProperty(error, 'rawStdout', {
44
+ value: rawStdout, writable: true, enumerable: true, configurable: true
45
+ });
46
+ attached = true;
47
+ }
48
+ } catch (cause) { attachmentError = cause; }
49
+ if (attached) throw error;
50
+ const failure = new ArcaneError(error?.code ?? ERROR_CODES.operationFailed,
51
+ error instanceof Error ? error.message : String(error),
52
+ {cause: error, details: error?.details, exitCode: error?.exitCode});
53
+ failure.rawStdout = rawStdout;
54
+ if (attachmentError) failure.attachmentError = attachmentError;
55
+ throw failure;
56
+ }
57
+ }
58
+
59
+ /** Capture a caller-selected operation target without changing its strings. */
60
+ export function captureRepositoryTarget(target) {
61
+ if (target === undefined) return undefined;
62
+ const {remote, ref} = target;
63
+ for (const [name, value] of Object.entries({remote, ref})) {
64
+ if (!is.string(value) || value === '' || !value.isWellFormed() || value.includes('\0')) {
65
+ throw new TypeError(`target.${name} must be nonempty text representable as a native process argument.`);
66
+ }
67
+ }
68
+ return {remote, ref};
69
+ }
70
+
71
+ export function repositoryPushArguments(target) {
72
+ return target === undefined ? ['push'] : ['push', '--no-follow-tags', '--', target.remote, `HEAD:${target.ref}`];
73
+ }
74
+
75
+ /** Observe an existing workspace without initializing or refreshing it. */
76
+ export async function repositoryConfiguration({workspaceRoot = process.cwd(), signal, onEvent, run = runProcess} = {}) {
77
+ const repositoryRoot = path.resolve(workspaceRoot);
78
+ const options = {cwd: repositoryRoot, signal, onEvent, run, allowNonzero: true};
79
+ throwIfAborted(signal);
80
+
81
+ function processFailure(observation, message) {
82
+ const failure = new ArcaneError(ERROR_CODES.operationFailed, message, {details: observation.result});
83
+ failure.rawStdout = observation.rawStdout;
84
+ return failure;
85
+ }
86
+
87
+ async function configuration(pattern) {
88
+ const observation = await readGitText(['config', '--includes', '--null', '--get-regexp', pattern], options);
89
+ const values = new Map();
90
+ if (observation.result.code === 1) return values;
91
+ if (observation.result.code !== 0) throw processFailure(observation, 'Git configuration observation failed.');
92
+ for (const record of observation.output.split('\0')) {
93
+ if (record === '') continue;
94
+ const separator = record.indexOf('\n');
95
+ const key = separator === -1 ? record : record.slice(0, separator);
96
+ const value = separator === -1 ? '' : record.slice(separator + 1);
97
+ const entries = values.get(key) ?? [];
98
+ entries.push(value);
99
+ values.set(key, entries);
100
+ }
101
+ return values;
102
+ }
103
+
104
+ function literalPattern(value) {
105
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
106
+ }
107
+
108
+ const root = await readGitText(['rev-parse', '--is-inside-work-tree', '--show-prefix'], options);
109
+ if (root.result.code !== 0) throw processFailure(root, 'Git working repository observation failed.');
110
+ if (root.output !== 'true\n\n' && root.output !== 'true\r\n\r\n') {
111
+ const failure = new ArcaneError('ARCANE_REPOSITORY_DIRECTORY_INVALID',
112
+ 'The selected directory is not a Git working repository root.', {details: root.result});
113
+ failure.rawStdout = root.rawStdout;
114
+ throw failure;
115
+ }
116
+ const head = await readGitText(['symbolic-ref', '--quiet', 'HEAD'], options);
117
+ if (head.result.code !== 0 && head.result.code !== 1) throw processFailure(head, 'Git symbolic HEAD observation failed.');
118
+ const headRef = head.result.code === 1 ? null : head.output.replace(/\r?\n$/u, '');
119
+ let upstream = null;
120
+ let remoteName;
121
+ if (headRef?.startsWith('refs/heads/')) {
122
+ const key = `branch.${headRef.slice('refs/heads/'.length)}`;
123
+ const branch = await configuration(`^${literalPattern(key)}[.](remote|merge)$`);
124
+ const remoteNames = branch.get(`${key}.remote`) ?? [];
125
+ remoteName = remoteNames.at(-1);
126
+ upstream = {remoteNames, mergeRefs: branch.get(`${key}.merge`) ?? [], urls: [], pushUrls: []};
127
+ }
128
+ const selectedNames = ['origin'];
129
+ if (remoteName !== undefined && remoteName !== '.' && remoteName !== 'origin') selectedNames.push(remoteName);
130
+ const names = selectedNames.map(literalPattern).join('|');
131
+ const remotes = await configuration(`^remote[.](${names})[.](url|pushurl)$`);
132
+ const origin = {urls: remotes.get('remote.origin.url') ?? [], pushUrls: remotes.get('remote.origin.pushurl') ?? []};
133
+ if (upstream && remoteName !== undefined && remoteName !== '.') {
134
+ upstream.urls = remotes.get(`remote.${remoteName}.url`) ?? [];
135
+ upstream.pushUrls = remotes.get(`remote.${remoteName}.pushurl`) ?? [];
136
+ }
137
+ return {repositoryRoot, headRef, origin, upstream};
138
+ }
139
+
8
140
  export async function repositoryStatus({
9
141
  workspaceRoot=process.cwd(),
10
142
  signal,
@@ -28,11 +160,13 @@ export async function repositoryStatus({
28
160
 
29
161
  export async function repositoryPull({
30
162
  workspaceRoot=process.cwd(),
163
+ target,
31
164
  signal,
32
165
  onEvent,
33
166
  run=runProcess
34
167
  }={}){
35
168
  throwIfAborted(signal);
169
+ const selectedTarget=captureRepositoryTarget(target);
36
170
  const before=await repositoryStatus({workspaceRoot,signal,onEvent,run});
37
171
  if(!before.clean){
38
172
  throw new ArcaneError(
@@ -41,22 +175,27 @@ export async function repositoryPull({
41
175
  {details:before}
42
176
  );
43
177
  }
44
- const result=await git(['pull','--ff-only'],{cwd:workspaceRoot,signal,onEvent,run});
178
+ const args=selectedTarget===undefined?['pull','--ff-only']
179
+ :['pull','--ff-only','--',selectedTarget.remote,`${selectedTarget.ref}:`];
180
+ const result=await git(args,{cwd:workspaceRoot,signal,onEvent,run});
45
181
  return {
46
182
  action:'pull',
47
183
  repositoryRoot:before.repositoryRoot,
48
184
  branch:before.branch,
49
- output:result.stdout.trim()
185
+ output:result.stdout.trim(),
186
+ ...(selectedTarget===undefined?{}:{target:selectedTarget,stdout:result.stdout,stderr:result.stderr})
50
187
  };
51
188
  }
52
189
 
53
190
  export async function repositoryPush({
54
191
  workspaceRoot=process.cwd(),
192
+ target,
55
193
  signal,
56
194
  onEvent,
57
195
  run=runProcess
58
196
  }={}){
59
197
  throwIfAborted(signal);
198
+ const selectedTarget=captureRepositoryTarget(target);
60
199
  const before=await repositoryStatus({workspaceRoot,signal,onEvent,run});
61
200
  if(!before.branch){
62
201
  throw new ArcaneError(
@@ -64,12 +203,13 @@ export async function repositoryPush({
64
203
  'Refusing to push from a detached HEAD.'
65
204
  );
66
205
  }
67
- const result=await git(['push'],{cwd:workspaceRoot,signal,onEvent,run});
206
+ const result=await git(repositoryPushArguments(selectedTarget),{cwd:workspaceRoot,signal,onEvent,run});
68
207
  return {
69
208
  action:'push',
70
209
  repositoryRoot:before.repositoryRoot,
71
210
  branch:before.branch,
72
- output:(result.stdout||result.stderr).trim()
211
+ output:(result.stdout||result.stderr).trim(),
212
+ ...(selectedTarget===undefined?{}:{target:selectedTarget,stdout:result.stdout,stderr:result.stderr})
73
213
  };
74
214
  }
75
215