@notis_ai/cli 0.2.0-beta.178.1 → 0.2.0-beta.1792022492092.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +548 -98
- package/config/portal-route-roots.json +47 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
- package/dist/base-skills/notis-apps/SKILL.md +76 -51
- package/dist/base-skills/notis-apps/references/architecture.md +116 -164
- package/dist/base-skills/notis-apps/references/design.md +26 -107
- package/dist/base-skills/notis-apps/references/memory.md +58 -0
- package/dist/base-skills/notis-apps/references/reading.md +52 -74
- package/dist/base-skills/notis-apps/references/release.md +105 -96
- package/dist/base-skills/notis-apps/references/sdk.md +115 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +145 -0
- package/dist/base-skills/notis-cli/SKILL.md +29 -12
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
- package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
- package/dist/base-skills/notis-query/SKILL.md +34 -60
- package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
- package/dist/base-skills/notis-query/references/documents.md +53 -39
- package/dist/base-skills/notis-query/references/query.md +57 -524
- package/dist/sdk/src/bundleModule.ts +79 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
- package/dist/sdk/src/components/DocumentPage.tsx +144 -0
- package/dist/sdk/src/components/RecordComponents.tsx +33 -0
- package/dist/sdk/src/components/RenderChartData.tsx +19 -0
- package/dist/sdk/src/config.ts +4 -26
- package/dist/sdk/src/documents.ts +2 -0
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
- package/dist/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -0
- package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
- package/dist/sdk/src/hooks/useQuery.ts +7 -4
- package/dist/sdk/src/hooks/useViewParams.ts +121 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/dist/sdk/src/index.ts +36 -0
- package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
- package/dist/sdk/src/nativeData.ts +39 -0
- package/dist/sdk/src/presentation.tsx +82 -0
- package/dist/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +5 -3
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +163 -13
- package/dist/sdk/src/space.ts +425 -0
- package/dist/sdk/src/styles.css +96 -72
- package/dist/sdk/src/vite.ts +11 -1
- package/dist/skill-sync/index.js +571 -80
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +858 -233
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1028 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +180 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +175 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/pool.js +76 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +201 -0
- package/package.json +16 -5
- package/skills/notis-apps/cli.md +323 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +2 -1
- package/src/command-specs/auth.js +2 -2
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +2 -0
- package/src/command-specs/index.js +11 -5
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +242 -1
- package/src/command-specs/spaces.js +645 -0
- package/src/command-specs/tools.js +0 -6
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +92 -29
- package/src/runtime/app-boundary-validator.js +7 -4
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +321 -253
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +18 -1
- package/src/runtime/profiles.js +5 -107
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/screenshot-framing.js +85 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +230 -35
- package/src/runtime/skill-sync/local-scanner.ts +188 -29
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/sync-plan.ts +16 -9
- package/src/runtime/skill-sync/types.ts +32 -6
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +66 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +50 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +105 -0
- package/src/runtime/space-source.js +281 -0
- package/src/runtime/space-test-server.js +327 -0
- package/src/runtime/space-verification.js +89 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +254 -0
- package/src/runtime/store-screenshot.js +10 -7
- package/src/runtime/sync-skills.d.ts +3 -1
- package/src/runtime/sync-skills.js +10 -2
- package/src/runtime/transport.js +8 -6
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker.js +14 -2
- package/src/space-harness-host.ts +103 -0
- package/template/.harness/index.html.tmpl +7 -10
- package/template/app/globals.css +1 -0
- package/src/command-specs/reports.js +0 -97
package/src/runtime/profiles.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import {
|
|
3
2
|
existsSync,
|
|
4
3
|
mkdirSync,
|
|
5
4
|
readFileSync,
|
|
6
5
|
renameSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
statSync,
|
|
9
6
|
writeFileSync,
|
|
10
7
|
} from 'node:fs';
|
|
11
8
|
import { homedir } from 'node:os';
|
|
12
9
|
import { dirname, join, parse, resolve } from 'node:path';
|
|
13
10
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
11
|
+
import { withConfigFileWriteLock } from './config-lock.js';
|
|
14
12
|
import { getAuthRecovery, quoteShellArgument } from './auth-recovery.js';
|
|
15
13
|
import { channelFromProfile, cliCommandForChannel, isReleaseChannel } from './channel.js';
|
|
16
14
|
|
|
@@ -34,14 +32,6 @@ const WORKTREE_RUNTIME_FILENAME = join('.context', 'notis-runtime.json');
|
|
|
34
32
|
const WORKTREE_ROUTING_FILENAME = join('.context', 'notis-routing.json');
|
|
35
33
|
const LOCAL_API_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
|
|
36
34
|
const LIVE_API_HOSTS = new Set(['api.notis.ai', 'api-beta.notis.ai']);
|
|
37
|
-
// Cross-process write lock over ~/.notis/config.json. Independent `notis`
|
|
38
|
-
// processes (a `login` in one terminal and a `tools exec` in another) all
|
|
39
|
-
// rewrite this file, so the lock directory name and these three timings are the
|
|
40
|
-
// whole coordination protocol. Guarded by the concurrency test in
|
|
41
|
-
// packages/cli/test/runtime-auth.test.js.
|
|
42
|
-
const CONFIG_WRITE_LOCK_TIMEOUT_MS = 5_000;
|
|
43
|
-
const CONFIG_WRITE_LOCK_STALE_MS = 2_000;
|
|
44
|
-
const CONFIG_WRITE_LOCK_POLL_MS = 10;
|
|
45
35
|
|
|
46
36
|
function clone(value) {
|
|
47
37
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -372,103 +362,11 @@ function writeRawConfig(configFile, config) {
|
|
|
372
362
|
renameSync(temporaryFile, configFile);
|
|
373
363
|
}
|
|
374
364
|
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// would then delete the thief's lock and admit a third writer. Both sides are
|
|
379
|
-
// on the same host, so the stored timestamp and Date.now() share a clock.
|
|
380
|
-
function readLockOwner(lockDirectory) {
|
|
381
|
-
try {
|
|
382
|
-
return JSON.parse(readFileSync(join(lockDirectory, 'owner'), 'utf-8'));
|
|
383
|
-
} catch {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function lockDirectoryMtime(lockDirectory) {
|
|
389
|
-
try {
|
|
390
|
-
return statSync(lockDirectory).mtimeMs;
|
|
391
|
-
} catch {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function publishConfigWriteLock(lockDirectory, ownerId) {
|
|
397
|
-
const candidateDirectory = `${lockDirectory}.candidate-${ownerId}`;
|
|
398
|
-
try {
|
|
399
|
-
mkdirSync(candidateDirectory);
|
|
400
|
-
writeFileSync(
|
|
401
|
-
join(candidateDirectory, 'owner'),
|
|
402
|
-
JSON.stringify({ id: ownerId, at: Date.now() }),
|
|
403
|
-
{ mode: 0o600 },
|
|
404
|
-
);
|
|
405
|
-
try {
|
|
406
|
-
renameSync(candidateDirectory, lockDirectory);
|
|
407
|
-
return true;
|
|
408
|
-
} catch (error) {
|
|
409
|
-
if (!existsSync(lockDirectory)) throw error;
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
} finally {
|
|
413
|
-
rmSync(candidateDirectory, { recursive: true, force: true });
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
365
|
+
// Independent `notis` processes and Desktop all rewrite this file. The lock
|
|
366
|
+
// protocol lives in config-lock.js, shared with computer.json and mirrored by
|
|
367
|
+
// Desktop.
|
|
417
368
|
function withConfigWriteLock(callback) {
|
|
418
|
-
|
|
419
|
-
const lockDirectory = `${configFile}.write-lock`;
|
|
420
|
-
const ownerId = `${process.pid}.${randomUUID()}`;
|
|
421
|
-
const deadline = Date.now() + CONFIG_WRITE_LOCK_TIMEOUT_MS;
|
|
422
|
-
mkdirSync(dirname(configFile), { recursive: true });
|
|
423
|
-
for (;;) {
|
|
424
|
-
if (!existsSync(lockDirectory) && publishConfigWriteLock(lockDirectory, ownerId)) {
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const owner = readLockOwner(lockDirectory);
|
|
429
|
-
const observedMtime = lockDirectoryMtime(lockDirectory);
|
|
430
|
-
const ownerIsStale =
|
|
431
|
-
owner?.id && Date.now() - Number(owner.at) > CONFIG_WRITE_LOCK_STALE_MS;
|
|
432
|
-
const ownerlessIsStale =
|
|
433
|
-
!owner
|
|
434
|
-
&& observedMtime !== null
|
|
435
|
-
&& Date.now() - observedMtime > CONFIG_WRITE_LOCK_STALE_MS;
|
|
436
|
-
if (ownerIsStale || ownerlessIsStale) {
|
|
437
|
-
try {
|
|
438
|
-
const currentOwner = readLockOwner(lockDirectory);
|
|
439
|
-
const ownerUnchanged = owner?.id
|
|
440
|
-
? currentOwner?.id === owner.id
|
|
441
|
-
: !currentOwner && lockDirectoryMtime(lockDirectory) === observedMtime;
|
|
442
|
-
if (ownerUnchanged) {
|
|
443
|
-
rmSync(lockDirectory, { recursive: true, force: true });
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
} catch {
|
|
447
|
-
// Losing the reclaim race is normal; fall through and keep waiting.
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (Date.now() >= deadline) {
|
|
451
|
-
throw new Error(`Timed out waiting to update ${configFile}`);
|
|
452
|
-
}
|
|
453
|
-
Atomics.wait(
|
|
454
|
-
new Int32Array(new SharedArrayBuffer(4)),
|
|
455
|
-
0,
|
|
456
|
-
0,
|
|
457
|
-
CONFIG_WRITE_LOCK_POLL_MS,
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
try {
|
|
461
|
-
return callback(configFile);
|
|
462
|
-
} finally {
|
|
463
|
-
try {
|
|
464
|
-
// Never release a lock that was reclaimed from us while we worked.
|
|
465
|
-
if (readLockOwner(lockDirectory)?.id === ownerId) {
|
|
466
|
-
rmSync(lockDirectory, { recursive: true, force: true });
|
|
467
|
-
}
|
|
468
|
-
} catch {
|
|
469
|
-
// The lock may already have been removed after an interrupted write.
|
|
470
|
-
}
|
|
471
|
-
}
|
|
369
|
+
return withConfigFileWriteLock(resolveConfigFile(), callback);
|
|
472
370
|
}
|
|
473
371
|
|
|
474
372
|
export function saveConfig(config) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Exact canonical files retired by the one-Shadow-runtime cutover.
|
|
2
|
+
* Modified or additional source is retained for explicit reconciliation. */
|
|
3
|
+
// Host mounting code stays in the canonical SDK and CLI host bundle, not in
|
|
4
|
+
// authored apps' public SDK mirror. It is not a public package export.
|
|
5
|
+
export const HOST_ONLY_SDK_FILES = Object.freeze(['src/presentation.tsx']);
|
|
6
|
+
|
|
7
|
+
export const RETIRED_SDK_FILES = Object.freeze({
|
|
8
|
+
"src/presentation.tsx": [
|
|
9
|
+
"d29022f92e1e96b451b62cc8a561878a1c0cf8a3b4c0419181de7266f8a1b525"
|
|
10
|
+
],
|
|
11
|
+
"src/isolatedHost.tsx": [
|
|
12
|
+
"7f59049281a9533d39c557ca75635073c2f404f1eb0a528b274323d61db776cd"
|
|
13
|
+
],
|
|
14
|
+
"src/frameProtocol.ts": [
|
|
15
|
+
"f32337ddb507718be04289a4acf50cf2485698bc716556230f35f06a6f633f19"
|
|
16
|
+
],
|
|
17
|
+
"src/frameIsolation.ts": [
|
|
18
|
+
"8b57cb38f618eefc4133ae50fbf2b37bbdef5be02e44b39758d8328fb2a59ab6"
|
|
19
|
+
]
|
|
20
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Runs only in the capture harness, never in an installed app. */
|
|
2
|
+
export function measureScreenshotContent() {
|
|
3
|
+
const root = document.getElementById('root');
|
|
4
|
+
if (!root) return null;
|
|
5
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
6
|
+
const visible = (element) => {
|
|
7
|
+
const rect = element.getBoundingClientRect();
|
|
8
|
+
const style = getComputedStyle(element);
|
|
9
|
+
return rect.width > 0 && rect.height > 0
|
|
10
|
+
&& style.display !== 'none' && style.visibility !== 'hidden';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Follow layout wrappers only while they contain the entire visible app.
|
|
14
|
+
// Never select one column, card, or control out of a multi-pane layout.
|
|
15
|
+
let node = root;
|
|
16
|
+
let content = null;
|
|
17
|
+
while (node) {
|
|
18
|
+
const hasOwnText = Array.from(node.childNodes).some(
|
|
19
|
+
(child) => child.nodeType === 3 && child.textContent.trim(),
|
|
20
|
+
);
|
|
21
|
+
const children = Array.from(node.children).filter(visible);
|
|
22
|
+
if (hasOwnText || children.length !== 1) break;
|
|
23
|
+
node = children[0];
|
|
24
|
+
const rect = node.getBoundingClientRect();
|
|
25
|
+
if (['DIV', 'MAIN', 'SECTION', 'ARTICLE', 'FORM'].includes(node.tagName)
|
|
26
|
+
&& rect.width >= 320 && rect.height >= 160
|
|
27
|
+
&& rect.width < viewportWidth * 0.9) {
|
|
28
|
+
content = node;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!content) return null;
|
|
32
|
+
content.setAttribute('data-notis-screenshot-content', '');
|
|
33
|
+
const rect = content.getBoundingClientRect();
|
|
34
|
+
return {
|
|
35
|
+
selector: '[data-notis-screenshot-content]',
|
|
36
|
+
width: rect.width,
|
|
37
|
+
height: rect.height,
|
|
38
|
+
viewport_width: viewportWidth,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const MEASURE_SCREENSHOT_CONTENT_SCRIPT = `(${measureScreenshotContent.toString()})()`;
|
|
43
|
+
|
|
44
|
+
/** Keep long pages readable, and rasterize bounded views at their final width. */
|
|
45
|
+
export function contentCapturePlan(bounds, viewport, outputWidth, outputHeight) {
|
|
46
|
+
const aspect = outputWidth / outputHeight;
|
|
47
|
+
if (bounds.width / bounds.height < aspect * 0.75) {
|
|
48
|
+
const width = Math.max(768, Math.ceil((bounds.width + 32) / 8) * 8);
|
|
49
|
+
return { focus: false, width, height: Math.round(width / aspect), scale: outputWidth / width };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
focus: true,
|
|
53
|
+
width: viewport.width,
|
|
54
|
+
height: viewport.height,
|
|
55
|
+
scale: Math.min(4, outputWidth / bounds.width),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function safeViewportHeight(rectangles, height) {
|
|
60
|
+
const crossing = rectangles.filter((rect) => rect.top < height && rect.bottom > height
|
|
61
|
+
&& rect.top >= height * 0.85);
|
|
62
|
+
return crossing.length
|
|
63
|
+
? Math.max(Math.ceil(height * 0.85), Math.floor(Math.min(...crossing.map((rect) => rect.top)) - 8))
|
|
64
|
+
: height;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// End a long-page viewport in whitespace rather than through a text line or
|
|
68
|
+
// control. Only geometry leaves the browser; no page text is returned.
|
|
69
|
+
export const SAFE_VIEWPORT_HEIGHT_SCRIPT = `(() => {
|
|
70
|
+
const root = document.getElementById('root');
|
|
71
|
+
if (!root) return innerHeight;
|
|
72
|
+
const rects = [];
|
|
73
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
74
|
+
let node;
|
|
75
|
+
while ((node = walker.nextNode())) {
|
|
76
|
+
if (!node.textContent.trim() || getComputedStyle(node.parentElement).visibility !== 'visible') continue;
|
|
77
|
+
const range = document.createRange(); range.selectNodeContents(node);
|
|
78
|
+
for (const rect of range.getClientRects()) rects.push({ top: rect.top, bottom: rect.bottom });
|
|
79
|
+
}
|
|
80
|
+
for (const control of root.querySelectorAll('button,input,select,textarea')) {
|
|
81
|
+
const rect = control.getBoundingClientRect();
|
|
82
|
+
rects.push({ top: rect.top, bottom: rect.bottom });
|
|
83
|
+
}
|
|
84
|
+
return (${safeViewportHeight.toString()})(rects, innerHeight);
|
|
85
|
+
})()`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { createSkillBundleBase64 } from './local-scanner';
|
|
2
|
-
import
|
|
1
|
+
import { captureNativeSkillFiles, createSkillBundleBase64 } from './local-scanner';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { checkedNativeReference } from './native-identity';
|
|
4
|
+
import type { AgentTargets, LocalSkill, NativeSkillReference, SyncPullResponse, SyncPushResponse, SyncSettings } from './types';
|
|
3
5
|
|
|
4
6
|
type JsonBody = Record<string, unknown> | undefined;
|
|
5
7
|
|
|
@@ -34,7 +36,7 @@ export async function fetchSyncSettings(serverUrl: string, jwt: string): Promise
|
|
|
34
36
|
|
|
35
37
|
export async function pullSkills(serverUrl: string, jwt: string): Promise<SyncPullResponse> {
|
|
36
38
|
return requestJson<SyncPullResponse>(`${serverUrl}/portal_skills/sync-pull`, jwt, {
|
|
37
|
-
body: {},
|
|
39
|
+
body: { skill_sync_protocol: 2 },
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -49,11 +51,16 @@ export async function pushChangedSkills(
|
|
|
49
51
|
skill_md: skill.skillMd,
|
|
50
52
|
source_url: skill.sourceUrl,
|
|
51
53
|
folder_hash: skill.folderHash,
|
|
52
|
-
|
|
54
|
+
...(skill.nativeReference ? { bundle_files: await captureNativeSkillFiles(skill) }
|
|
55
|
+
: { bundle_base64: await createSkillBundleBase64(skill) }),
|
|
56
|
+
...(skill.cloudId ? { cloud_id: skill.cloudId } : {}),
|
|
57
|
+
...(skill.nativeReference ? { native_reference: skill.nativeReference, display_name: skill.displayName,
|
|
58
|
+
native_scope: skill.nativeScope } : {}),
|
|
53
59
|
})));
|
|
54
60
|
|
|
55
61
|
return requestJson<SyncPushResponse>(`${serverUrl}/portal_skills/sync-push`, jwt, {
|
|
56
62
|
body: {
|
|
63
|
+
skill_sync_protocol: 2,
|
|
57
64
|
skills: payloadSkills,
|
|
58
65
|
},
|
|
59
66
|
});
|
|
@@ -75,7 +82,22 @@ export async function updateAgentTargets(
|
|
|
75
82
|
skillId: string,
|
|
76
83
|
targets: Partial<AgentTargets>,
|
|
77
84
|
expectedUpdatedAt?: string,
|
|
85
|
+
native?: { reference: NativeSkillReference; settingsRevision: number },
|
|
78
86
|
): Promise<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }> {
|
|
87
|
+
if (native) {
|
|
88
|
+
checkedNativeReference(native.reference, skillId);
|
|
89
|
+
if (!Number.isSafeInteger(native.settingsRevision) || native.settingsRevision < 0) throw new Error('Refresh native Skill settings before changing assignments.');
|
|
90
|
+
const payload = { operation: 'update', target: { skill_id: skillId, access_revision: native.reference.access_revision },
|
|
91
|
+
revision: native.settingsRevision, patch: { agent_targets: Object.fromEntries(Object.entries(targets).sort(([a], [b]) => a.localeCompare(b))) } };
|
|
92
|
+
const requestId = 'sync-settings-' + createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
93
|
+
const saved = await requestJson<{ receipt_id: string; skill_id: string; target: { skill_id: string }; settings: { agent_targets: AgentTargets; updated_at: string };
|
|
94
|
+
current?: { target: { skill_id: string }; settings: { agent_targets: AgentTargets; updated_at: string } } }>(`${serverUrl}/portal_skills/native-settings`, jwt,
|
|
95
|
+
{ body: { ...payload, request_id: requestId } });
|
|
96
|
+
const current = saved.current || saved;
|
|
97
|
+
if (saved.skill_id !== skillId || typeof saved.receipt_id !== 'string' || !saved.receipt_id
|
|
98
|
+
|| current.target?.skill_id !== skillId || !current.settings) throw new Error('Native assignment update was not confirmed.');
|
|
99
|
+
return { success: true, agent_targets: current.settings.agent_targets, updated_at: current.settings.updated_at };
|
|
100
|
+
}
|
|
79
101
|
return requestJson<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }>(`${serverUrl}/portal_skills/agent-targets`, jwt, {
|
|
80
102
|
method: 'PATCH',
|
|
81
103
|
body: {
|