@axiom-lattice/core 7.0.3 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +158 -3
- package/dist/index.d.ts +158 -3
- package/dist/index.js +655 -92
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +652 -92
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1800,6 +1800,7 @@ __export(index_exports, {
|
|
|
1800
1800
|
buildTableName: () => buildTableName,
|
|
1801
1801
|
buildTaskMutationFallback: () => buildTaskMutationFallback,
|
|
1802
1802
|
buildTaskNormalizationFallback: () => buildTaskNormalizationFallback,
|
|
1803
|
+
canConvergeOrphan: () => canConvergeOrphan,
|
|
1803
1804
|
checkEmptyContent: () => checkEmptyContent,
|
|
1804
1805
|
clearEncryptionKeyCache: () => clearEncryptionKeyCache,
|
|
1805
1806
|
clearEvalRunService: () => clearEvalRunService,
|
|
@@ -1849,6 +1850,7 @@ __export(index_exports, {
|
|
|
1849
1850
|
createTaskLifecycleService: () => createTaskLifecycleService,
|
|
1850
1851
|
createTaskMiddleware: () => createTaskMiddleware,
|
|
1851
1852
|
createTaskRunBridge: () => createTaskRunBridge,
|
|
1853
|
+
createTaskRunLiveness: () => createTaskRunLiveness,
|
|
1852
1854
|
createTeamMiddleware: () => createTeamMiddleware,
|
|
1853
1855
|
createTeammateTools: () => createTeammateTools,
|
|
1854
1856
|
createTrustedAgentMessageDispatcher: () => createTrustedAgentMessageDispatcher,
|
|
@@ -1946,6 +1948,7 @@ __export(index_exports, {
|
|
|
1946
1948
|
isMcpConnectionError: () => isMcpConnectionError,
|
|
1947
1949
|
isPluginSkillAuthorizedInSandbox: () => isPluginSkillAuthorizedInSandbox,
|
|
1948
1950
|
isPluginSkillManagedInSandbox: () => isPluginSkillManagedInSandbox,
|
|
1951
|
+
isTaskRunLeaseHeld: () => isTaskRunLeaseHeld,
|
|
1949
1952
|
isUsingDefaultKey: () => isUsingDefaultKey,
|
|
1950
1953
|
isValidCronExpression: () => isValidCronExpression,
|
|
1951
1954
|
isValidSandboxName: () => isValidSandboxName,
|
|
@@ -9058,7 +9061,13 @@ If the Worker has task capability, it may truthfully update the same task.
|
|
|
9058
9061
|
Otherwise, the owning caller interprets the returned observation.
|
|
9059
9062
|
|
|
9060
9063
|
An execution result is an observation, not automatic proof of business
|
|
9061
|
-
completion. \`task\` returns its exact \`executionResultId
|
|
9064
|
+
completion. \`task\` returns its exact \`executionResultId\` and the task's
|
|
9065
|
+
\`taskStatus\` (re-read **after** the worker settled). **Check \`taskStatus\`
|
|
9066
|
+
before completing:** if it is already \`completed\` the task is done \u2014 do NOT
|
|
9067
|
+
complete it again (use \`add_activity\` for extra notes); if it is still
|
|
9068
|
+
\`in_progress\` you are the single completer and may \`set_status completed\`;
|
|
9069
|
+
if it is \`failed\` or \`interrupted\`, act on that (retry / resume / new task)
|
|
9070
|
+
instead. After \`task\`
|
|
9062
9071
|
returns, inspect the observation, then pass that \`executionResultId\` to the
|
|
9063
9072
|
meaningful \`manage_task set_status\` (status transition), \`manage_task
|
|
9064
9073
|
update\` (content fields), or \`manage_task add_activity\` that interprets
|
|
@@ -9102,7 +9111,11 @@ handled by the configured HITL lifecycle; \`review_required\` is not a task stat
|
|
|
9102
9111
|
Create the active parent and each started subtask as records: \`manage_task
|
|
9103
9112
|
create\` always persists \`pending\`. **Process the task you were assigned in this
|
|
9104
9113
|
run: set it \`in_progress\` with \`manage_task set_status\` when you start (a
|
|
9105
|
-
dispatched task is claimed automatically by the engine).**
|
|
9114
|
+
dispatched task is claimed automatically by the engine).** \`in_progress\` means a
|
|
9115
|
+
run is executing that task: set it yourself ONLY for the task THIS run executes,
|
|
9116
|
+
and for a task you delegate leave it \`pending\` \u2014 \`task\` dispatch claims it.
|
|
9117
|
+
Complete a task exactly once: if it is already \`completed\`, do NOT complete it
|
|
9118
|
+
again (the terminal state stands; use \`add_activity\` for extra notes). To bring
|
|
9106
9119
|
in a more suitable expert, create a subtask (\`pending\`) and dispatch THAT with
|
|
9107
9120
|
\`task { taskId, agentId }\` (a specialist, or agentId "general-purpose" to run your
|
|
9108
9121
|
own capabilities in an isolated run). **Never re-dispatch the task you are
|
|
@@ -10668,7 +10681,9 @@ criteria are truly met \u2014 never as a workaround.
|
|
|
10668
10681
|
\`manage_task create(title: <goal>, description: <contract + Belief State>, ownerType: "agent")\`
|
|
10669
10682
|
\`create\` always yields a \`pending\` record \u2014 it never starts execution. Record
|
|
10670
10683
|
the returned parent task id, then **process it in this run**, setting it
|
|
10671
|
-
\`in_progress\` with \`manage_task set_status\` when you start.
|
|
10684
|
+
\`in_progress\` with \`manage_task set_status\` when you start. Set \`in_progress\`
|
|
10685
|
+
only for a task THIS run executes; a task you delegate stays \`pending\` and is
|
|
10686
|
+
claimed by \`task\` dispatch. To decompose, create each subtask
|
|
10672
10687
|
with \`manage_task create\` (\`pending\`) and delegate it with \`task { taskId,
|
|
10673
10688
|
agentId }\` to the agent that should execute it (a specialist, or
|
|
10674
10689
|
"general-purpose" for your own capabilities). The executor does not need
|
|
@@ -10701,10 +10716,13 @@ ${TASK_BELIEF_REFERENCE_EXAMPLE}
|
|
|
10701
10716
|
## Status discipline \u2014 MANDATORY
|
|
10702
10717
|
|
|
10703
10718
|
- Execution ownership: process the task you were assigned in this run; set it
|
|
10704
|
-
\`in_progress\` with \`manage_task set_status\` when you start.
|
|
10719
|
+
\`in_progress\` with \`manage_task set_status\` when you start. Set it only for a
|
|
10720
|
+
task THIS run executes; a task you delegate stays \`pending\`. To involve a more
|
|
10705
10721
|
suitable expert, create a subtask and dispatch it with \`task { taskId, agentId }\`
|
|
10706
10722
|
\u2014 never re-dispatch the task you are processing. Report the outcome with
|
|
10707
10723
|
\`set_status completed\` (result + beliefImpact), \`failed\`, or \`interrupted\`.
|
|
10724
|
+
Complete a task exactly once: if it is already \`completed\`, do not complete it
|
|
10725
|
+
again \u2014 use \`add_activity\` for extra notes.
|
|
10708
10726
|
- All status transitions go through \`manage_task set_status\` (with
|
|
10709
10727
|
\`result\` on completed, \`failureReason\` on failed). \`update\` is for
|
|
10710
10728
|
content fields only and rejects a status field.
|
|
@@ -21382,6 +21400,7 @@ async function runDiscoverHandler(executor, manifest, handler, config, tenantId2
|
|
|
21382
21400
|
var PLUGINS_ROOT2 = "/root/.agents/plugins";
|
|
21383
21401
|
var PLUGIN_TYPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
21384
21402
|
var DEFAULT_HASH_TTL_MS = 3e4;
|
|
21403
|
+
var BACKUP_DIR_NAME = ".backup";
|
|
21385
21404
|
function pluginTypeFromPath(dirPath) {
|
|
21386
21405
|
return dirPath.slice(dirPath.lastIndexOf("/") + 1);
|
|
21387
21406
|
}
|
|
@@ -21393,6 +21412,10 @@ function topLevelPluginDirs(entries) {
|
|
|
21393
21412
|
return remainder.length > 0 && !remainder.includes("/");
|
|
21394
21413
|
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
21395
21414
|
}
|
|
21415
|
+
function isUnderBackup(path8, pluginDir) {
|
|
21416
|
+
const backupDir = `${pluginDir}/${BACKUP_DIR_NAME}`;
|
|
21417
|
+
return path8 === backupDir || path8.startsWith(`${backupDir}/`);
|
|
21418
|
+
}
|
|
21396
21419
|
var TenantPluginCatalog = class {
|
|
21397
21420
|
/**
|
|
21398
21421
|
* @param deps - Sandbox resolver, executor, reserved types, and cache/logger seams
|
|
@@ -21428,6 +21451,177 @@ var TenantPluginCatalog = class {
|
|
|
21428
21451
|
const snapshot = await this.ensureLoaded(tenantId2);
|
|
21429
21452
|
return snapshot.manifests[type];
|
|
21430
21453
|
}
|
|
21454
|
+
/**
|
|
21455
|
+
* Validate a single plugin by reading its files from the sandbox volume.
|
|
21456
|
+
*
|
|
21457
|
+
* Dry-run counterpart to {@link reload}: it lists the plugin directory, reads
|
|
21458
|
+
* `plugin.json`, and runs the same manifest validation the catalog uses when
|
|
21459
|
+
* loading, with handler existence checked against the files actually present
|
|
21460
|
+
* on disk. It never touches the cache and never publishes anything, so a
|
|
21461
|
+
* caller can validate an on-disk plugin without making it live.
|
|
21462
|
+
*
|
|
21463
|
+
* @param tenantId - Tenant that owns the plugin
|
|
21464
|
+
* @param type - Plugin type (directory name); must match the kebab-case type pattern
|
|
21465
|
+
* @returns The structured `diagnostics` and whether the manifest passed fatal
|
|
21466
|
+
* validation (`valid`). A non-kebab-case `type`, an unreadable directory, or
|
|
21467
|
+
* a missing/invalid `plugin.json` yields a diagnostic with `valid: false`.
|
|
21468
|
+
*/
|
|
21469
|
+
async validatePluginFromDisk(tenantId2, type) {
|
|
21470
|
+
if (typeof type !== "string" || !PLUGIN_TYPE_RE.test(type)) {
|
|
21471
|
+
return {
|
|
21472
|
+
diagnostics: [
|
|
21473
|
+
{
|
|
21474
|
+
level: "error",
|
|
21475
|
+
code: "TYPE_INVALID",
|
|
21476
|
+
pluginType: type,
|
|
21477
|
+
message: `type "${type}" must match ${PLUGIN_TYPE_RE}`
|
|
21478
|
+
}
|
|
21479
|
+
],
|
|
21480
|
+
valid: false
|
|
21481
|
+
};
|
|
21482
|
+
}
|
|
21483
|
+
const pluginDir = `${PLUGINS_ROOT2}/${type}`;
|
|
21484
|
+
const sandbox = await this.deps.resolveSandbox({ tenantId: tenantId2 });
|
|
21485
|
+
let listing;
|
|
21486
|
+
try {
|
|
21487
|
+
listing = await sandbox.file.listPath(pluginDir, { recursive: true });
|
|
21488
|
+
} catch (err) {
|
|
21489
|
+
return {
|
|
21490
|
+
diagnostics: [
|
|
21491
|
+
{
|
|
21492
|
+
level: "error",
|
|
21493
|
+
code: "PLUGINS_LIST_FAILED",
|
|
21494
|
+
pluginType: type,
|
|
21495
|
+
message: err instanceof Error ? err.message : String(err)
|
|
21496
|
+
}
|
|
21497
|
+
],
|
|
21498
|
+
valid: false
|
|
21499
|
+
};
|
|
21500
|
+
}
|
|
21501
|
+
let raw;
|
|
21502
|
+
let manifestJson;
|
|
21503
|
+
try {
|
|
21504
|
+
const { content } = await sandbox.file.readFile(`${pluginDir}/plugin.json`);
|
|
21505
|
+
raw = JSON.parse(content);
|
|
21506
|
+
manifestJson = JSON.stringify(raw);
|
|
21507
|
+
} catch (err) {
|
|
21508
|
+
return {
|
|
21509
|
+
diagnostics: [
|
|
21510
|
+
{
|
|
21511
|
+
level: "error",
|
|
21512
|
+
code: "MANIFEST_READ",
|
|
21513
|
+
pluginType: type,
|
|
21514
|
+
message: err instanceof Error ? err.message : String(err)
|
|
21515
|
+
}
|
|
21516
|
+
],
|
|
21517
|
+
valid: false
|
|
21518
|
+
};
|
|
21519
|
+
}
|
|
21520
|
+
const files = listing.files ?? [];
|
|
21521
|
+
const handlerExists = (rel) => files.some((f) => !f.is_dir && f.path === `${pluginDir}/${rel}`);
|
|
21522
|
+
const knownTypes = new Set(
|
|
21523
|
+
Object.keys(this.cache.get(tenantId2)?.value.manifests ?? {}).filter((t) => t !== type)
|
|
21524
|
+
);
|
|
21525
|
+
const result = validateSandboxPluginManifest({
|
|
21526
|
+
raw,
|
|
21527
|
+
dirName: type,
|
|
21528
|
+
reservedTypes: this.deps.reservedTypes(),
|
|
21529
|
+
knownTypes,
|
|
21530
|
+
handlerExists,
|
|
21531
|
+
rawSizeBytes: Buffer.byteLength(manifestJson, "utf8")
|
|
21532
|
+
});
|
|
21533
|
+
return { diagnostics: result.diagnostics, valid: Boolean(result.manifest) };
|
|
21534
|
+
}
|
|
21535
|
+
/**
|
|
21536
|
+
* Snapshot a plugin's current files into its in-directory backup.
|
|
21537
|
+
*
|
|
21538
|
+
* Keeps a single backup (`<pluginDir>/.backup/`): the previous backup is
|
|
21539
|
+
* replaced. Files already under `.backup/` are excluded, so a backup never
|
|
21540
|
+
* nests. Manual action — it is not triggered by {@link writePlugin}.
|
|
21541
|
+
*
|
|
21542
|
+
* @param tenantId - Tenant that owns the plugin
|
|
21543
|
+
* @param type - Plugin type (directory name)
|
|
21544
|
+
* @returns The number of files snapshotted (`0` when the plugin has no files)
|
|
21545
|
+
*/
|
|
21546
|
+
async backupPlugin(tenantId2, type) {
|
|
21547
|
+
this.assertSafeType(type);
|
|
21548
|
+
const pluginDir = `${PLUGINS_ROOT2}/${type}`;
|
|
21549
|
+
const backupDir = `${pluginDir}/${BACKUP_DIR_NAME}`;
|
|
21550
|
+
const sandbox = await this.deps.resolveSandbox({ tenantId: tenantId2 });
|
|
21551
|
+
const entries = (await sandbox.file.listPath(pluginDir, { recursive: true })).files ?? [];
|
|
21552
|
+
const files = entries.filter((entry) => !entry.is_dir && !isUnderBackup(entry.path, pluginDir));
|
|
21553
|
+
if (files.length === 0) {
|
|
21554
|
+
return { files: 0 };
|
|
21555
|
+
}
|
|
21556
|
+
if (entries.some((entry) => isUnderBackup(entry.path, pluginDir))) {
|
|
21557
|
+
await sandbox.file.deletePath(backupDir);
|
|
21558
|
+
}
|
|
21559
|
+
let count = 0;
|
|
21560
|
+
for (const entry of files) {
|
|
21561
|
+
const rel = entry.path.slice(pluginDir.length + 1);
|
|
21562
|
+
const { content } = await sandbox.file.readFile(entry.path);
|
|
21563
|
+
await this.writeRelativeFile(sandbox, backupDir, rel, content);
|
|
21564
|
+
count += 1;
|
|
21565
|
+
}
|
|
21566
|
+
return { files: count };
|
|
21567
|
+
}
|
|
21568
|
+
/**
|
|
21569
|
+
* Restore a plugin's files from its in-directory backup.
|
|
21570
|
+
*
|
|
21571
|
+
* The plugin directory (excluding `.backup/`) is made to match the backup:
|
|
21572
|
+
* files present in the backup are (re)written and files absent from it are
|
|
21573
|
+
* removed. Callers should {@link reload} afterward so the restored shape is
|
|
21574
|
+
* picked up.
|
|
21575
|
+
*
|
|
21576
|
+
* @param tenantId - Tenant that owns the plugin
|
|
21577
|
+
* @param type - Plugin type (directory name)
|
|
21578
|
+
* @returns The number of files restored
|
|
21579
|
+
* @throws Error when the plugin has no backup
|
|
21580
|
+
*/
|
|
21581
|
+
async restorePlugin(tenantId2, type) {
|
|
21582
|
+
this.assertSafeType(type);
|
|
21583
|
+
const pluginDir = `${PLUGINS_ROOT2}/${type}`;
|
|
21584
|
+
const backupDir = `${pluginDir}/${BACKUP_DIR_NAME}`;
|
|
21585
|
+
const sandbox = await this.deps.resolveSandbox({ tenantId: tenantId2 });
|
|
21586
|
+
const backupFiles = await (async () => {
|
|
21587
|
+
try {
|
|
21588
|
+
return ((await sandbox.file.listPath(backupDir, { recursive: true })).files ?? []).filter((entry) => !entry.is_dir);
|
|
21589
|
+
} catch {
|
|
21590
|
+
return [];
|
|
21591
|
+
}
|
|
21592
|
+
})();
|
|
21593
|
+
if (backupFiles.length === 0) {
|
|
21594
|
+
throw new Error(`no backup found for plugin "${type}"`);
|
|
21595
|
+
}
|
|
21596
|
+
const backupRels = new Set(backupFiles.map((entry) => entry.path.slice(backupDir.length + 1)));
|
|
21597
|
+
const currentFiles = ((await sandbox.file.listPath(pluginDir, { recursive: true })).files ?? []).filter((entry) => !entry.is_dir && !isUnderBackup(entry.path, pluginDir));
|
|
21598
|
+
for (const entry of currentFiles) {
|
|
21599
|
+
const rel = entry.path.slice(pluginDir.length + 1);
|
|
21600
|
+
if (!backupRels.has(rel)) await sandbox.file.deletePath(entry.path);
|
|
21601
|
+
}
|
|
21602
|
+
let count = 0;
|
|
21603
|
+
for (const entry of backupFiles) {
|
|
21604
|
+
const rel = entry.path.slice(backupDir.length + 1);
|
|
21605
|
+
const { content } = await sandbox.file.readFile(entry.path);
|
|
21606
|
+
await this.writeRelativeFile(sandbox, pluginDir, rel, content);
|
|
21607
|
+
count += 1;
|
|
21608
|
+
}
|
|
21609
|
+
return { files: count };
|
|
21610
|
+
}
|
|
21611
|
+
/**
|
|
21612
|
+
* Write a file under a base directory, creating its parent directory first.
|
|
21613
|
+
*
|
|
21614
|
+
* @param sandbox - Sandbox whose file service receives the write
|
|
21615
|
+
* @param baseDir - Absolute target directory (a plugin or backup directory)
|
|
21616
|
+
* @param rel - Safe path relative to `baseDir`
|
|
21617
|
+
* @param content - File contents
|
|
21618
|
+
* @returns Nothing once the file is written
|
|
21619
|
+
*/
|
|
21620
|
+
async writeRelativeFile(sandbox, baseDir, rel, content) {
|
|
21621
|
+
const slash = rel.lastIndexOf("/");
|
|
21622
|
+
if (slash !== -1) await sandbox.file.createDirectory(`${baseDir}/${rel.slice(0, slash)}`);
|
|
21623
|
+
await sandbox.file.writeFile(`${baseDir}/${rel}`, content);
|
|
21624
|
+
}
|
|
21431
21625
|
/**
|
|
21432
21626
|
* Drop the cached snapshot for a tenant.
|
|
21433
21627
|
*
|
|
@@ -21504,6 +21698,30 @@ var TenantPluginCatalog = class {
|
|
|
21504
21698
|
const { content } = await sandbox.file.readFile(`${PLUGINS_ROOT2}/${type}/${relPath}`);
|
|
21505
21699
|
return content;
|
|
21506
21700
|
}
|
|
21701
|
+
/**
|
|
21702
|
+
* List the relative file paths currently present under a plugin directory.
|
|
21703
|
+
*
|
|
21704
|
+
* Used to decide whether a plugin already exists before scaffolding, and to
|
|
21705
|
+
* surface a plugin's files without reading their contents.
|
|
21706
|
+
*
|
|
21707
|
+
* @param tenantId - Tenant that owns the plugin
|
|
21708
|
+
* @param type - Plugin type (directory name); must match the kebab-case type pattern
|
|
21709
|
+
* @returns Relative paths (e.g. `plugin.json`, `handlers/x.js`); empty when the
|
|
21710
|
+
* directory is missing or the listing fails
|
|
21711
|
+
*/
|
|
21712
|
+
async listPluginFiles(tenantId2, type) {
|
|
21713
|
+
this.assertSafeType(type);
|
|
21714
|
+
const sandbox = await this.deps.resolveSandbox({ tenantId: tenantId2 });
|
|
21715
|
+
const pluginDir = `${PLUGINS_ROOT2}/${type}`;
|
|
21716
|
+
let listing;
|
|
21717
|
+
try {
|
|
21718
|
+
listing = await sandbox.file.listPath(pluginDir, { recursive: true });
|
|
21719
|
+
} catch {
|
|
21720
|
+
return [];
|
|
21721
|
+
}
|
|
21722
|
+
const prefix = `${pluginDir}/`;
|
|
21723
|
+
return listing.files.filter((entry) => !entry.is_dir).map((entry) => entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path);
|
|
21724
|
+
}
|
|
21507
21725
|
/**
|
|
21508
21726
|
* Write a plugin manifest and handler files into the tenant volume.
|
|
21509
21727
|
*
|
|
@@ -21528,11 +21746,7 @@ var TenantPluginCatalog = class {
|
|
|
21528
21746
|
await sandbox.file.createDirectory(dir);
|
|
21529
21747
|
await sandbox.file.writeFile(`${dir}/plugin.json`, JSON.stringify(manifest, null, 2));
|
|
21530
21748
|
for (const [rel, content] of Object.entries(handlers)) {
|
|
21531
|
-
|
|
21532
|
-
if (slash !== -1) {
|
|
21533
|
-
await sandbox.file.createDirectory(`${dir}/${rel.slice(0, slash)}`);
|
|
21534
|
-
}
|
|
21535
|
-
await sandbox.file.writeFile(`${dir}/${rel}`, content);
|
|
21749
|
+
await this.writeRelativeFile(sandbox, dir, rel, content);
|
|
21536
21750
|
}
|
|
21537
21751
|
}
|
|
21538
21752
|
/**
|
|
@@ -21637,7 +21851,7 @@ var TenantPluginCatalog = class {
|
|
|
21637
21851
|
const dirFiles = entries.filter(
|
|
21638
21852
|
(entry) => entry.path === dir.path || entry.path.startsWith(`${dir.path}/`)
|
|
21639
21853
|
);
|
|
21640
|
-
const relPaths = dirFiles.filter((entry) => !entry.is_dir && entry.path !== `${dir.path}/plugin.json`).map((entry) => entry.path.slice(dir.path.length + 1)).sort();
|
|
21854
|
+
const relPaths = dirFiles.filter((entry) => !entry.is_dir && entry.path !== `${dir.path}/plugin.json` && !isUnderBackup(entry.path, dir.path)).map((entry) => entry.path.slice(dir.path.length + 1)).sort();
|
|
21641
21855
|
const manifestPath = `${dir.path}/plugin.json`;
|
|
21642
21856
|
let raw;
|
|
21643
21857
|
let manifestJson;
|
|
@@ -21668,7 +21882,7 @@ var TenantPluginCatalog = class {
|
|
|
21668
21882
|
const manifests = {};
|
|
21669
21883
|
for (const candidate of candidates) {
|
|
21670
21884
|
try {
|
|
21671
|
-
const handlerExists = (rel) => candidate.files.some((f) => f.path === `${candidate.dirPath}/${rel}`);
|
|
21885
|
+
const handlerExists = (rel) => candidate.files.some((f) => !f.is_dir && f.path === `${candidate.dirPath}/${rel}`);
|
|
21672
21886
|
const result = validateSandboxPluginManifest({
|
|
21673
21887
|
raw: candidate.raw,
|
|
21674
21888
|
dirName: candidate.type,
|
|
@@ -33126,6 +33340,12 @@ var _AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager
|
|
|
33126
33340
|
* {@link buildingKeys}.
|
|
33127
33341
|
*/
|
|
33128
33342
|
this.buildContext = new import_async_hooks.AsyncLocalStorage();
|
|
33343
|
+
/**
|
|
33344
|
+
* Store-backed assistant ids previously reconciled per tenant. Used to
|
|
33345
|
+
* unregister assistants that were deleted on another replica without
|
|
33346
|
+
* touching built-in / plugin agents (which are re-ensured on every reload).
|
|
33347
|
+
*/
|
|
33348
|
+
this.storedAssistantKeys = /* @__PURE__ */ new Map();
|
|
33129
33349
|
this.subscribeToAssistantEvents();
|
|
33130
33350
|
}
|
|
33131
33351
|
/**
|
|
@@ -33354,11 +33574,29 @@ var _AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager
|
|
|
33354
33574
|
try {
|
|
33355
33575
|
const storeLattice = getStoreLattice("default", "assistant");
|
|
33356
33576
|
const assistants = await storeLattice.store.getAllAssistants(tenantId2);
|
|
33577
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
33357
33578
|
for (const assistant of assistants) {
|
|
33358
|
-
|
|
33579
|
+
currentIds.add(assistant.id);
|
|
33359
33580
|
const config = assistantToConfig(assistant);
|
|
33360
|
-
this.
|
|
33581
|
+
const existing = this.getAgentConfigWithTenant(tenantId2, assistant.id);
|
|
33582
|
+
if (existing === void 0) {
|
|
33583
|
+
this.registerLatticeWithTenant(tenantId2, config);
|
|
33584
|
+
continue;
|
|
33585
|
+
}
|
|
33586
|
+
if (JSON.stringify(existing) !== JSON.stringify(config)) {
|
|
33587
|
+
this.removeWithTenant(tenantId2, assistant.id);
|
|
33588
|
+
this.registerLatticeWithTenant(tenantId2, config);
|
|
33589
|
+
}
|
|
33590
|
+
}
|
|
33591
|
+
const previousIds = this.storedAssistantKeys.get(tenantId2);
|
|
33592
|
+
if (previousIds) {
|
|
33593
|
+
for (const id of previousIds) {
|
|
33594
|
+
if (!currentIds.has(id)) {
|
|
33595
|
+
this.removeWithTenant(tenantId2, id);
|
|
33596
|
+
}
|
|
33597
|
+
}
|
|
33361
33598
|
}
|
|
33599
|
+
this.storedAssistantKeys.set(tenantId2, currentIds);
|
|
33362
33600
|
} catch (error) {
|
|
33363
33601
|
console.error(`Failed to initialize stored assistants for tenant ${tenantId2}:`, error);
|
|
33364
33602
|
}
|
|
@@ -40404,7 +40642,11 @@ var TaskLifecycleService = class {
|
|
|
40404
40642
|
expectedStatuses: ["completed", "cancelled"],
|
|
40405
40643
|
updates: { status: "in_progress", failureReason: null, result: null },
|
|
40406
40644
|
summary: "Task reopened",
|
|
40407
|
-
detail: { type: "reopen" }
|
|
40645
|
+
detail: { type: "reopen" },
|
|
40646
|
+
// Preserve an existing thread by default (the project runtime reuses the
|
|
40647
|
+
// canonical Task Thread); a dispatch that allocated a fresh run thread
|
|
40648
|
+
// opts in to replacing it.
|
|
40649
|
+
replaceThread: input.replaceThread ?? false
|
|
40408
40650
|
});
|
|
40409
40651
|
}
|
|
40410
40652
|
/**
|
|
@@ -42265,6 +42507,39 @@ function createTaskRunBridge(deps) {
|
|
|
42265
42507
|
transitioned: true
|
|
42266
42508
|
};
|
|
42267
42509
|
}
|
|
42510
|
+
if (task.status === "completed" || task.status === "cancelled") {
|
|
42511
|
+
if (task.ownerType !== "agent") {
|
|
42512
|
+
return {
|
|
42513
|
+
success: false,
|
|
42514
|
+
code: "AGENT_TASK_REQUIRED",
|
|
42515
|
+
error: "Lifecycle transitions only support agent tasks."
|
|
42516
|
+
};
|
|
42517
|
+
}
|
|
42518
|
+
const reopened = await deps.lifecycle.reopenTask({
|
|
42519
|
+
tenantId: tenantId2,
|
|
42520
|
+
taskId: task.id,
|
|
42521
|
+
actor,
|
|
42522
|
+
...threadId !== void 0 ? { threadId } : {},
|
|
42523
|
+
// The caller allocated a fresh run thread for this dispatch.
|
|
42524
|
+
replaceThread: true
|
|
42525
|
+
});
|
|
42526
|
+
if (!reopened.success) {
|
|
42527
|
+
return {
|
|
42528
|
+
success: false,
|
|
42529
|
+
code: reopened.code,
|
|
42530
|
+
error: reopened.error,
|
|
42531
|
+
...reopened.hint !== void 0 ? { hint: reopened.hint } : {},
|
|
42532
|
+
...reopened.retryable !== void 0 ? { retryable: reopened.retryable } : {}
|
|
42533
|
+
};
|
|
42534
|
+
}
|
|
42535
|
+
return {
|
|
42536
|
+
success: true,
|
|
42537
|
+
task: reopened.task,
|
|
42538
|
+
warnings: reopened.warnings ?? [],
|
|
42539
|
+
mutated: true,
|
|
42540
|
+
transitioned: true
|
|
42541
|
+
};
|
|
42542
|
+
}
|
|
42268
42543
|
return { success: true, task, warnings, mutated: false, transitioned: false };
|
|
42269
42544
|
},
|
|
42270
42545
|
async settleRun(input) {
|
|
@@ -42437,6 +42712,45 @@ async function tryAcquireTaskRun(input) {
|
|
|
42437
42712
|
};
|
|
42438
42713
|
}
|
|
42439
42714
|
|
|
42715
|
+
// src/services/taskLiveness.ts
|
|
42716
|
+
function registeredLeaseStore() {
|
|
42717
|
+
try {
|
|
42718
|
+
return storeLatticeManager.getStoreLattice("default", "lease").store;
|
|
42719
|
+
} catch {
|
|
42720
|
+
return void 0;
|
|
42721
|
+
}
|
|
42722
|
+
}
|
|
42723
|
+
async function isTaskRunLeaseHeld(task) {
|
|
42724
|
+
const store = registeredLeaseStore();
|
|
42725
|
+
if (!store) return false;
|
|
42726
|
+
try {
|
|
42727
|
+
if (await store.get(taskRunLeaseKey(task.id))) return true;
|
|
42728
|
+
const threadId = typeof task.context?.thread_id === "string" ? task.context.thread_id : void 0;
|
|
42729
|
+
if (threadId && task.ownerId && await store.get(threadRunLeaseKey(task.tenantId, task.ownerId, threadId))) {
|
|
42730
|
+
return true;
|
|
42731
|
+
}
|
|
42732
|
+
return false;
|
|
42733
|
+
} catch {
|
|
42734
|
+
return true;
|
|
42735
|
+
}
|
|
42736
|
+
}
|
|
42737
|
+
function createTaskRunLiveness() {
|
|
42738
|
+
return async (task) => taskRunRegistry.isLive(task.id) || isTaskRunLeaseHeld(task);
|
|
42739
|
+
}
|
|
42740
|
+
function isMultiReplicaDeclared() {
|
|
42741
|
+
const id = process.env.AXIOM_REPLICA_ID;
|
|
42742
|
+
return typeof id === "string" && id.trim() !== "";
|
|
42743
|
+
}
|
|
42744
|
+
function isSharedLeaseStore() {
|
|
42745
|
+
const store = registeredLeaseStore();
|
|
42746
|
+
const name = store?.constructor?.name;
|
|
42747
|
+
return typeof name === "string" && !name.startsWith("InMemory");
|
|
42748
|
+
}
|
|
42749
|
+
function canConvergeOrphan() {
|
|
42750
|
+
if (!isMultiReplicaDeclared()) return true;
|
|
42751
|
+
return isSharedLeaseStore();
|
|
42752
|
+
}
|
|
42753
|
+
|
|
42440
42754
|
// src/services/ExecutionAuthorityRegistry.ts
|
|
42441
42755
|
var ManagedTaskStartError = class extends Error {
|
|
42442
42756
|
/**
|
|
@@ -42520,7 +42834,10 @@ async function prepareTaskDispatch(deps, input) {
|
|
|
42520
42834
|
}
|
|
42521
42835
|
};
|
|
42522
42836
|
}
|
|
42523
|
-
|
|
42837
|
+
const managedAuthority = executionAuthorityRegistry.resolve(task);
|
|
42838
|
+
const terminal = task.status === "completed" || task.status === "cancelled";
|
|
42839
|
+
const reopenableTerminal = terminal && task.ownerType === "agent" && !managedAuthority;
|
|
42840
|
+
if (terminal && !reopenableTerminal) {
|
|
42524
42841
|
return {
|
|
42525
42842
|
kind: "failure",
|
|
42526
42843
|
failure: {
|
|
@@ -42544,19 +42861,18 @@ async function prepareTaskDispatch(deps, input) {
|
|
|
42544
42861
|
}
|
|
42545
42862
|
};
|
|
42546
42863
|
}
|
|
42547
|
-
if (task.status !== "pending" && task.status !== "interrupted" && task.status !== "failed") {
|
|
42864
|
+
if (task.status !== "pending" && task.status !== "interrupted" && task.status !== "failed" && !reopenableTerminal) {
|
|
42548
42865
|
return {
|
|
42549
42866
|
kind: "failure",
|
|
42550
42867
|
failure: {
|
|
42551
42868
|
success: false,
|
|
42552
42869
|
code: "TASK_NOT_DISPATCHABLE",
|
|
42553
|
-
error: "Only a `pending`, `interrupted`,
|
|
42554
|
-
hint: `This task is '${task.status}'. A \`pending\` task starts, an \`interrupted\` task resumes,
|
|
42870
|
+
error: "Only a `pending`, `interrupted`, `failed`, or reopenable terminal task can be dispatched.",
|
|
42871
|
+
hint: `This task is '${task.status}'. A \`pending\` task starts, an \`interrupted\` task resumes, a \`failed\` task retries, and an agent-owned \`completed\`/\`cancelled\` task reopens. A task already \`in_progress\` is held by a live run.`,
|
|
42555
42872
|
taskId: task.id
|
|
42556
42873
|
}
|
|
42557
42874
|
};
|
|
42558
42875
|
}
|
|
42559
|
-
const managedAuthority = executionAuthorityRegistry.resolve(task);
|
|
42560
42876
|
if (managedAuthority) {
|
|
42561
42877
|
if (managedAuthority.onManagedStart) {
|
|
42562
42878
|
try {
|
|
@@ -42726,7 +43042,8 @@ function persistedExecutionObservation(item, attempted) {
|
|
|
42726
43042
|
}
|
|
42727
43043
|
function buildWorkerMessage(task, parent, dependencies) {
|
|
42728
43044
|
const sections2 = [
|
|
42729
|
-
|
|
43045
|
+
`You have been assigned persistent task \`${task.id}\` to execute in THIS run. Do NOT dispatch this task \u2014 \`task\` is only for delegating a NEW child subtask; this task is already yours.`,
|
|
43046
|
+
"When the acceptance criteria are truly met, record the outcome yourself with `manage_task set_status completed` (result + beliefImpact), or `failed`/`interrupted`. If you do not have `manage_task`, return your result as an observation for the caller to record.",
|
|
42730
43047
|
`Persisted Task ID: \`${task.id}\``,
|
|
42731
43048
|
`# Task: ${task.title}`,
|
|
42732
43049
|
task.description?.trim() || "No task description was provided."
|
|
@@ -42758,9 +43075,17 @@ function createTaskExecutionService(deps) {
|
|
|
42758
43075
|
taskStore: deps.taskStore,
|
|
42759
43076
|
workItemStore: deps.workItemStore
|
|
42760
43077
|
});
|
|
43078
|
+
const isTaskHeldByLiveRun = deps.isTaskHeldByLiveRun ?? createTaskRunLiveness();
|
|
42761
43079
|
const run = async (plan) => {
|
|
42762
43080
|
const { tenantId: tenantId2, agentId, actor, parent, completedDependencies } = plan;
|
|
42763
43081
|
let task = plan.task;
|
|
43082
|
+
const readTaskStatus = async () => {
|
|
43083
|
+
try {
|
|
43084
|
+
return (await deps.taskStore.getById(tenantId2, task.id))?.status;
|
|
43085
|
+
} catch {
|
|
43086
|
+
return void 0;
|
|
43087
|
+
}
|
|
43088
|
+
};
|
|
42764
43089
|
const storedThreadId = (task.status === "interrupted" || task.status === "failed") && typeof task.context?.thread_id === "string" && task.context.thread_id.trim() !== "" ? task.context.thread_id : void 0;
|
|
42765
43090
|
const storedRunId = typeof task.context?.run_id === "string" && task.context.run_id.trim() !== "" ? task.context.run_id : void 0;
|
|
42766
43091
|
let reuseCurrentRun = false;
|
|
@@ -42819,7 +43144,7 @@ function createTaskExecutionService(deps) {
|
|
|
42819
43144
|
task = transfer.task;
|
|
42820
43145
|
}
|
|
42821
43146
|
const warnings = [];
|
|
42822
|
-
if (task.status === "pending" || task.status === "interrupted" || task.status === "failed") {
|
|
43147
|
+
if (task.status === "pending" || task.status === "interrupted" || task.status === "failed" || task.status === "completed" || task.status === "cancelled") {
|
|
42823
43148
|
const begun = await bridge.beginRun({
|
|
42824
43149
|
tenantId: tenantId2,
|
|
42825
43150
|
task,
|
|
@@ -42946,6 +43271,7 @@ function createTaskExecutionService(deps) {
|
|
|
42946
43271
|
...warnings.length > 0 ? { warnings } : {}
|
|
42947
43272
|
};
|
|
42948
43273
|
}
|
|
43274
|
+
const settledStatus = await readTaskStatus();
|
|
42949
43275
|
return {
|
|
42950
43276
|
success: false,
|
|
42951
43277
|
code: failureCode,
|
|
@@ -42954,6 +43280,7 @@ function createTaskExecutionService(deps) {
|
|
|
42954
43280
|
agentId,
|
|
42955
43281
|
threadId,
|
|
42956
43282
|
executionResultId: eventKey,
|
|
43283
|
+
...settledStatus !== void 0 ? { taskStatus: settledStatus } : {},
|
|
42957
43284
|
...warnings.length > 0 ? { warnings } : {}
|
|
42958
43285
|
};
|
|
42959
43286
|
} catch (persistenceError) {
|
|
@@ -43002,6 +43329,7 @@ function createTaskExecutionService(deps) {
|
|
|
43002
43329
|
...warnings.length > 0 ? { warnings } : {}
|
|
43003
43330
|
};
|
|
43004
43331
|
}
|
|
43332
|
+
const taskStatus = await readTaskStatus();
|
|
43005
43333
|
return {
|
|
43006
43334
|
success: true,
|
|
43007
43335
|
taskId: task.id,
|
|
@@ -43009,6 +43337,7 @@ function createTaskExecutionService(deps) {
|
|
|
43009
43337
|
threadId,
|
|
43010
43338
|
observation: validated.observation,
|
|
43011
43339
|
executionResultId: eventKey,
|
|
43340
|
+
...taskStatus !== void 0 ? { taskStatus } : {},
|
|
43012
43341
|
...warnings.length > 0 ? { warnings } : {}
|
|
43013
43342
|
};
|
|
43014
43343
|
} catch (error) {
|
|
@@ -43029,6 +43358,23 @@ function createTaskExecutionService(deps) {
|
|
|
43029
43358
|
};
|
|
43030
43359
|
return {
|
|
43031
43360
|
start: async (input) => {
|
|
43361
|
+
let current;
|
|
43362
|
+
try {
|
|
43363
|
+
current = await deps.taskStore.getById(input.tenantId, input.taskId);
|
|
43364
|
+
} catch {
|
|
43365
|
+
current = null;
|
|
43366
|
+
}
|
|
43367
|
+
if (current && current.status === "in_progress" && current.ownerType === "agent" && canConvergeOrphan() && !executionAuthorityRegistry.resolve(current) && !await isTaskHeldByLiveRun(current)) {
|
|
43368
|
+
try {
|
|
43369
|
+
await lifecycle.failTask({
|
|
43370
|
+
tenantId: input.tenantId,
|
|
43371
|
+
taskId: current.id,
|
|
43372
|
+
actor: input.actor,
|
|
43373
|
+
failureReason: "Run is no longer executing this task (no_executor)"
|
|
43374
|
+
});
|
|
43375
|
+
} catch {
|
|
43376
|
+
}
|
|
43377
|
+
}
|
|
43032
43378
|
const prepared = await prepareTaskDispatch(deps, input);
|
|
43033
43379
|
if (prepared.kind === "managed") return prepared.result;
|
|
43034
43380
|
if (prepared.kind === "failure") return prepared.failure;
|
|
@@ -45173,9 +45519,85 @@ Use \`imagine_html\`. Wrap the entire thing in a single raised card. All content
|
|
|
45173
45519
|
</div>
|
|
45174
45520
|
</div>
|
|
45175
45521
|
\`\`\``;
|
|
45522
|
+
var ANNOTATABLE_MOCKUP = `## Annotatable drafts (reviewable mockups)
|
|
45523
|
+
|
|
45524
|
+
When the output is a **UI design** \u2014 a draft the user critiques element-by-element \u2014 make it **annotatable** so feedback comes back to the conversation and you can revise. **Right-click** (\`contextmenu\`) any marked element to leave a note; a plain left-click stays free for the mock's own interactions. (A structural or functional overview is different: make it **click-to-explore interactive** to confirm, not annotatable.)
|
|
45525
|
+
|
|
45526
|
+
- Show a small **always-visible hint** (in the user's language) so the draft is discoverable, e.g. \`\u53F3\u952E\u4EFB\u610F\u5143\u7D20\u6279\u6CE8\` / \`Right-click an element to comment\`.
|
|
45527
|
+
- Mark each element worth commenting on with \`class="anno"\`; highlight hover with an **\`outline\`** (never \`border\` \u2014 outline does not reflow the layout). Give it a **negative \`outline-offset\`** (e.g. \`-2px\`) so the frame sits *inside* the element: a positive offset can fall outside the container and get clipped, hiding the cue.
|
|
45528
|
+
- Wrap the whole draft in a \`position: relative\` container and put the comment box in an overlay inside it with \`position: absolute; inset: 0\` \u2014 **never** \`position: fixed\` (it collapses the iframe to min-height).
|
|
45529
|
+
- Handle \`contextmenu\` with event delegation; \`preventDefault()\` + \`stopPropagation()\` (this replaces the native browser menu, which is fine on a draft).
|
|
45530
|
+
- The dialog shows the element name and its **current value**, a note field, and **Cancel / Send** buttons (Send is primary). Cancel, clicking the scrim, and \`Escape\` all close without sending. Mark the element being commented (add \`.marked\`, a solid outline) so it is obvious which one is active.
|
|
45531
|
+
- Send the note back with \`sendPrompt()\` as a clear instruction \u2014 the element, its current value, and the requested change \u2014 so the agent knows exactly what to change. The agent then restates the feedback and re-shows a revised draft.
|
|
45532
|
+
|
|
45533
|
+
\`\`\`html
|
|
45534
|
+
<style>
|
|
45535
|
+
.anno{position:relative}
|
|
45536
|
+
.anno:hover{outline:1px dashed var(--color-border-info);outline-offset:-2px;border-radius:var(--border-radius-md)}
|
|
45537
|
+
.anno.marked{outline:1.5px solid var(--color-text-info);outline-offset:-2px}
|
|
45538
|
+
.overlay{position:absolute;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-lg);z-index:10}
|
|
45539
|
+
.modal{background:var(--color-background-primary);border:0.5px solid var(--color-border-secondary);border-radius:var(--border-radius-lg);padding:16px;width:88%;max-width:420px}
|
|
45540
|
+
.modal .mt{font-size:14px;font-weight:500;margin-bottom:2px}
|
|
45541
|
+
.modal .ms{font-size:11px;color:var(--color-text-tertiary);margin-bottom:10px}
|
|
45542
|
+
.modal textarea{width:100%;box-sizing:border-box;height:auto;min-height:72px;padding:8px 10px;resize:vertical}
|
|
45543
|
+
.modal .mrow{display:flex;gap:8px;justify-content:flex-end;margin-top:10px}
|
|
45544
|
+
.primary{background:var(--color-text-info);color:var(--color-background-primary);border-color:var(--color-text-info)}
|
|
45545
|
+
.primary:hover{background:var(--color-text-info);opacity:.9}
|
|
45546
|
+
</style>
|
|
45547
|
+
<div class="panel" style="position:relative">
|
|
45548
|
+
<div style="font-size:12px;color:var(--color-text-tertiary);margin-bottom:8px">Right-click an element to comment</div>
|
|
45549
|
+
<div class="anno" data-el="Title" data-val="Q4 revenue">Q4 revenue</div>
|
|
45550
|
+
<div class="anno" data-el="Primary action" data-val="Export">Export</div>
|
|
45551
|
+
</div>
|
|
45552
|
+
<script>
|
|
45553
|
+
var panel = document.querySelector('.panel');
|
|
45554
|
+
var current = null;
|
|
45555
|
+
function closeBox(){
|
|
45556
|
+
if (current) current.classList.remove('marked');
|
|
45557
|
+
var ov = panel.querySelector('.overlay');
|
|
45558
|
+
if (ov) ov.remove();
|
|
45559
|
+
current = null;
|
|
45560
|
+
}
|
|
45561
|
+
function openBox(el){
|
|
45562
|
+
closeBox();
|
|
45563
|
+
current = el;
|
|
45564
|
+
el.classList.add('marked');
|
|
45565
|
+
var ov = document.createElement('div');
|
|
45566
|
+
ov.className = 'overlay';
|
|
45567
|
+
ov.innerHTML = '<div class="modal">' +
|
|
45568
|
+
'<div class="mt">Note: ' + el.dataset.el + '</div>' +
|
|
45569
|
+
(el.dataset.val ? '<div class="ms">Current: ' + el.dataset.val + '</div>' : '') +
|
|
45570
|
+
'<textarea id="cmt" placeholder="What should change? e.g. bolder bar / expand by default / add a year filter"></textarea>' +
|
|
45571
|
+
'<div class="mrow"><button id="cancel">Cancel</button><button class="primary" id="send">Send note \u2197</button></div>' +
|
|
45572
|
+
'</div>';
|
|
45573
|
+
panel.appendChild(ov);
|
|
45574
|
+
ov.querySelector('#cmt').focus();
|
|
45575
|
+
ov.querySelector('#send').onclick = function(){
|
|
45576
|
+
var text = ov.querySelector('#cmt').value.trim();
|
|
45577
|
+
if (text && typeof sendPrompt === 'function') {
|
|
45578
|
+
sendPrompt('[design note] ' + el.dataset.el + (el.dataset.val ? ' (current: ' + el.dataset.val + ')' : '') + ' \u2014 change: ' + text);
|
|
45579
|
+
}
|
|
45580
|
+
closeBox();
|
|
45581
|
+
};
|
|
45582
|
+
ov.querySelector('#cancel').onclick = closeBox;
|
|
45583
|
+
ov.addEventListener('click', function(e){ if (e.target === ov) closeBox(); });
|
|
45584
|
+
}
|
|
45585
|
+
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeBox(); });
|
|
45586
|
+
panel.addEventListener('contextmenu', function(e){
|
|
45587
|
+
var el = e.target.closest('.anno');
|
|
45588
|
+
if (!el) return;
|
|
45589
|
+
e.preventDefault(); e.stopPropagation();
|
|
45590
|
+
openBox(el);
|
|
45591
|
+
});
|
|
45592
|
+
</script>
|
|
45593
|
+
\`\`\`
|
|
45594
|
+
|
|
45595
|
+
- The host already pre-styles bare \`<button>\` and \`<textarea>\` (token borders, radius, hover/focus) \u2014 use them as-is and add only what is missing: a \`.primary\` variant for the send button and a \`width\`/\`min-height\` override on the \`textarea\`. Do NOT re-declare the base button/field styles. Add a \`\u2197\` to the send button (it triggers \`sendPrompt\`).
|
|
45596
|
+
|
|
45597
|
+
Keep it lightweight: annotate only meaningful elements, reuse one overlay, no libraries, and put \`<script>\` last so the draft streams in top-down.`;
|
|
45176
45598
|
var MODULE_SECTIONS = {
|
|
45177
45599
|
art: [SVG_SETUP, ART_AND_ILLUSTRATION],
|
|
45178
|
-
mockup: [UI_COMPONENTS, COLOR_PALETTE],
|
|
45600
|
+
mockup: [UI_COMPONENTS, COLOR_PALETTE, ANNOTATABLE_MOCKUP],
|
|
45179
45601
|
interactive: [UI_COMPONENTS, COLOR_PALETTE],
|
|
45180
45602
|
chart: [UI_COMPONENTS, COLOR_PALETTE, CHARTS_CHART_JS],
|
|
45181
45603
|
diagram: [COLOR_PALETTE, SVG_SETUP, DIAGRAM_TYPES]
|
|
@@ -45677,7 +46099,7 @@ function renderTaskBinding(taskId) {
|
|
|
45677
46099
|
|
|
45678
46100
|
## Bound Task
|
|
45679
46101
|
|
|
45680
|
-
You are executing task \`${taskId}
|
|
46102
|
+
You are executing task \`${taskId}\` in THIS run \u2014 it is already assigned to you. Read its contract with \`manage_task get\`, update it as you work, and complete it when done. Do NOT dispatch this task with \`task\`; \`task\` is only for delegating a NEW child subtask.
|
|
45681
46103
|
`;
|
|
45682
46104
|
}
|
|
45683
46105
|
function createBoundTaskLifecycle() {
|
|
@@ -46196,7 +46618,7 @@ var manageTaskSchema = import_zod68.z.object({
|
|
|
46196
46618
|
title: import_zod68.z.string().optional().describe("Task title (required for create)"),
|
|
46197
46619
|
description: import_zod68.z.string().optional().describe("Task description in Markdown"),
|
|
46198
46620
|
priority: import_zod68.z.enum(["low", "medium", "high"]).optional().describe("Priority level"),
|
|
46199
|
-
status: import_zod68.z.enum(["pending", "in_progress", "review", "failed", "interrupted", "completed", "cancelled"]).optional().describe("Task status. For status-only changes, prefer set_status action. Set 'in_progress'
|
|
46621
|
+
status: import_zod68.z.enum(["pending", "in_progress", "review", "failed", "interrupted", "completed", "cancelled"]).optional().describe("Task status. For status-only changes, prefer set_status action. Set 'in_progress' only for a task THIS run executes (a delegated task is claimed by `task` dispatch); 'completed'/'failed' require the task to be 'in_progress'. Agent summary-only interrupted updates default to missing_input"),
|
|
46200
46622
|
dueDate: import_zod68.z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
46201
46623
|
metadata: import_zod68.z.record(import_zod68.z.unknown()).optional().describe("Structured metadata (e.g. projectId, module)"),
|
|
46202
46624
|
metadataFilter: import_zod68.z.record(
|
|
@@ -47787,8 +48209,8 @@ ${TASK_CONVERGENCE_GUIDANCE}
|
|
|
47787
48209
|
- update: Update a task's content fields (title/description/priority/dueDate/metadata/parentId/sourceId/context/ownerId/result/failureReason/requireReview/dependencies/files)
|
|
47788
48210
|
Status changes can also be done here, but use set_status for a simpler dedicated status update.
|
|
47789
48211
|
- set_status: Change a task's status (id and status required). Use this when you only need to change the status.
|
|
47790
|
-
Start
|
|
47791
|
-
To mark complete: set_status with status='completed', result='...' (the task must be in_progress)
|
|
48212
|
+
Start a task THIS run executes: set_status with status='in_progress' (the engine binds it to this run's thread). Do NOT set in_progress on a task you will delegate \u2014 leave it pending; \`task\` dispatch claims it.
|
|
48213
|
+
To mark complete: set_status with status='completed', result='...' (the task must be in_progress). Complete a task exactly once: if it is already completed, do not complete it again \u2014 use add_activity for extra notes.
|
|
47792
48214
|
To mark failed: set_status with status='failed', failureReason='...'
|
|
47793
48215
|
To pause for input: set_status with status='interrupted', summary='what is needed'
|
|
47794
48216
|
Valid status values: pending, in_progress, review, failed, interrupted, completed, cancelled.
|
|
@@ -47805,16 +48227,16 @@ Report existing keys directly; set 'isNew: true' to add a key the owner does not
|
|
|
47805
48227
|
track yet. Belief is evidence metadata: it never blocks a completion, and an
|
|
47806
48228
|
unmatched or malformed entry is recorded without changing the owner's table.
|
|
47807
48229
|
Create always persists a pending record \u2014 a supplied status is coerced and no
|
|
47808
|
-
execution is dispatched.
|
|
47809
|
-
|
|
47810
|
-
|
|
48230
|
+
execution is dispatched. Set \`in_progress\` yourself ONLY for a task THIS run
|
|
48231
|
+
executes; a task you delegate stays \`pending\` and is claimed automatically by
|
|
48232
|
+
\`task\` dispatch. Use \`task { taskId, agentId }\` to delegate a subtask
|
|
47811
48233
|
to an expert \u2014 never re-dispatch the task you are processing.`,
|
|
47812
48234
|
schema: manageTaskSchema
|
|
47813
48235
|
}
|
|
47814
48236
|
),
|
|
47815
48237
|
(0, import_langchain83.tool)(handleStartTask, {
|
|
47816
48238
|
name: "task",
|
|
47817
|
-
description: `Start a persisted TaskItem with any existing Agent in a fresh isolated thread. Use this to delegate work \u2014 dispatch a subtask you created to the agent that should execute it; never re-dispatch the task you are currently processing (give the expert a child task instead). Pass agentId "general-purpose" for the general-purpose worker \u2014 ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION} It has access to all the caller's tools, model, and middleware, so use it for general-purpose subtasks, to isolate context and token usage, and whenever no specialized agent fits. The target does NOT need task-management tools: it runs and returns an observation
|
|
48239
|
+
description: `Start a persisted TaskItem with any existing Agent in a fresh isolated thread. Use this to delegate work \u2014 dispatch a subtask you created to the agent that should execute it; never re-dispatch the task you are currently processing (give the expert a child task instead). Pass agentId "general-purpose" for the general-purpose worker \u2014 ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION} It has access to all the caller's tools, model, and middleware, so use it for general-purpose subtasks, to isolate context and token usage, and whenever no specialized agent fits. The target does NOT need task-management tools: it runs and returns an observation, an executionResultId, and the task's taskStatus (re-read after the worker settled). Check taskStatus before completing: if it is already 'completed' the worker finished it \u2014 do NOT complete again (use manage_task add_activity); if it is still 'in_progress' you are the single completer and pass the executionResultId to manage_task set_status completed; if it is 'failed' or 'interrupted', act on that. The caller reconciles via manage_task, which interprets the result and records the outcome. A project task instead returns a managed result (success:true, managed:true) because the project-lifecycle queue owns execution.`,
|
|
47818
48240
|
schema: startTaskSchema
|
|
47819
48241
|
})
|
|
47820
48242
|
]
|
|
@@ -49512,7 +49934,7 @@ A plugin is the SET of classes it needs, e.g. \`connection + tools + skills\` or
|
|
|
49512
49934
|
| Tool shows an interactive panel | \`sandbox-plugins-ui\` |
|
|
49513
49935
|
| Tool orchestrates platform tools/agents | \`sandbox-plugins-consume-open\` |
|
|
49514
49936
|
| Tool callable by external MCP clients | \`sandbox-plugins-expose\` |
|
|
49515
|
-
| Run as a long-lived resident service | \`sandbox-plugins-deploy\` |
|
|
49937
|
+
| Run as a long-lived resident service (only on the human's explicit request) | \`sandbox-plugins-deploy\` |
|
|
49516
49938
|
| Validate / test / debug | \`sandbox-plugins-testing\` |
|
|
49517
49939
|
|
|
49518
49940
|
## Agents (not a supported authoring path)
|
|
@@ -49554,8 +49976,8 @@ chart, table, viewer\u2026). It can also call the plugin's OWN tools back from t
|
|
|
49554
49976
|
- It is NOT the tool's JSON \`schema\` (that is the input contract for the model).
|
|
49555
49977
|
- It is NOT the tool \`description\`.
|
|
49556
49978
|
- It is NOT required: a text/JSON-returning tool needs no UI.
|
|
49557
|
-
- Saying "add a UI" means: add a \`ui\` field to the tool in \`plugin.json\`
|
|
49558
|
-
\`ui/<tool>.html
|
|
49979
|
+
- Saying "add a UI" means: add a \`ui\` field to the tool in \`plugin.json\` and write
|
|
49980
|
+
\`ui/<tool>.html\` (with \`write_file\`). Details in \`sandbox-plugins-ui\`.
|
|
49559
49981
|
|
|
49560
49982
|
## Decide first \u2014 clarify these with the user before writing files
|
|
49561
49983
|
Ask for anything not already stated; do not guess silently:
|
|
@@ -49570,7 +49992,7 @@ Ask for anything not already stated; do not guess silently:
|
|
|
49570
49992
|
multi-select? connection test needed? \u2192 read \`sandbox-plugins-connection\`.
|
|
49571
49993
|
- **UI**: should the tool show an interactive panel instead of only text? what
|
|
49572
49994
|
does it show, how do users interact, external origins? \u2192 read
|
|
49573
|
-
|
|
49995
|
+
\`sandbox-plugins-ui\` and add \`ui\` + \`ui/<tool>.html\` (step 6 below). If the
|
|
49574
49996
|
user says no UI, skip it \u2014 text-only is fine.
|
|
49575
49997
|
- **Skills**: procedure or knowledge? who consumes it? trigger phrase? tied to a
|
|
49576
49998
|
specific tool?
|
|
@@ -49603,13 +50025,34 @@ Ask for anything not already stated; do not guess silently:
|
|
|
49603
50025
|
\`\`\`
|
|
49604
50026
|
|
|
49605
50027
|
## Flow
|
|
49606
|
-
1.
|
|
49607
|
-
|
|
49608
|
-
|
|
49609
|
-
|
|
49610
|
-
|
|
50028
|
+
1. **Design first, and iterate \u2014 then get explicit approval before you build (non-trivial plugins).**
|
|
50029
|
+
Before writing any file, produce the design and review it with the human. It has two parts,
|
|
50030
|
+
with different treatments:
|
|
50031
|
+
- **Functional design** (always): an **interactive** component map of what you intend to
|
|
50032
|
+
build \u2014 the tools, their inputs/outputs, whether a connection is needed, whether a UI
|
|
50033
|
+
is needed, and how the agent calls them. Make it click-to-explore (e.g. click a tool to
|
|
50034
|
+
reveal its handler / IO / connection); it is a map to **confirm**, not to annotate.
|
|
50035
|
+
- **UI design** (only when the UI class applies): an **annotatable** mock of the tool's UI \u2014
|
|
50036
|
+
the human right-clicks elements to leave notes (see \`sandbox-plugins-ui\` \u2192
|
|
50037
|
+
"Reviewable drafts").
|
|
50038
|
+
Show both drafts with the \`widget\` middleware, then run the review loop:
|
|
50039
|
+
- Ask the human to review (say how to comment). On each round of comments, **restate the
|
|
50040
|
+
feedback** (element + requested change), then **regenerate the draft with the feedback and
|
|
50041
|
+
present it again** \u2014 comments are "revise and re-show", never "start building".
|
|
50042
|
+
- **Do NOT scaffold or write any file during the loop.** Implement only after the human
|
|
50043
|
+
**explicitly approves**, and ask for that go-ahead yourself before you start.
|
|
50044
|
+
Skip the drafts only for a trivial single-tool plugin whose shape is already agreed \u2014 but
|
|
50045
|
+
still confirm the goal.
|
|
50046
|
+
2. Scaffold with \`manage_plugin { action: "create", type, name }\` \u2014 creates
|
|
50047
|
+
\`/root/.agents/plugins/<type>/\` with a stub \`plugin.json\`. It carries no content.
|
|
50048
|
+
3. Read \`sandbox-plugins-manifest\` and write \`plugin.json\` with \`write_file\`.
|
|
50049
|
+
4. Read \`sandbox-plugins-handlers\` and write each handler with \`write_file\`.
|
|
50050
|
+
5. If the plugin needs credentials: read \`sandbox-plugins-connection\`.
|
|
50051
|
+
6. **Only if a tool needs a UI** (the UI class from \`sandbox-plugins-patterns\`): read \`sandbox-plugins-ui\`, add \`ui\` to that tool in \`plugin.json\`, and write \`ui/<tool>.html\` with \`write_file\`.
|
|
50052
|
+
7. If a tool must be callable by external MCP clients: read \`sandbox-plugins-expose\`
|
|
49611
50053
|
and add \`expose: true\` (plus \`readOnly\`/\`destructive\` accuracy hints).
|
|
49612
|
-
|
|
50054
|
+
8. Read \`sandbox-plugins-testing\` and iterate: edit files with \`edit_file\`, then
|
|
50055
|
+
\`validate\` \u2192 \`reload\` \u2192 \`test\`.
|
|
49613
50056
|
|
|
49614
50057
|
Templates: \`templates/plugin.json\`, \`templates/handler.js\`, \`templates/test.js\`, \`templates/discover.js\`, \`templates/ui.html\`.
|
|
49615
50058
|
`,
|
|
@@ -49671,6 +50114,20 @@ description: Add an interactive HTML UI to a tenant plugin tool (rendered as a s
|
|
|
49671
50114
|
A tool can declare an HTML file that Axiom renders as a sandboxed MCP App when
|
|
49672
50115
|
the tool runs. The app can call the plugin's OWN tools back.
|
|
49673
50116
|
|
|
50117
|
+
## Reviewable drafts (before you build the real UI)
|
|
50118
|
+
When you show a **UI mock with \`show_widget\`** for the user to react to (the design
|
|
50119
|
+
step, before writing \`ui/<tool>.html\`), make it **annotatable**: the user right-clicks
|
|
50120
|
+
any element to leave a comment that comes back to you as a message. Turn it on by
|
|
50121
|
+
default for review drafts and include a small visible hint (e.g. "\u53F3\u952E\u4EFB\u610F\u5143\u7D20\u6279\u6CE8");
|
|
50122
|
+
the \`widget\` mockup guidelines have the pattern. Omit it only when the draft is meant
|
|
50123
|
+
to be purely experienced (a live demo with no review) \u2014 then leave out the hint and
|
|
50124
|
+
the handlers. This is only for the **mock**: the real \`ui/<tool>.html\` is the plugin's
|
|
50125
|
+
product and is never annotatable.
|
|
50126
|
+
A round of annotations is **NOT approval**: restate the comments you received, regenerate
|
|
50127
|
+
the mock from them, and show it again, iterating until the human is satisfied. Build
|
|
50128
|
+
\`ui/<tool>.html\` only after the human **explicitly approves** \u2014 and ask for that go-ahead
|
|
50129
|
+
yourself.
|
|
50130
|
+
|
|
49674
50131
|
## Declare it
|
|
49675
50132
|
\`\`\`
|
|
49676
50133
|
/root/.agents/plugins/<type>/
|
|
@@ -49703,11 +50160,10 @@ the tool runs. The app can call the plugin's OWN tools back.
|
|
|
49703
50160
|
- Omit \`csp\` for the restrictive default (\`default-src 'none'\`). WebAssembly is allowed by default.
|
|
49704
50161
|
|
|
49705
50162
|
## How to write the HTML file
|
|
49706
|
-
|
|
49707
|
-
|
|
49708
|
-
|
|
49709
|
-
|
|
49710
|
-
- Or write the file directly in the sandbox under \`ui/\` and call \`manage_plugin reload\`.
|
|
50163
|
+
Write the HTML to \`/root/.agents/plugins/<type>/ui/<tool>.html\` with \`write_file\`,
|
|
50164
|
+
then call \`manage_plugin { action: "reload", type }\` so the host serves the new HTML.
|
|
50165
|
+
The file must be declared by a tool's \`ui.resource\` in \`plugin.json\`, otherwise it is
|
|
50166
|
+
treated as missing (\`TOOL_UI\`).
|
|
49711
50167
|
|
|
49712
50168
|
## How it behaves at runtime
|
|
49713
50169
|
- The tool result carries a hidden \`mcp_app\` marker; Axiom renders \`ui/<tool>.html\` inline. \`displayMode\` is recorded but NOT yet honored \u2014 fullscreen happens only when the app itself calls \`ui/request-display-mode\`.
|
|
@@ -49803,7 +50259,7 @@ grants apply to the UI too). See \`sandbox-plugins-expose\`.
|
|
|
49803
50259
|
## Gotchas
|
|
49804
50260
|
- \`ui.resource\` must be a safe relative path ending in \`.html\` that EXISTS; otherwise the UI is dropped (diagnostic \`TOOL_UI\`) but the tool still loads.
|
|
49805
50261
|
- \`displayMode\`, \`permissions\`, and \`prefersBorder\` are accepted but NOT yet applied.
|
|
49806
|
-
- After editing the HTML, call \`manage_plugin reload
|
|
50262
|
+
- After editing the HTML, call \`manage_plugin { action: "reload", type }\`; the host caches the resource, so a reload is what makes it re-read the new file.
|
|
49807
50263
|
|
|
49808
50264
|
Start from \`templates/ui.html\`.
|
|
49809
50265
|
`,
|
|
@@ -50067,21 +50523,23 @@ description: Validate, test, and reload a tenant sandbox plugin using the manage
|
|
|
50067
50523
|
|
|
50068
50524
|
Use the \`manage_plugin\` tool. The loop:
|
|
50069
50525
|
|
|
50070
|
-
1.
|
|
50071
|
-
|
|
50526
|
+
1. Write/edit the plugin files with the filesystem tools (\`write_file\`/\`edit_file\`) under
|
|
50527
|
+
\`/root/.agents/plugins/<type>/\`; \`create\` only scaffolds the directory + stub.
|
|
50528
|
+
2. \`action: "validate"\` with \`{ type }\` \u2014 reads the plugin back **from the sandbox** and returns diagnostics (no writes). The files must already be on disk; a \`type\` with no files reports a read error.
|
|
50072
50529
|
3. If the plugin has \`connection.fields\` and a tool reads credentials: a **human**
|
|
50073
50530
|
must configure a connection of this plugin type. You cannot create credentials.
|
|
50074
50531
|
Call \`list_connections { type }\` first \u2014 if none exist, use \`ask_user_to_clarify\`
|
|
50075
50532
|
to state explicitly that only the human can configure the connector, ask them to
|
|
50076
50533
|
add it (Console \u2192 Connections) and to give you the connection key. Do not claim
|
|
50077
50534
|
the tool works until a real \`connectionKey\` test passes.
|
|
50078
|
-
4. \`action: "
|
|
50535
|
+
4. \`action: "reload"\` \u2014 refresh the compiled catalog after writing/editing files (the catalog caches; an edited file is invisible to \`test\` until you reload).
|
|
50536
|
+
5. \`action: "test"\` to exercise a handler without an LLM:
|
|
50079
50537
|
- Tool: \`{ type, target: { kind: "tool", name, input, connectionKey? } }\`
|
|
50080
50538
|
- Connection: \`{ type, target: { kind: "connection-test" } }\` or \`{ kind: "connection-discover" }\`
|
|
50081
50539
|
Returns \`{ ok, result, exitCode, durationMs, stdout, stderr }\`.
|
|
50082
50540
|
For credentialed tools, pass the real \`connectionKey\`. Without one, the handler
|
|
50083
50541
|
receives an empty connection \u2014 that only checks plumbing, NOT functional success.
|
|
50084
|
-
|
|
50542
|
+
6. \`action: "backup"\` / \`"restore"\` \u2014 manual snapshot and rollback. \`backup\` copies the plugin into \`.backup/\` **inside its own directory** (keeps only the latest), and \`restore\` makes the plugin match that snapshot (adds back missing files, removes files added since). Use it before a risky rewrite.
|
|
50085
50543
|
|
|
50086
50544
|
## Secrets
|
|
50087
50545
|
- \`list_connections\` returns identifiers only (\`key\`/\`name\`/\`description\`), never config or secrets.
|
|
@@ -50096,10 +50554,10 @@ Use the \`manage_plugin\` tool. The loop:
|
|
|
50096
50554
|
| HANDLER_MISSING | handler path wrong or file not written |
|
|
50097
50555
|
| TOOL_SCHEMA | unsupported JSON Schema keyword |
|
|
50098
50556
|
| non-zero exit | handler threw; read stdout/stderr from \`test\` |
|
|
50099
|
-
| old behaviour after edit |
|
|
50557
|
+
| old behaviour after edit | the catalog caches by content hash/TTL \u2014 call \`reload\` |
|
|
50100
50558
|
| empty connection in handler | no \`connectionKey\` passed, or no connection configured for this type |
|
|
50101
50559
|
|
|
50102
|
-
Fall back to \`
|
|
50560
|
+
Fall back to \`shell_exec\` to run \`node handlers/x.js\` manually with
|
|
50103
50561
|
\`PLUGIN_INPUT\` and a synthetic \`PLUGIN_CONNECTION_FILE\` to reproduce logic bugs.
|
|
50104
50562
|
A synthetic fixture only verifies plumbing \u2014 a real upstream call requires a real
|
|
50105
50563
|
\`connectionKey\` configured by a human.
|
|
@@ -50110,7 +50568,7 @@ A synthetic fixture only verifies plumbing \u2014 a real upstream call requires
|
|
|
50110
50568
|
version: "1.0.0",
|
|
50111
50569
|
content: `---
|
|
50112
50570
|
name: sandbox-plugins-deploy
|
|
50113
|
-
description: Deploy a validated tenant plugin as a resident service (alwaysOn vs onDemand), check its status, and undeploy it. Trigger:
|
|
50571
|
+
description: Deploy a validated tenant plugin as a resident service (alwaysOn vs onDemand), check its status, and undeploy it. Trigger: the human explicitly asks to run a plugin as a long-lived resident service (or to inspect/remove one) \u2014 deployment is human-gated, never automatic.
|
|
50114
50572
|
---
|
|
50115
50573
|
|
|
50116
50574
|
# Deploying a Plugin as a Resident Service
|
|
@@ -50120,10 +50578,13 @@ Optionally, you can **deploy** the plugin as a **resident service**: one
|
|
|
50120
50578
|
long-lived sandbox per plugin that serves every call. This is faster for
|
|
50121
50579
|
frequent or heavy calls, but costs a running sandbox.
|
|
50122
50580
|
|
|
50123
|
-
## Precondition: tests must pass first
|
|
50124
|
-
|
|
50125
|
-
|
|
50126
|
-
|
|
50581
|
+
## Precondition: human request, then tests must pass first
|
|
50582
|
+
Deployment is **opt-in and human-gated**: NEVER deploy on your own after tests
|
|
50583
|
+
pass. Wait until the human explicitly asks for it or approves it. A passing test
|
|
50584
|
+
is necessary but NOT sufficient to deploy.
|
|
50585
|
+
Then confirm the plugin is validated AND its tests pass:
|
|
50586
|
+
1. Files written with the filesystem tools and \`reload\` run.
|
|
50587
|
+
2. \`manage_plugin validate\` \u2014 no diagnostics.
|
|
50127
50588
|
3. \`manage_plugin test\` \u2014 every tool/connection test returns \`ok: true\`
|
|
50128
50589
|
(a real \`connectionKey\` test for credentialed tools).
|
|
50129
50590
|
Never deploy a plugin you have not tested: a resident service wraps the same
|
|
@@ -50191,6 +50652,30 @@ load "sandbox-plugins-expose" and follow it (\`expose: true\` + key grants).
|
|
|
50191
50652
|
If a tool must call the platform's own tools/agents from its handler, load
|
|
50192
50653
|
"sandbox-plugins-consume-open".
|
|
50193
50654
|
|
|
50655
|
+
Design first, and iterate on it before you build \u2014 the design is a conversation, not
|
|
50656
|
+
a one-shot. Use the \`widget\` middleware to present it (a decision surface, not a showcase):
|
|
50657
|
+
- **Functional design** (always): an **interactive** component map of the shape you
|
|
50658
|
+
intend to build \u2014 which tools, their inputs/outputs, whether a connection or a UI
|
|
50659
|
+
is needed, how the agent calls them. Make it click-to-explore (and prefer it over
|
|
50660
|
+
an SVG flowchart); it is a map to **confirm**, NOT an annotatable draft.
|
|
50661
|
+
- **UI design** (UI class only): an **annotatable** mock of the tool's UI \u2014 the human
|
|
50662
|
+
right-clicks elements to leave notes.
|
|
50663
|
+
Then run a review loop:
|
|
50664
|
+
- Ask the human to review (tell them how to comment). When they send comments, **restate the
|
|
50665
|
+
feedback** (which element, what change) so they can confirm you understood, then **build a
|
|
50666
|
+
NEW draft that incorporates the feedback and present it again**; keep iterating until
|
|
50667
|
+
they are satisfied. Comments mean "revise and re-show", NOT "start building".
|
|
50668
|
+
- Do NOT scaffold or write any file during the loop. Implement only once the human
|
|
50669
|
+
**explicitly approves** the design \u2014 and still ASK for that go-ahead yourself; never
|
|
50670
|
+
start building on your own initiative.
|
|
50671
|
+
Also surface genuinely different options (scope, layout, which tools) visually and let
|
|
50672
|
+
the human choose, instead of describing them in prose.
|
|
50673
|
+
|
|
50674
|
+
How to render a good widget is the widget middleware's own concern \u2014 follow it
|
|
50675
|
+
there; keep this prompt to plugin-design intent. These widgets are design aids
|
|
50676
|
+
only; the plugin's actual UI is still the MCP App HTML you author and test with
|
|
50677
|
+
\`manage_plugin\`.
|
|
50678
|
+
|
|
50194
50679
|
Plan with persistent Tasks: MANDATORY until you load "task-tracking" with the
|
|
50195
50680
|
\`skill\` tool, call neither \`manage_task\` nor any other task operation. Use
|
|
50196
50681
|
persistent Tasks as the development planning surface and create one Task per
|
|
@@ -50198,7 +50683,16 @@ independently verifiable outcome (for example "manifest written", "handlers
|
|
|
50198
50683
|
implemented", "UI added", "validated/tested via manage_plugin"). As you work,
|
|
50199
50684
|
update each Task's status to reflect reality. Never use \`write_todos\`.
|
|
50200
50685
|
|
|
50201
|
-
|
|
50686
|
+
After the human confirms the design, author the plugin files directly with the filesystem tools, then verify with \`manage_plugin\`.
|
|
50687
|
+
1. Scaffold once: \`manage_plugin { action: "create", type: "<type>", name }\` \u2014 it creates
|
|
50688
|
+
\`/root/.agents/plugins/<type>/\` with a stub \`plugin.json\`. It never carries content.
|
|
50689
|
+
2. Write and edit every file yourself with \`write_file\` / \`edit_file\` (read back with
|
|
50690
|
+
\`read_file\`) under \`/root/.agents/plugins/<type>/\`: \`plugin.json\`, \`handlers/*.js\`, and
|
|
50691
|
+
(UI class) \`ui/*.html\`. Change one file at a time; edit in place instead of rewriting.
|
|
50692
|
+
3. Verify in order: \`manage_plugin { action: "validate", type }\` (reads the files back from
|
|
50693
|
+
the sandbox) \u2192 \`manage_plugin { action: "reload", type }\` (the catalog caches, so a
|
|
50694
|
+
freshly written file is invisible to \`test\` until you reload) \u2192 \`manage_plugin
|
|
50695
|
+
{ action: "test", type, target }\`.
|
|
50202
50696
|
If a tool needs credentials, a human must configure the connection \u2014 use
|
|
50203
50697
|
\`list_connections\` to check, and \`ask_user_to_clarify\` to ask the human to add
|
|
50204
50698
|
the connector and give you its key. Only after a real \`connectionKey\` test
|
|
@@ -50207,14 +50701,15 @@ unverified (missing credentials), not as passing. Use \`shell_exec\` (the tool
|
|
|
50207
50701
|
provided by the \`code_eval\` middleware) to run \`node handlers/x.js\` for manual
|
|
50208
50702
|
checks when reproducing a failure.
|
|
50209
50703
|
|
|
50210
|
-
|
|
50211
|
-
|
|
50704
|
+
Deployment is human-gated: do NOT deploy on your own after the tests pass.
|
|
50705
|
+
Only deploy when the human explicitly asks for it or approves it, then load
|
|
50706
|
+
"sandbox-plugins-deploy" and follow it. Choose \`onDemand\`
|
|
50212
50707
|
(default) or \`alwaysOn\` for hot paths, call \`manage_plugin deploy\` with the
|
|
50213
50708
|
chosen \`mode\`, then confirm with \`manage_plugin status\` (\`running\`). Only claim
|
|
50214
50709
|
the plugin is deployed and serving once \`status\` reports \`running\`; if the
|
|
50215
50710
|
environment reports no deployment service, say so and treat deployment as
|
|
50216
|
-
unavailable \u2014 do NOT claim success.
|
|
50217
|
-
touching the plugin files.`;
|
|
50711
|
+
unavailable \u2014 do NOT claim success. Report the plugin as ready and let the human
|
|
50712
|
+
decide; \`undeploy\` removes the service without touching the plugin files.`;
|
|
50218
50713
|
|
|
50219
50714
|
// src/middlewares/sandboxPluginsUi.ts
|
|
50220
50715
|
var SANDBOX_PLUGINS_UI_RESOURCE = "ui://sandbox-plugins/app";
|
|
@@ -50390,6 +50885,7 @@ var SANDBOX_PLUGINS_APP_HTML = `<!doctype html>
|
|
|
50390
50885
|
`;
|
|
50391
50886
|
|
|
50392
50887
|
// src/middlewares/sandboxPluginsMiddleware.ts
|
|
50888
|
+
var PLUGINS_ROOT3 = "/root/.agents/plugins";
|
|
50393
50889
|
var SANDBOX_PLUGINS_UI_REF = {
|
|
50394
50890
|
kind: "plugin",
|
|
50395
50891
|
pluginType: "sandbox-plugins",
|
|
@@ -50446,18 +50942,19 @@ var managePluginSchema = import_zod71.z.object({
|
|
|
50446
50942
|
"get",
|
|
50447
50943
|
"validate",
|
|
50448
50944
|
"create",
|
|
50449
|
-
"update",
|
|
50450
50945
|
"delete",
|
|
50451
50946
|
"reload",
|
|
50452
50947
|
"test",
|
|
50948
|
+
"backup",
|
|
50949
|
+
"restore",
|
|
50453
50950
|
"deploy",
|
|
50454
50951
|
"undeploy",
|
|
50455
50952
|
"status",
|
|
50456
50953
|
"deployments"
|
|
50457
50954
|
]),
|
|
50458
50955
|
type: import_zod71.z.string().optional(),
|
|
50459
|
-
|
|
50460
|
-
|
|
50956
|
+
name: import_zod71.z.string().optional(),
|
|
50957
|
+
description: import_zod71.z.string().optional(),
|
|
50461
50958
|
mode: import_zod71.z.enum(["onDemand", "alwaysOn"]).optional(),
|
|
50462
50959
|
target: import_zod71.z.object({
|
|
50463
50960
|
kind: import_zod71.z.enum(["tool", "connection-test", "connection-discover"]),
|
|
@@ -50471,12 +50968,12 @@ var sandboxPluginsPlugin = {
|
|
|
50471
50968
|
type: "sandbox-plugins",
|
|
50472
50969
|
category: "execution",
|
|
50473
50970
|
name: "Sandbox Plugins",
|
|
50474
|
-
description: "Author and manage tenant plugins stored in the sandbox: list/validate/create/
|
|
50971
|
+
description: "Author and manage tenant plugins stored in the sandbox: list/validate/create/delete/reload/test/backup/restore, and deploy/undeploy/status/deployments to run a plugin as a resident service. Tools are agent-callable JS handlers and may declare an optional interactive HTML UI (rendered as a sandboxed MCP App). Pairs with the Skill plugin for authoring guidance.",
|
|
50475
50972
|
recommends: ["skill"],
|
|
50476
50973
|
tools: [
|
|
50477
50974
|
{
|
|
50478
50975
|
name: "manage_plugin",
|
|
50479
|
-
description: "Author and manage tenant sandbox plugins (tools, optional per-tool HTML UI, connections, Open/MCP exposure, and resident deployment). Actions: list/get/validate/create/
|
|
50976
|
+
description: "Author and manage tenant sandbox plugins (tools, optional per-tool HTML UI, connections, Open/MCP exposure, and resident deployment). Actions: list/get/validate/create/delete/reload/test/backup/restore, deploy/undeploy/status, and `deployments` to list every resident deployment for the tenant.",
|
|
50480
50977
|
ui: { resource: SANDBOX_PLUGINS_UI_RESOURCE, displayMode: "inline" }
|
|
50481
50978
|
}
|
|
50482
50979
|
],
|
|
@@ -50503,7 +51000,7 @@ var sandboxPluginsPlugin = {
|
|
|
50503
51000
|
id: "sandbox-plugins",
|
|
50504
51001
|
type: "sandbox-plugins",
|
|
50505
51002
|
name: "Sandbox Plugins",
|
|
50506
|
-
description: "Author and manage tenant plugins (list/validate/create/
|
|
51003
|
+
description: "Author and manage tenant plugins (list/validate/create/delete/reload/test/backup/restore/deploy/undeploy/status)",
|
|
50507
51004
|
enabled: true,
|
|
50508
51005
|
config: {}
|
|
50509
51006
|
},
|
|
@@ -50523,6 +51020,14 @@ var sandboxPluginsPlugin = {
|
|
|
50523
51020
|
enabled: true,
|
|
50524
51021
|
config: {}
|
|
50525
51022
|
},
|
|
51023
|
+
{
|
|
51024
|
+
id: "widget",
|
|
51025
|
+
type: "widget",
|
|
51026
|
+
name: "Widget",
|
|
51027
|
+
description: "Align on the plugin design before building it: show the shape you intend to build (tools, IO, connections, UI) and a UI mock for the human to correct scope before files exist.",
|
|
51028
|
+
enabled: true,
|
|
51029
|
+
config: {}
|
|
51030
|
+
},
|
|
50526
51031
|
{
|
|
50527
51032
|
id: "ask_user_to_clarify",
|
|
50528
51033
|
type: "ask_user_to_clarify",
|
|
@@ -50563,6 +51068,7 @@ var sandboxPluginsPlugin = {
|
|
|
50563
51068
|
return JSON.stringify({
|
|
50564
51069
|
plugins: loaded.plugins.map((p) => ({
|
|
50565
51070
|
type: p.meta.type,
|
|
51071
|
+
path: `${PLUGINS_ROOT3}/${p.meta.type}`,
|
|
50566
51072
|
name: p.meta.name,
|
|
50567
51073
|
tools: p.meta.tools?.map((t) => t.name) ?? [],
|
|
50568
51074
|
source: p.meta.source
|
|
@@ -50573,32 +51079,87 @@ var sandboxPluginsPlugin = {
|
|
|
50573
51079
|
}
|
|
50574
51080
|
case "get": {
|
|
50575
51081
|
if (!input.type) return "type is required";
|
|
50576
|
-
|
|
51082
|
+
const content = await catalog2.readPluginFile(tenantId2, input.type, "plugin.json");
|
|
51083
|
+
let manifest = content;
|
|
51084
|
+
try {
|
|
51085
|
+
manifest = JSON.parse(content);
|
|
51086
|
+
} catch {
|
|
51087
|
+
}
|
|
51088
|
+
return JSON.stringify({
|
|
51089
|
+
type: input.type,
|
|
51090
|
+
path: `${PLUGINS_ROOT3}/${input.type}`,
|
|
51091
|
+
manifest
|
|
51092
|
+
});
|
|
50577
51093
|
}
|
|
50578
51094
|
case "reload": {
|
|
50579
51095
|
const loaded = await catalog2.reload(tenantId2);
|
|
50580
51096
|
void publishPluginCatalogChanged(tenantId2);
|
|
50581
51097
|
return JSON.stringify({ reloaded: true, plugins: loaded.plugins.length, diagnostics: loaded.diagnostics });
|
|
50582
51098
|
}
|
|
50583
|
-
case "
|
|
50584
|
-
|
|
50585
|
-
|
|
50586
|
-
|
|
51099
|
+
case "backup": {
|
|
51100
|
+
if (!input.type) return "type is required";
|
|
51101
|
+
try {
|
|
51102
|
+
const result = await catalog2.backupPlugin(tenantId2, input.type);
|
|
51103
|
+
if (result.files === 0) {
|
|
51104
|
+
return JSON.stringify({ backedUp: false, error: "nothing to back up (plugin has no files)" });
|
|
51105
|
+
}
|
|
51106
|
+
return JSON.stringify({ backedUp: true, files: result.files });
|
|
51107
|
+
} catch (err) {
|
|
51108
|
+
return JSON.stringify({ backedUp: false, error: errorText(err) });
|
|
51109
|
+
}
|
|
51110
|
+
}
|
|
51111
|
+
case "restore": {
|
|
51112
|
+
if (!input.type) return "type is required";
|
|
51113
|
+
let restored;
|
|
51114
|
+
let failure3;
|
|
51115
|
+
try {
|
|
51116
|
+
restored = await catalog2.restorePlugin(tenantId2, input.type);
|
|
51117
|
+
} catch (err) {
|
|
51118
|
+
failure3 = errorText(err);
|
|
51119
|
+
}
|
|
51120
|
+
try {
|
|
51121
|
+
await catalog2.reload(tenantId2);
|
|
51122
|
+
void publishPluginCatalogChanged(tenantId2);
|
|
51123
|
+
} catch {
|
|
51124
|
+
}
|
|
51125
|
+
if (failure3) return JSON.stringify({ restored: false, error: failure3 });
|
|
51126
|
+
return JSON.stringify({ restored: true, files: restored?.files ?? 0 });
|
|
51127
|
+
}
|
|
51128
|
+
case "create": {
|
|
51129
|
+
if (!input.type) return "type is required";
|
|
50587
51130
|
const known = new Set(
|
|
50588
51131
|
(catalog2.getCached(tenantId2)?.plugins ?? []).map((p) => p.meta.type).filter((t) => t !== input.type)
|
|
50589
51132
|
);
|
|
51133
|
+
const stub = {
|
|
51134
|
+
schemaVersion: 1,
|
|
51135
|
+
type: input.type,
|
|
51136
|
+
name: input.name ?? input.type,
|
|
51137
|
+
description: input.description ?? "",
|
|
51138
|
+
version: "0.1.0",
|
|
51139
|
+
tools: []
|
|
51140
|
+
};
|
|
50590
51141
|
const check = validateSandboxPluginManifest({
|
|
50591
|
-
raw:
|
|
51142
|
+
raw: stub,
|
|
50592
51143
|
dirName: input.type,
|
|
50593
51144
|
reservedTypes: new Set(PluginRegistry.list()),
|
|
50594
51145
|
knownTypes: known,
|
|
50595
|
-
handlerExists: (
|
|
51146
|
+
handlerExists: () => true
|
|
50596
51147
|
});
|
|
50597
|
-
if (!check.manifest)
|
|
50598
|
-
|
|
51148
|
+
if (!check.manifest) {
|
|
51149
|
+
return JSON.stringify({ created: false, diagnostics: check.diagnostics });
|
|
51150
|
+
}
|
|
51151
|
+
const files = await catalog2.listPluginFiles(tenantId2, input.type);
|
|
51152
|
+
if (files.includes("plugin.json")) {
|
|
51153
|
+
return JSON.stringify({ created: false, error: "plugin already exists" });
|
|
51154
|
+
}
|
|
51155
|
+
await catalog2.writePlugin(tenantId2, input.type, check.manifest, {});
|
|
50599
51156
|
const loaded = await catalog2.reload(tenantId2);
|
|
50600
51157
|
void publishPluginCatalogChanged(tenantId2);
|
|
50601
|
-
return JSON.stringify({
|
|
51158
|
+
return JSON.stringify({
|
|
51159
|
+
created: true,
|
|
51160
|
+
path: `${PLUGINS_ROOT3}/${input.type}`,
|
|
51161
|
+
diagnostics: loaded.diagnostics
|
|
51162
|
+
});
|
|
50602
51163
|
}
|
|
50603
51164
|
case "delete": {
|
|
50604
51165
|
if (!input.type) return "type is required";
|
|
@@ -50609,18 +51170,8 @@ var sandboxPluginsPlugin = {
|
|
|
50609
51170
|
}
|
|
50610
51171
|
case "validate": {
|
|
50611
51172
|
if (!input.type) return "type is required";
|
|
50612
|
-
const
|
|
50613
|
-
|
|
50614
|
-
(catalog2.getCached(tenantId2)?.plugins ?? []).map((p) => p.meta.type).filter((t) => t !== input.type)
|
|
50615
|
-
);
|
|
50616
|
-
const result = validateSandboxPluginManifest({
|
|
50617
|
-
raw: input.manifest ?? {},
|
|
50618
|
-
dirName: input.type,
|
|
50619
|
-
reservedTypes: new Set(PluginRegistry.list()),
|
|
50620
|
-
knownTypes: known,
|
|
50621
|
-
handlerExists: (rel) => rel in handlers
|
|
50622
|
-
});
|
|
50623
|
-
return JSON.stringify({ diagnostics: result.diagnostics, valid: Boolean(result.manifest) });
|
|
51173
|
+
const result = await catalog2.validatePluginFromDisk(tenantId2, input.type);
|
|
51174
|
+
return JSON.stringify({ diagnostics: result.diagnostics, valid: result.valid });
|
|
50624
51175
|
}
|
|
50625
51176
|
case "test": {
|
|
50626
51177
|
if (!input.type || !input.target) return "type and target are required";
|
|
@@ -50694,7 +51245,16 @@ var sandboxPluginsPlugin = {
|
|
|
50694
51245
|
assistantId: typeof runCfg.assistant_id === "string" ? runCfg.assistant_id : void 0
|
|
50695
51246
|
}
|
|
50696
51247
|
});
|
|
50697
|
-
|
|
51248
|
+
const text = handlerResultText(redactSecretValues(result, secrets));
|
|
51249
|
+
const ui = toolDef.ui;
|
|
51250
|
+
if (!ui?.resource) return text;
|
|
51251
|
+
const uiRef = {
|
|
51252
|
+
kind: "plugin",
|
|
51253
|
+
pluginType: input.type,
|
|
51254
|
+
resource: ui.resource,
|
|
51255
|
+
displayMode: ui.displayMode ?? "inline"
|
|
51256
|
+
};
|
|
51257
|
+
return appendUiFence(text, uiRef);
|
|
50698
51258
|
} catch (err) {
|
|
50699
51259
|
return JSON.stringify(redactSecretValues({ ok: false, error: errorText(err) }, secrets));
|
|
50700
51260
|
}
|
|
@@ -50766,7 +51326,7 @@ var sandboxPluginsPlugin = {
|
|
|
50766
51326
|
},
|
|
50767
51327
|
{
|
|
50768
51328
|
name: "manage_plugin",
|
|
50769
|
-
description: "Author and manage tenant sandbox plugins (list/get/validate/create/
|
|
51329
|
+
description: "Author and manage tenant sandbox plugins (list/get/validate/create/delete/reload/test/backup/restore) and deploy/undeploy/status a plugin as a resident service.",
|
|
50770
51330
|
schema: managePluginSchema
|
|
50771
51331
|
}
|
|
50772
51332
|
)
|
|
@@ -51950,6 +52510,7 @@ registerBuiltinPlugins();
|
|
|
51950
52510
|
buildTableName,
|
|
51951
52511
|
buildTaskMutationFallback,
|
|
51952
52512
|
buildTaskNormalizationFallback,
|
|
52513
|
+
canConvergeOrphan,
|
|
51953
52514
|
checkEmptyContent,
|
|
51954
52515
|
clearEncryptionKeyCache,
|
|
51955
52516
|
clearEvalRunService,
|
|
@@ -51999,6 +52560,7 @@ registerBuiltinPlugins();
|
|
|
51999
52560
|
createTaskLifecycleService,
|
|
52000
52561
|
createTaskMiddleware,
|
|
52001
52562
|
createTaskRunBridge,
|
|
52563
|
+
createTaskRunLiveness,
|
|
52002
52564
|
createTeamMiddleware,
|
|
52003
52565
|
createTeammateTools,
|
|
52004
52566
|
createTrustedAgentMessageDispatcher,
|
|
@@ -52096,6 +52658,7 @@ registerBuiltinPlugins();
|
|
|
52096
52658
|
isMcpConnectionError,
|
|
52097
52659
|
isPluginSkillAuthorizedInSandbox,
|
|
52098
52660
|
isPluginSkillManagedInSandbox,
|
|
52661
|
+
isTaskRunLeaseHeld,
|
|
52099
52662
|
isUsingDefaultKey,
|
|
52100
52663
|
isValidCronExpression,
|
|
52101
52664
|
isValidSandboxName,
|