@axiom-lattice/core 7.0.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +152 -3
- package/dist/index.d.ts +152 -3
- package/dist/index.js +629 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +626 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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,
|
|
@@ -40404,7 +40618,11 @@ var TaskLifecycleService = class {
|
|
|
40404
40618
|
expectedStatuses: ["completed", "cancelled"],
|
|
40405
40619
|
updates: { status: "in_progress", failureReason: null, result: null },
|
|
40406
40620
|
summary: "Task reopened",
|
|
40407
|
-
detail: { type: "reopen" }
|
|
40621
|
+
detail: { type: "reopen" },
|
|
40622
|
+
// Preserve an existing thread by default (the project runtime reuses the
|
|
40623
|
+
// canonical Task Thread); a dispatch that allocated a fresh run thread
|
|
40624
|
+
// opts in to replacing it.
|
|
40625
|
+
replaceThread: input.replaceThread ?? false
|
|
40408
40626
|
});
|
|
40409
40627
|
}
|
|
40410
40628
|
/**
|
|
@@ -42265,6 +42483,39 @@ function createTaskRunBridge(deps) {
|
|
|
42265
42483
|
transitioned: true
|
|
42266
42484
|
};
|
|
42267
42485
|
}
|
|
42486
|
+
if (task.status === "completed" || task.status === "cancelled") {
|
|
42487
|
+
if (task.ownerType !== "agent") {
|
|
42488
|
+
return {
|
|
42489
|
+
success: false,
|
|
42490
|
+
code: "AGENT_TASK_REQUIRED",
|
|
42491
|
+
error: "Lifecycle transitions only support agent tasks."
|
|
42492
|
+
};
|
|
42493
|
+
}
|
|
42494
|
+
const reopened = await deps.lifecycle.reopenTask({
|
|
42495
|
+
tenantId: tenantId2,
|
|
42496
|
+
taskId: task.id,
|
|
42497
|
+
actor,
|
|
42498
|
+
...threadId !== void 0 ? { threadId } : {},
|
|
42499
|
+
// The caller allocated a fresh run thread for this dispatch.
|
|
42500
|
+
replaceThread: true
|
|
42501
|
+
});
|
|
42502
|
+
if (!reopened.success) {
|
|
42503
|
+
return {
|
|
42504
|
+
success: false,
|
|
42505
|
+
code: reopened.code,
|
|
42506
|
+
error: reopened.error,
|
|
42507
|
+
...reopened.hint !== void 0 ? { hint: reopened.hint } : {},
|
|
42508
|
+
...reopened.retryable !== void 0 ? { retryable: reopened.retryable } : {}
|
|
42509
|
+
};
|
|
42510
|
+
}
|
|
42511
|
+
return {
|
|
42512
|
+
success: true,
|
|
42513
|
+
task: reopened.task,
|
|
42514
|
+
warnings: reopened.warnings ?? [],
|
|
42515
|
+
mutated: true,
|
|
42516
|
+
transitioned: true
|
|
42517
|
+
};
|
|
42518
|
+
}
|
|
42268
42519
|
return { success: true, task, warnings, mutated: false, transitioned: false };
|
|
42269
42520
|
},
|
|
42270
42521
|
async settleRun(input) {
|
|
@@ -42437,6 +42688,45 @@ async function tryAcquireTaskRun(input) {
|
|
|
42437
42688
|
};
|
|
42438
42689
|
}
|
|
42439
42690
|
|
|
42691
|
+
// src/services/taskLiveness.ts
|
|
42692
|
+
function registeredLeaseStore() {
|
|
42693
|
+
try {
|
|
42694
|
+
return storeLatticeManager.getStoreLattice("default", "lease").store;
|
|
42695
|
+
} catch {
|
|
42696
|
+
return void 0;
|
|
42697
|
+
}
|
|
42698
|
+
}
|
|
42699
|
+
async function isTaskRunLeaseHeld(task) {
|
|
42700
|
+
const store = registeredLeaseStore();
|
|
42701
|
+
if (!store) return false;
|
|
42702
|
+
try {
|
|
42703
|
+
if (await store.get(taskRunLeaseKey(task.id))) return true;
|
|
42704
|
+
const threadId = typeof task.context?.thread_id === "string" ? task.context.thread_id : void 0;
|
|
42705
|
+
if (threadId && task.ownerId && await store.get(threadRunLeaseKey(task.tenantId, task.ownerId, threadId))) {
|
|
42706
|
+
return true;
|
|
42707
|
+
}
|
|
42708
|
+
return false;
|
|
42709
|
+
} catch {
|
|
42710
|
+
return true;
|
|
42711
|
+
}
|
|
42712
|
+
}
|
|
42713
|
+
function createTaskRunLiveness() {
|
|
42714
|
+
return async (task) => taskRunRegistry.isLive(task.id) || isTaskRunLeaseHeld(task);
|
|
42715
|
+
}
|
|
42716
|
+
function isMultiReplicaDeclared() {
|
|
42717
|
+
const id = process.env.AXIOM_REPLICA_ID;
|
|
42718
|
+
return typeof id === "string" && id.trim() !== "";
|
|
42719
|
+
}
|
|
42720
|
+
function isSharedLeaseStore() {
|
|
42721
|
+
const store = registeredLeaseStore();
|
|
42722
|
+
const name = store?.constructor?.name;
|
|
42723
|
+
return typeof name === "string" && !name.startsWith("InMemory");
|
|
42724
|
+
}
|
|
42725
|
+
function canConvergeOrphan() {
|
|
42726
|
+
if (!isMultiReplicaDeclared()) return true;
|
|
42727
|
+
return isSharedLeaseStore();
|
|
42728
|
+
}
|
|
42729
|
+
|
|
42440
42730
|
// src/services/ExecutionAuthorityRegistry.ts
|
|
42441
42731
|
var ManagedTaskStartError = class extends Error {
|
|
42442
42732
|
/**
|
|
@@ -42520,7 +42810,10 @@ async function prepareTaskDispatch(deps, input) {
|
|
|
42520
42810
|
}
|
|
42521
42811
|
};
|
|
42522
42812
|
}
|
|
42523
|
-
|
|
42813
|
+
const managedAuthority = executionAuthorityRegistry.resolve(task);
|
|
42814
|
+
const terminal = task.status === "completed" || task.status === "cancelled";
|
|
42815
|
+
const reopenableTerminal = terminal && task.ownerType === "agent" && !managedAuthority;
|
|
42816
|
+
if (terminal && !reopenableTerminal) {
|
|
42524
42817
|
return {
|
|
42525
42818
|
kind: "failure",
|
|
42526
42819
|
failure: {
|
|
@@ -42544,19 +42837,18 @@ async function prepareTaskDispatch(deps, input) {
|
|
|
42544
42837
|
}
|
|
42545
42838
|
};
|
|
42546
42839
|
}
|
|
42547
|
-
if (task.status !== "pending" && task.status !== "interrupted" && task.status !== "failed") {
|
|
42840
|
+
if (task.status !== "pending" && task.status !== "interrupted" && task.status !== "failed" && !reopenableTerminal) {
|
|
42548
42841
|
return {
|
|
42549
42842
|
kind: "failure",
|
|
42550
42843
|
failure: {
|
|
42551
42844
|
success: false,
|
|
42552
42845
|
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,
|
|
42846
|
+
error: "Only a `pending`, `interrupted`, `failed`, or reopenable terminal task can be dispatched.",
|
|
42847
|
+
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
42848
|
taskId: task.id
|
|
42556
42849
|
}
|
|
42557
42850
|
};
|
|
42558
42851
|
}
|
|
42559
|
-
const managedAuthority = executionAuthorityRegistry.resolve(task);
|
|
42560
42852
|
if (managedAuthority) {
|
|
42561
42853
|
if (managedAuthority.onManagedStart) {
|
|
42562
42854
|
try {
|
|
@@ -42726,7 +43018,8 @@ function persistedExecutionObservation(item, attempted) {
|
|
|
42726
43018
|
}
|
|
42727
43019
|
function buildWorkerMessage(task, parent, dependencies) {
|
|
42728
43020
|
const sections2 = [
|
|
42729
|
-
|
|
43021
|
+
`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.`,
|
|
43022
|
+
"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
43023
|
`Persisted Task ID: \`${task.id}\``,
|
|
42731
43024
|
`# Task: ${task.title}`,
|
|
42732
43025
|
task.description?.trim() || "No task description was provided."
|
|
@@ -42758,9 +43051,17 @@ function createTaskExecutionService(deps) {
|
|
|
42758
43051
|
taskStore: deps.taskStore,
|
|
42759
43052
|
workItemStore: deps.workItemStore
|
|
42760
43053
|
});
|
|
43054
|
+
const isTaskHeldByLiveRun = deps.isTaskHeldByLiveRun ?? createTaskRunLiveness();
|
|
42761
43055
|
const run = async (plan) => {
|
|
42762
43056
|
const { tenantId: tenantId2, agentId, actor, parent, completedDependencies } = plan;
|
|
42763
43057
|
let task = plan.task;
|
|
43058
|
+
const readTaskStatus = async () => {
|
|
43059
|
+
try {
|
|
43060
|
+
return (await deps.taskStore.getById(tenantId2, task.id))?.status;
|
|
43061
|
+
} catch {
|
|
43062
|
+
return void 0;
|
|
43063
|
+
}
|
|
43064
|
+
};
|
|
42764
43065
|
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
43066
|
const storedRunId = typeof task.context?.run_id === "string" && task.context.run_id.trim() !== "" ? task.context.run_id : void 0;
|
|
42766
43067
|
let reuseCurrentRun = false;
|
|
@@ -42819,7 +43120,7 @@ function createTaskExecutionService(deps) {
|
|
|
42819
43120
|
task = transfer.task;
|
|
42820
43121
|
}
|
|
42821
43122
|
const warnings = [];
|
|
42822
|
-
if (task.status === "pending" || task.status === "interrupted" || task.status === "failed") {
|
|
43123
|
+
if (task.status === "pending" || task.status === "interrupted" || task.status === "failed" || task.status === "completed" || task.status === "cancelled") {
|
|
42823
43124
|
const begun = await bridge.beginRun({
|
|
42824
43125
|
tenantId: tenantId2,
|
|
42825
43126
|
task,
|
|
@@ -42946,6 +43247,7 @@ function createTaskExecutionService(deps) {
|
|
|
42946
43247
|
...warnings.length > 0 ? { warnings } : {}
|
|
42947
43248
|
};
|
|
42948
43249
|
}
|
|
43250
|
+
const settledStatus = await readTaskStatus();
|
|
42949
43251
|
return {
|
|
42950
43252
|
success: false,
|
|
42951
43253
|
code: failureCode,
|
|
@@ -42954,6 +43256,7 @@ function createTaskExecutionService(deps) {
|
|
|
42954
43256
|
agentId,
|
|
42955
43257
|
threadId,
|
|
42956
43258
|
executionResultId: eventKey,
|
|
43259
|
+
...settledStatus !== void 0 ? { taskStatus: settledStatus } : {},
|
|
42957
43260
|
...warnings.length > 0 ? { warnings } : {}
|
|
42958
43261
|
};
|
|
42959
43262
|
} catch (persistenceError) {
|
|
@@ -43002,6 +43305,7 @@ function createTaskExecutionService(deps) {
|
|
|
43002
43305
|
...warnings.length > 0 ? { warnings } : {}
|
|
43003
43306
|
};
|
|
43004
43307
|
}
|
|
43308
|
+
const taskStatus = await readTaskStatus();
|
|
43005
43309
|
return {
|
|
43006
43310
|
success: true,
|
|
43007
43311
|
taskId: task.id,
|
|
@@ -43009,6 +43313,7 @@ function createTaskExecutionService(deps) {
|
|
|
43009
43313
|
threadId,
|
|
43010
43314
|
observation: validated.observation,
|
|
43011
43315
|
executionResultId: eventKey,
|
|
43316
|
+
...taskStatus !== void 0 ? { taskStatus } : {},
|
|
43012
43317
|
...warnings.length > 0 ? { warnings } : {}
|
|
43013
43318
|
};
|
|
43014
43319
|
} catch (error) {
|
|
@@ -43029,6 +43334,23 @@ function createTaskExecutionService(deps) {
|
|
|
43029
43334
|
};
|
|
43030
43335
|
return {
|
|
43031
43336
|
start: async (input) => {
|
|
43337
|
+
let current;
|
|
43338
|
+
try {
|
|
43339
|
+
current = await deps.taskStore.getById(input.tenantId, input.taskId);
|
|
43340
|
+
} catch {
|
|
43341
|
+
current = null;
|
|
43342
|
+
}
|
|
43343
|
+
if (current && current.status === "in_progress" && current.ownerType === "agent" && canConvergeOrphan() && !executionAuthorityRegistry.resolve(current) && !await isTaskHeldByLiveRun(current)) {
|
|
43344
|
+
try {
|
|
43345
|
+
await lifecycle.failTask({
|
|
43346
|
+
tenantId: input.tenantId,
|
|
43347
|
+
taskId: current.id,
|
|
43348
|
+
actor: input.actor,
|
|
43349
|
+
failureReason: "Run is no longer executing this task (no_executor)"
|
|
43350
|
+
});
|
|
43351
|
+
} catch {
|
|
43352
|
+
}
|
|
43353
|
+
}
|
|
43032
43354
|
const prepared = await prepareTaskDispatch(deps, input);
|
|
43033
43355
|
if (prepared.kind === "managed") return prepared.result;
|
|
43034
43356
|
if (prepared.kind === "failure") return prepared.failure;
|
|
@@ -45173,9 +45495,85 @@ Use \`imagine_html\`. Wrap the entire thing in a single raised card. All content
|
|
|
45173
45495
|
</div>
|
|
45174
45496
|
</div>
|
|
45175
45497
|
\`\`\``;
|
|
45498
|
+
var ANNOTATABLE_MOCKUP = `## Annotatable drafts (reviewable mockups)
|
|
45499
|
+
|
|
45500
|
+
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.)
|
|
45501
|
+
|
|
45502
|
+
- 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\`.
|
|
45503
|
+
- 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.
|
|
45504
|
+
- 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).
|
|
45505
|
+
- Handle \`contextmenu\` with event delegation; \`preventDefault()\` + \`stopPropagation()\` (this replaces the native browser menu, which is fine on a draft).
|
|
45506
|
+
- 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.
|
|
45507
|
+
- 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.
|
|
45508
|
+
|
|
45509
|
+
\`\`\`html
|
|
45510
|
+
<style>
|
|
45511
|
+
.anno{position:relative}
|
|
45512
|
+
.anno:hover{outline:1px dashed var(--color-border-info);outline-offset:-2px;border-radius:var(--border-radius-md)}
|
|
45513
|
+
.anno.marked{outline:1.5px solid var(--color-text-info);outline-offset:-2px}
|
|
45514
|
+
.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}
|
|
45515
|
+
.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}
|
|
45516
|
+
.modal .mt{font-size:14px;font-weight:500;margin-bottom:2px}
|
|
45517
|
+
.modal .ms{font-size:11px;color:var(--color-text-tertiary);margin-bottom:10px}
|
|
45518
|
+
.modal textarea{width:100%;box-sizing:border-box;height:auto;min-height:72px;padding:8px 10px;resize:vertical}
|
|
45519
|
+
.modal .mrow{display:flex;gap:8px;justify-content:flex-end;margin-top:10px}
|
|
45520
|
+
.primary{background:var(--color-text-info);color:var(--color-background-primary);border-color:var(--color-text-info)}
|
|
45521
|
+
.primary:hover{background:var(--color-text-info);opacity:.9}
|
|
45522
|
+
</style>
|
|
45523
|
+
<div class="panel" style="position:relative">
|
|
45524
|
+
<div style="font-size:12px;color:var(--color-text-tertiary);margin-bottom:8px">Right-click an element to comment</div>
|
|
45525
|
+
<div class="anno" data-el="Title" data-val="Q4 revenue">Q4 revenue</div>
|
|
45526
|
+
<div class="anno" data-el="Primary action" data-val="Export">Export</div>
|
|
45527
|
+
</div>
|
|
45528
|
+
<script>
|
|
45529
|
+
var panel = document.querySelector('.panel');
|
|
45530
|
+
var current = null;
|
|
45531
|
+
function closeBox(){
|
|
45532
|
+
if (current) current.classList.remove('marked');
|
|
45533
|
+
var ov = panel.querySelector('.overlay');
|
|
45534
|
+
if (ov) ov.remove();
|
|
45535
|
+
current = null;
|
|
45536
|
+
}
|
|
45537
|
+
function openBox(el){
|
|
45538
|
+
closeBox();
|
|
45539
|
+
current = el;
|
|
45540
|
+
el.classList.add('marked');
|
|
45541
|
+
var ov = document.createElement('div');
|
|
45542
|
+
ov.className = 'overlay';
|
|
45543
|
+
ov.innerHTML = '<div class="modal">' +
|
|
45544
|
+
'<div class="mt">Note: ' + el.dataset.el + '</div>' +
|
|
45545
|
+
(el.dataset.val ? '<div class="ms">Current: ' + el.dataset.val + '</div>' : '') +
|
|
45546
|
+
'<textarea id="cmt" placeholder="What should change? e.g. bolder bar / expand by default / add a year filter"></textarea>' +
|
|
45547
|
+
'<div class="mrow"><button id="cancel">Cancel</button><button class="primary" id="send">Send note \u2197</button></div>' +
|
|
45548
|
+
'</div>';
|
|
45549
|
+
panel.appendChild(ov);
|
|
45550
|
+
ov.querySelector('#cmt').focus();
|
|
45551
|
+
ov.querySelector('#send').onclick = function(){
|
|
45552
|
+
var text = ov.querySelector('#cmt').value.trim();
|
|
45553
|
+
if (text && typeof sendPrompt === 'function') {
|
|
45554
|
+
sendPrompt('[design note] ' + el.dataset.el + (el.dataset.val ? ' (current: ' + el.dataset.val + ')' : '') + ' \u2014 change: ' + text);
|
|
45555
|
+
}
|
|
45556
|
+
closeBox();
|
|
45557
|
+
};
|
|
45558
|
+
ov.querySelector('#cancel').onclick = closeBox;
|
|
45559
|
+
ov.addEventListener('click', function(e){ if (e.target === ov) closeBox(); });
|
|
45560
|
+
}
|
|
45561
|
+
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeBox(); });
|
|
45562
|
+
panel.addEventListener('contextmenu', function(e){
|
|
45563
|
+
var el = e.target.closest('.anno');
|
|
45564
|
+
if (!el) return;
|
|
45565
|
+
e.preventDefault(); e.stopPropagation();
|
|
45566
|
+
openBox(el);
|
|
45567
|
+
});
|
|
45568
|
+
</script>
|
|
45569
|
+
\`\`\`
|
|
45570
|
+
|
|
45571
|
+
- 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\`).
|
|
45572
|
+
|
|
45573
|
+
Keep it lightweight: annotate only meaningful elements, reuse one overlay, no libraries, and put \`<script>\` last so the draft streams in top-down.`;
|
|
45176
45574
|
var MODULE_SECTIONS = {
|
|
45177
45575
|
art: [SVG_SETUP, ART_AND_ILLUSTRATION],
|
|
45178
|
-
mockup: [UI_COMPONENTS, COLOR_PALETTE],
|
|
45576
|
+
mockup: [UI_COMPONENTS, COLOR_PALETTE, ANNOTATABLE_MOCKUP],
|
|
45179
45577
|
interactive: [UI_COMPONENTS, COLOR_PALETTE],
|
|
45180
45578
|
chart: [UI_COMPONENTS, COLOR_PALETTE, CHARTS_CHART_JS],
|
|
45181
45579
|
diagram: [COLOR_PALETTE, SVG_SETUP, DIAGRAM_TYPES]
|
|
@@ -45677,7 +46075,7 @@ function renderTaskBinding(taskId) {
|
|
|
45677
46075
|
|
|
45678
46076
|
## Bound Task
|
|
45679
46077
|
|
|
45680
|
-
You are executing task \`${taskId}
|
|
46078
|
+
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
46079
|
`;
|
|
45682
46080
|
}
|
|
45683
46081
|
function createBoundTaskLifecycle() {
|
|
@@ -46196,7 +46594,7 @@ var manageTaskSchema = import_zod68.z.object({
|
|
|
46196
46594
|
title: import_zod68.z.string().optional().describe("Task title (required for create)"),
|
|
46197
46595
|
description: import_zod68.z.string().optional().describe("Task description in Markdown"),
|
|
46198
46596
|
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'
|
|
46597
|
+
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
46598
|
dueDate: import_zod68.z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
46201
46599
|
metadata: import_zod68.z.record(import_zod68.z.unknown()).optional().describe("Structured metadata (e.g. projectId, module)"),
|
|
46202
46600
|
metadataFilter: import_zod68.z.record(
|
|
@@ -47787,8 +48185,8 @@ ${TASK_CONVERGENCE_GUIDANCE}
|
|
|
47787
48185
|
- update: Update a task's content fields (title/description/priority/dueDate/metadata/parentId/sourceId/context/ownerId/result/failureReason/requireReview/dependencies/files)
|
|
47788
48186
|
Status changes can also be done here, but use set_status for a simpler dedicated status update.
|
|
47789
48187
|
- 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)
|
|
48188
|
+
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.
|
|
48189
|
+
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
48190
|
To mark failed: set_status with status='failed', failureReason='...'
|
|
47793
48191
|
To pause for input: set_status with status='interrupted', summary='what is needed'
|
|
47794
48192
|
Valid status values: pending, in_progress, review, failed, interrupted, completed, cancelled.
|
|
@@ -47805,16 +48203,16 @@ Report existing keys directly; set 'isNew: true' to add a key the owner does not
|
|
|
47805
48203
|
track yet. Belief is evidence metadata: it never blocks a completion, and an
|
|
47806
48204
|
unmatched or malformed entry is recorded without changing the owner's table.
|
|
47807
48205
|
Create always persists a pending record \u2014 a supplied status is coerced and no
|
|
47808
|
-
execution is dispatched.
|
|
47809
|
-
|
|
47810
|
-
|
|
48206
|
+
execution is dispatched. Set \`in_progress\` yourself ONLY for a task THIS run
|
|
48207
|
+
executes; a task you delegate stays \`pending\` and is claimed automatically by
|
|
48208
|
+
\`task\` dispatch. Use \`task { taskId, agentId }\` to delegate a subtask
|
|
47811
48209
|
to an expert \u2014 never re-dispatch the task you are processing.`,
|
|
47812
48210
|
schema: manageTaskSchema
|
|
47813
48211
|
}
|
|
47814
48212
|
),
|
|
47815
48213
|
(0, import_langchain83.tool)(handleStartTask, {
|
|
47816
48214
|
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
|
|
48215
|
+
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
48216
|
schema: startTaskSchema
|
|
47819
48217
|
})
|
|
47820
48218
|
]
|
|
@@ -49512,7 +49910,7 @@ A plugin is the SET of classes it needs, e.g. \`connection + tools + skills\` or
|
|
|
49512
49910
|
| Tool shows an interactive panel | \`sandbox-plugins-ui\` |
|
|
49513
49911
|
| Tool orchestrates platform tools/agents | \`sandbox-plugins-consume-open\` |
|
|
49514
49912
|
| Tool callable by external MCP clients | \`sandbox-plugins-expose\` |
|
|
49515
|
-
| Run as a long-lived resident service | \`sandbox-plugins-deploy\` |
|
|
49913
|
+
| Run as a long-lived resident service (only on the human's explicit request) | \`sandbox-plugins-deploy\` |
|
|
49516
49914
|
| Validate / test / debug | \`sandbox-plugins-testing\` |
|
|
49517
49915
|
|
|
49518
49916
|
## Agents (not a supported authoring path)
|
|
@@ -49554,8 +49952,8 @@ chart, table, viewer\u2026). It can also call the plugin's OWN tools back from t
|
|
|
49554
49952
|
- It is NOT the tool's JSON \`schema\` (that is the input contract for the model).
|
|
49555
49953
|
- It is NOT the tool \`description\`.
|
|
49556
49954
|
- 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
|
|
49955
|
+
- Saying "add a UI" means: add a \`ui\` field to the tool in \`plugin.json\` and write
|
|
49956
|
+
\`ui/<tool>.html\` (with \`write_file\`). Details in \`sandbox-plugins-ui\`.
|
|
49559
49957
|
|
|
49560
49958
|
## Decide first \u2014 clarify these with the user before writing files
|
|
49561
49959
|
Ask for anything not already stated; do not guess silently:
|
|
@@ -49570,7 +49968,7 @@ Ask for anything not already stated; do not guess silently:
|
|
|
49570
49968
|
multi-select? connection test needed? \u2192 read \`sandbox-plugins-connection\`.
|
|
49571
49969
|
- **UI**: should the tool show an interactive panel instead of only text? what
|
|
49572
49970
|
does it show, how do users interact, external origins? \u2192 read
|
|
49573
|
-
|
|
49971
|
+
\`sandbox-plugins-ui\` and add \`ui\` + \`ui/<tool>.html\` (step 6 below). If the
|
|
49574
49972
|
user says no UI, skip it \u2014 text-only is fine.
|
|
49575
49973
|
- **Skills**: procedure or knowledge? who consumes it? trigger phrase? tied to a
|
|
49576
49974
|
specific tool?
|
|
@@ -49603,13 +50001,34 @@ Ask for anything not already stated; do not guess silently:
|
|
|
49603
50001
|
\`\`\`
|
|
49604
50002
|
|
|
49605
50003
|
## Flow
|
|
49606
|
-
1.
|
|
49607
|
-
|
|
49608
|
-
|
|
49609
|
-
|
|
49610
|
-
|
|
50004
|
+
1. **Design first, and iterate \u2014 then get explicit approval before you build (non-trivial plugins).**
|
|
50005
|
+
Before writing any file, produce the design and review it with the human. It has two parts,
|
|
50006
|
+
with different treatments:
|
|
50007
|
+
- **Functional design** (always): an **interactive** component map of what you intend to
|
|
50008
|
+
build \u2014 the tools, their inputs/outputs, whether a connection is needed, whether a UI
|
|
50009
|
+
is needed, and how the agent calls them. Make it click-to-explore (e.g. click a tool to
|
|
50010
|
+
reveal its handler / IO / connection); it is a map to **confirm**, not to annotate.
|
|
50011
|
+
- **UI design** (only when the UI class applies): an **annotatable** mock of the tool's UI \u2014
|
|
50012
|
+
the human right-clicks elements to leave notes (see \`sandbox-plugins-ui\` \u2192
|
|
50013
|
+
"Reviewable drafts").
|
|
50014
|
+
Show both drafts with the \`widget\` middleware, then run the review loop:
|
|
50015
|
+
- Ask the human to review (say how to comment). On each round of comments, **restate the
|
|
50016
|
+
feedback** (element + requested change), then **regenerate the draft with the feedback and
|
|
50017
|
+
present it again** \u2014 comments are "revise and re-show", never "start building".
|
|
50018
|
+
- **Do NOT scaffold or write any file during the loop.** Implement only after the human
|
|
50019
|
+
**explicitly approves**, and ask for that go-ahead yourself before you start.
|
|
50020
|
+
Skip the drafts only for a trivial single-tool plugin whose shape is already agreed \u2014 but
|
|
50021
|
+
still confirm the goal.
|
|
50022
|
+
2. Scaffold with \`manage_plugin { action: "create", type, name }\` \u2014 creates
|
|
50023
|
+
\`/root/.agents/plugins/<type>/\` with a stub \`plugin.json\`. It carries no content.
|
|
50024
|
+
3. Read \`sandbox-plugins-manifest\` and write \`plugin.json\` with \`write_file\`.
|
|
50025
|
+
4. Read \`sandbox-plugins-handlers\` and write each handler with \`write_file\`.
|
|
50026
|
+
5. If the plugin needs credentials: read \`sandbox-plugins-connection\`.
|
|
50027
|
+
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\`.
|
|
50028
|
+
7. If a tool must be callable by external MCP clients: read \`sandbox-plugins-expose\`
|
|
49611
50029
|
and add \`expose: true\` (plus \`readOnly\`/\`destructive\` accuracy hints).
|
|
49612
|
-
|
|
50030
|
+
8. Read \`sandbox-plugins-testing\` and iterate: edit files with \`edit_file\`, then
|
|
50031
|
+
\`validate\` \u2192 \`reload\` \u2192 \`test\`.
|
|
49613
50032
|
|
|
49614
50033
|
Templates: \`templates/plugin.json\`, \`templates/handler.js\`, \`templates/test.js\`, \`templates/discover.js\`, \`templates/ui.html\`.
|
|
49615
50034
|
`,
|
|
@@ -49671,6 +50090,20 @@ description: Add an interactive HTML UI to a tenant plugin tool (rendered as a s
|
|
|
49671
50090
|
A tool can declare an HTML file that Axiom renders as a sandboxed MCP App when
|
|
49672
50091
|
the tool runs. The app can call the plugin's OWN tools back.
|
|
49673
50092
|
|
|
50093
|
+
## Reviewable drafts (before you build the real UI)
|
|
50094
|
+
When you show a **UI mock with \`show_widget\`** for the user to react to (the design
|
|
50095
|
+
step, before writing \`ui/<tool>.html\`), make it **annotatable**: the user right-clicks
|
|
50096
|
+
any element to leave a comment that comes back to you as a message. Turn it on by
|
|
50097
|
+
default for review drafts and include a small visible hint (e.g. "\u53F3\u952E\u4EFB\u610F\u5143\u7D20\u6279\u6CE8");
|
|
50098
|
+
the \`widget\` mockup guidelines have the pattern. Omit it only when the draft is meant
|
|
50099
|
+
to be purely experienced (a live demo with no review) \u2014 then leave out the hint and
|
|
50100
|
+
the handlers. This is only for the **mock**: the real \`ui/<tool>.html\` is the plugin's
|
|
50101
|
+
product and is never annotatable.
|
|
50102
|
+
A round of annotations is **NOT approval**: restate the comments you received, regenerate
|
|
50103
|
+
the mock from them, and show it again, iterating until the human is satisfied. Build
|
|
50104
|
+
\`ui/<tool>.html\` only after the human **explicitly approves** \u2014 and ask for that go-ahead
|
|
50105
|
+
yourself.
|
|
50106
|
+
|
|
49674
50107
|
## Declare it
|
|
49675
50108
|
\`\`\`
|
|
49676
50109
|
/root/.agents/plugins/<type>/
|
|
@@ -49703,11 +50136,10 @@ the tool runs. The app can call the plugin's OWN tools back.
|
|
|
49703
50136
|
- Omit \`csp\` for the restrictive default (\`default-src 'none'\`). WebAssembly is allowed by default.
|
|
49704
50137
|
|
|
49705
50138
|
## 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\`.
|
|
50139
|
+
Write the HTML to \`/root/.agents/plugins/<type>/ui/<tool>.html\` with \`write_file\`,
|
|
50140
|
+
then call \`manage_plugin { action: "reload", type }\` so the host serves the new HTML.
|
|
50141
|
+
The file must be declared by a tool's \`ui.resource\` in \`plugin.json\`, otherwise it is
|
|
50142
|
+
treated as missing (\`TOOL_UI\`).
|
|
49711
50143
|
|
|
49712
50144
|
## How it behaves at runtime
|
|
49713
50145
|
- 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 +50235,7 @@ grants apply to the UI too). See \`sandbox-plugins-expose\`.
|
|
|
49803
50235
|
## Gotchas
|
|
49804
50236
|
- \`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
50237
|
- \`displayMode\`, \`permissions\`, and \`prefersBorder\` are accepted but NOT yet applied.
|
|
49806
|
-
- After editing the HTML, call \`manage_plugin reload
|
|
50238
|
+
- 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
50239
|
|
|
49808
50240
|
Start from \`templates/ui.html\`.
|
|
49809
50241
|
`,
|
|
@@ -50067,21 +50499,23 @@ description: Validate, test, and reload a tenant sandbox plugin using the manage
|
|
|
50067
50499
|
|
|
50068
50500
|
Use the \`manage_plugin\` tool. The loop:
|
|
50069
50501
|
|
|
50070
|
-
1.
|
|
50071
|
-
|
|
50502
|
+
1. Write/edit the plugin files with the filesystem tools (\`write_file\`/\`edit_file\`) under
|
|
50503
|
+
\`/root/.agents/plugins/<type>/\`; \`create\` only scaffolds the directory + stub.
|
|
50504
|
+
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
50505
|
3. If the plugin has \`connection.fields\` and a tool reads credentials: a **human**
|
|
50073
50506
|
must configure a connection of this plugin type. You cannot create credentials.
|
|
50074
50507
|
Call \`list_connections { type }\` first \u2014 if none exist, use \`ask_user_to_clarify\`
|
|
50075
50508
|
to state explicitly that only the human can configure the connector, ask them to
|
|
50076
50509
|
add it (Console \u2192 Connections) and to give you the connection key. Do not claim
|
|
50077
50510
|
the tool works until a real \`connectionKey\` test passes.
|
|
50078
|
-
4. \`action: "
|
|
50511
|
+
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).
|
|
50512
|
+
5. \`action: "test"\` to exercise a handler without an LLM:
|
|
50079
50513
|
- Tool: \`{ type, target: { kind: "tool", name, input, connectionKey? } }\`
|
|
50080
50514
|
- Connection: \`{ type, target: { kind: "connection-test" } }\` or \`{ kind: "connection-discover" }\`
|
|
50081
50515
|
Returns \`{ ok, result, exitCode, durationMs, stdout, stderr }\`.
|
|
50082
50516
|
For credentialed tools, pass the real \`connectionKey\`. Without one, the handler
|
|
50083
50517
|
receives an empty connection \u2014 that only checks plumbing, NOT functional success.
|
|
50084
|
-
|
|
50518
|
+
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
50519
|
|
|
50086
50520
|
## Secrets
|
|
50087
50521
|
- \`list_connections\` returns identifiers only (\`key\`/\`name\`/\`description\`), never config or secrets.
|
|
@@ -50096,10 +50530,10 @@ Use the \`manage_plugin\` tool. The loop:
|
|
|
50096
50530
|
| HANDLER_MISSING | handler path wrong or file not written |
|
|
50097
50531
|
| TOOL_SCHEMA | unsupported JSON Schema keyword |
|
|
50098
50532
|
| non-zero exit | handler threw; read stdout/stderr from \`test\` |
|
|
50099
|
-
| old behaviour after edit |
|
|
50533
|
+
| old behaviour after edit | the catalog caches by content hash/TTL \u2014 call \`reload\` |
|
|
50100
50534
|
| empty connection in handler | no \`connectionKey\` passed, or no connection configured for this type |
|
|
50101
50535
|
|
|
50102
|
-
Fall back to \`
|
|
50536
|
+
Fall back to \`shell_exec\` to run \`node handlers/x.js\` manually with
|
|
50103
50537
|
\`PLUGIN_INPUT\` and a synthetic \`PLUGIN_CONNECTION_FILE\` to reproduce logic bugs.
|
|
50104
50538
|
A synthetic fixture only verifies plumbing \u2014 a real upstream call requires a real
|
|
50105
50539
|
\`connectionKey\` configured by a human.
|
|
@@ -50110,7 +50544,7 @@ A synthetic fixture only verifies plumbing \u2014 a real upstream call requires
|
|
|
50110
50544
|
version: "1.0.0",
|
|
50111
50545
|
content: `---
|
|
50112
50546
|
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:
|
|
50547
|
+
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
50548
|
---
|
|
50115
50549
|
|
|
50116
50550
|
# Deploying a Plugin as a Resident Service
|
|
@@ -50120,10 +50554,13 @@ Optionally, you can **deploy** the plugin as a **resident service**: one
|
|
|
50120
50554
|
long-lived sandbox per plugin that serves every call. This is faster for
|
|
50121
50555
|
frequent or heavy calls, but costs a running sandbox.
|
|
50122
50556
|
|
|
50123
|
-
## Precondition: tests must pass first
|
|
50124
|
-
|
|
50125
|
-
|
|
50126
|
-
|
|
50557
|
+
## Precondition: human request, then tests must pass first
|
|
50558
|
+
Deployment is **opt-in and human-gated**: NEVER deploy on your own after tests
|
|
50559
|
+
pass. Wait until the human explicitly asks for it or approves it. A passing test
|
|
50560
|
+
is necessary but NOT sufficient to deploy.
|
|
50561
|
+
Then confirm the plugin is validated AND its tests pass:
|
|
50562
|
+
1. Files written with the filesystem tools and \`reload\` run.
|
|
50563
|
+
2. \`manage_plugin validate\` \u2014 no diagnostics.
|
|
50127
50564
|
3. \`manage_plugin test\` \u2014 every tool/connection test returns \`ok: true\`
|
|
50128
50565
|
(a real \`connectionKey\` test for credentialed tools).
|
|
50129
50566
|
Never deploy a plugin you have not tested: a resident service wraps the same
|
|
@@ -50191,6 +50628,30 @@ load "sandbox-plugins-expose" and follow it (\`expose: true\` + key grants).
|
|
|
50191
50628
|
If a tool must call the platform's own tools/agents from its handler, load
|
|
50192
50629
|
"sandbox-plugins-consume-open".
|
|
50193
50630
|
|
|
50631
|
+
Design first, and iterate on it before you build \u2014 the design is a conversation, not
|
|
50632
|
+
a one-shot. Use the \`widget\` middleware to present it (a decision surface, not a showcase):
|
|
50633
|
+
- **Functional design** (always): an **interactive** component map of the shape you
|
|
50634
|
+
intend to build \u2014 which tools, their inputs/outputs, whether a connection or a UI
|
|
50635
|
+
is needed, how the agent calls them. Make it click-to-explore (and prefer it over
|
|
50636
|
+
an SVG flowchart); it is a map to **confirm**, NOT an annotatable draft.
|
|
50637
|
+
- **UI design** (UI class only): an **annotatable** mock of the tool's UI \u2014 the human
|
|
50638
|
+
right-clicks elements to leave notes.
|
|
50639
|
+
Then run a review loop:
|
|
50640
|
+
- Ask the human to review (tell them how to comment). When they send comments, **restate the
|
|
50641
|
+
feedback** (which element, what change) so they can confirm you understood, then **build a
|
|
50642
|
+
NEW draft that incorporates the feedback and present it again**; keep iterating until
|
|
50643
|
+
they are satisfied. Comments mean "revise and re-show", NOT "start building".
|
|
50644
|
+
- Do NOT scaffold or write any file during the loop. Implement only once the human
|
|
50645
|
+
**explicitly approves** the design \u2014 and still ASK for that go-ahead yourself; never
|
|
50646
|
+
start building on your own initiative.
|
|
50647
|
+
Also surface genuinely different options (scope, layout, which tools) visually and let
|
|
50648
|
+
the human choose, instead of describing them in prose.
|
|
50649
|
+
|
|
50650
|
+
How to render a good widget is the widget middleware's own concern \u2014 follow it
|
|
50651
|
+
there; keep this prompt to plugin-design intent. These widgets are design aids
|
|
50652
|
+
only; the plugin's actual UI is still the MCP App HTML you author and test with
|
|
50653
|
+
\`manage_plugin\`.
|
|
50654
|
+
|
|
50194
50655
|
Plan with persistent Tasks: MANDATORY until you load "task-tracking" with the
|
|
50195
50656
|
\`skill\` tool, call neither \`manage_task\` nor any other task operation. Use
|
|
50196
50657
|
persistent Tasks as the development planning surface and create one Task per
|
|
@@ -50198,7 +50659,16 @@ independently verifiable outcome (for example "manifest written", "handlers
|
|
|
50198
50659
|
implemented", "UI added", "validated/tested via manage_plugin"). As you work,
|
|
50199
50660
|
update each Task's status to reflect reality. Never use \`write_todos\`.
|
|
50200
50661
|
|
|
50201
|
-
|
|
50662
|
+
After the human confirms the design, author the plugin files directly with the filesystem tools, then verify with \`manage_plugin\`.
|
|
50663
|
+
1. Scaffold once: \`manage_plugin { action: "create", type: "<type>", name }\` \u2014 it creates
|
|
50664
|
+
\`/root/.agents/plugins/<type>/\` with a stub \`plugin.json\`. It never carries content.
|
|
50665
|
+
2. Write and edit every file yourself with \`write_file\` / \`edit_file\` (read back with
|
|
50666
|
+
\`read_file\`) under \`/root/.agents/plugins/<type>/\`: \`plugin.json\`, \`handlers/*.js\`, and
|
|
50667
|
+
(UI class) \`ui/*.html\`. Change one file at a time; edit in place instead of rewriting.
|
|
50668
|
+
3. Verify in order: \`manage_plugin { action: "validate", type }\` (reads the files back from
|
|
50669
|
+
the sandbox) \u2192 \`manage_plugin { action: "reload", type }\` (the catalog caches, so a
|
|
50670
|
+
freshly written file is invisible to \`test\` until you reload) \u2192 \`manage_plugin
|
|
50671
|
+
{ action: "test", type, target }\`.
|
|
50202
50672
|
If a tool needs credentials, a human must configure the connection \u2014 use
|
|
50203
50673
|
\`list_connections\` to check, and \`ask_user_to_clarify\` to ask the human to add
|
|
50204
50674
|
the connector and give you its key. Only after a real \`connectionKey\` test
|
|
@@ -50207,14 +50677,15 @@ unverified (missing credentials), not as passing. Use \`shell_exec\` (the tool
|
|
|
50207
50677
|
provided by the \`code_eval\` middleware) to run \`node handlers/x.js\` for manual
|
|
50208
50678
|
checks when reproducing a failure.
|
|
50209
50679
|
|
|
50210
|
-
|
|
50211
|
-
|
|
50680
|
+
Deployment is human-gated: do NOT deploy on your own after the tests pass.
|
|
50681
|
+
Only deploy when the human explicitly asks for it or approves it, then load
|
|
50682
|
+
"sandbox-plugins-deploy" and follow it. Choose \`onDemand\`
|
|
50212
50683
|
(default) or \`alwaysOn\` for hot paths, call \`manage_plugin deploy\` with the
|
|
50213
50684
|
chosen \`mode\`, then confirm with \`manage_plugin status\` (\`running\`). Only claim
|
|
50214
50685
|
the plugin is deployed and serving once \`status\` reports \`running\`; if the
|
|
50215
50686
|
environment reports no deployment service, say so and treat deployment as
|
|
50216
|
-
unavailable \u2014 do NOT claim success.
|
|
50217
|
-
touching the plugin files.`;
|
|
50687
|
+
unavailable \u2014 do NOT claim success. Report the plugin as ready and let the human
|
|
50688
|
+
decide; \`undeploy\` removes the service without touching the plugin files.`;
|
|
50218
50689
|
|
|
50219
50690
|
// src/middlewares/sandboxPluginsUi.ts
|
|
50220
50691
|
var SANDBOX_PLUGINS_UI_RESOURCE = "ui://sandbox-plugins/app";
|
|
@@ -50390,6 +50861,7 @@ var SANDBOX_PLUGINS_APP_HTML = `<!doctype html>
|
|
|
50390
50861
|
`;
|
|
50391
50862
|
|
|
50392
50863
|
// src/middlewares/sandboxPluginsMiddleware.ts
|
|
50864
|
+
var PLUGINS_ROOT3 = "/root/.agents/plugins";
|
|
50393
50865
|
var SANDBOX_PLUGINS_UI_REF = {
|
|
50394
50866
|
kind: "plugin",
|
|
50395
50867
|
pluginType: "sandbox-plugins",
|
|
@@ -50446,18 +50918,19 @@ var managePluginSchema = import_zod71.z.object({
|
|
|
50446
50918
|
"get",
|
|
50447
50919
|
"validate",
|
|
50448
50920
|
"create",
|
|
50449
|
-
"update",
|
|
50450
50921
|
"delete",
|
|
50451
50922
|
"reload",
|
|
50452
50923
|
"test",
|
|
50924
|
+
"backup",
|
|
50925
|
+
"restore",
|
|
50453
50926
|
"deploy",
|
|
50454
50927
|
"undeploy",
|
|
50455
50928
|
"status",
|
|
50456
50929
|
"deployments"
|
|
50457
50930
|
]),
|
|
50458
50931
|
type: import_zod71.z.string().optional(),
|
|
50459
|
-
|
|
50460
|
-
|
|
50932
|
+
name: import_zod71.z.string().optional(),
|
|
50933
|
+
description: import_zod71.z.string().optional(),
|
|
50461
50934
|
mode: import_zod71.z.enum(["onDemand", "alwaysOn"]).optional(),
|
|
50462
50935
|
target: import_zod71.z.object({
|
|
50463
50936
|
kind: import_zod71.z.enum(["tool", "connection-test", "connection-discover"]),
|
|
@@ -50471,12 +50944,12 @@ var sandboxPluginsPlugin = {
|
|
|
50471
50944
|
type: "sandbox-plugins",
|
|
50472
50945
|
category: "execution",
|
|
50473
50946
|
name: "Sandbox Plugins",
|
|
50474
|
-
description: "Author and manage tenant plugins stored in the sandbox: list/validate/create/
|
|
50947
|
+
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
50948
|
recommends: ["skill"],
|
|
50476
50949
|
tools: [
|
|
50477
50950
|
{
|
|
50478
50951
|
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/
|
|
50952
|
+
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
50953
|
ui: { resource: SANDBOX_PLUGINS_UI_RESOURCE, displayMode: "inline" }
|
|
50481
50954
|
}
|
|
50482
50955
|
],
|
|
@@ -50503,7 +50976,7 @@ var sandboxPluginsPlugin = {
|
|
|
50503
50976
|
id: "sandbox-plugins",
|
|
50504
50977
|
type: "sandbox-plugins",
|
|
50505
50978
|
name: "Sandbox Plugins",
|
|
50506
|
-
description: "Author and manage tenant plugins (list/validate/create/
|
|
50979
|
+
description: "Author and manage tenant plugins (list/validate/create/delete/reload/test/backup/restore/deploy/undeploy/status)",
|
|
50507
50980
|
enabled: true,
|
|
50508
50981
|
config: {}
|
|
50509
50982
|
},
|
|
@@ -50523,6 +50996,14 @@ var sandboxPluginsPlugin = {
|
|
|
50523
50996
|
enabled: true,
|
|
50524
50997
|
config: {}
|
|
50525
50998
|
},
|
|
50999
|
+
{
|
|
51000
|
+
id: "widget",
|
|
51001
|
+
type: "widget",
|
|
51002
|
+
name: "Widget",
|
|
51003
|
+
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.",
|
|
51004
|
+
enabled: true,
|
|
51005
|
+
config: {}
|
|
51006
|
+
},
|
|
50526
51007
|
{
|
|
50527
51008
|
id: "ask_user_to_clarify",
|
|
50528
51009
|
type: "ask_user_to_clarify",
|
|
@@ -50563,6 +51044,7 @@ var sandboxPluginsPlugin = {
|
|
|
50563
51044
|
return JSON.stringify({
|
|
50564
51045
|
plugins: loaded.plugins.map((p) => ({
|
|
50565
51046
|
type: p.meta.type,
|
|
51047
|
+
path: `${PLUGINS_ROOT3}/${p.meta.type}`,
|
|
50566
51048
|
name: p.meta.name,
|
|
50567
51049
|
tools: p.meta.tools?.map((t) => t.name) ?? [],
|
|
50568
51050
|
source: p.meta.source
|
|
@@ -50573,32 +51055,87 @@ var sandboxPluginsPlugin = {
|
|
|
50573
51055
|
}
|
|
50574
51056
|
case "get": {
|
|
50575
51057
|
if (!input.type) return "type is required";
|
|
50576
|
-
|
|
51058
|
+
const content = await catalog2.readPluginFile(tenantId2, input.type, "plugin.json");
|
|
51059
|
+
let manifest = content;
|
|
51060
|
+
try {
|
|
51061
|
+
manifest = JSON.parse(content);
|
|
51062
|
+
} catch {
|
|
51063
|
+
}
|
|
51064
|
+
return JSON.stringify({
|
|
51065
|
+
type: input.type,
|
|
51066
|
+
path: `${PLUGINS_ROOT3}/${input.type}`,
|
|
51067
|
+
manifest
|
|
51068
|
+
});
|
|
50577
51069
|
}
|
|
50578
51070
|
case "reload": {
|
|
50579
51071
|
const loaded = await catalog2.reload(tenantId2);
|
|
50580
51072
|
void publishPluginCatalogChanged(tenantId2);
|
|
50581
51073
|
return JSON.stringify({ reloaded: true, plugins: loaded.plugins.length, diagnostics: loaded.diagnostics });
|
|
50582
51074
|
}
|
|
50583
|
-
case "
|
|
50584
|
-
|
|
50585
|
-
|
|
50586
|
-
|
|
51075
|
+
case "backup": {
|
|
51076
|
+
if (!input.type) return "type is required";
|
|
51077
|
+
try {
|
|
51078
|
+
const result = await catalog2.backupPlugin(tenantId2, input.type);
|
|
51079
|
+
if (result.files === 0) {
|
|
51080
|
+
return JSON.stringify({ backedUp: false, error: "nothing to back up (plugin has no files)" });
|
|
51081
|
+
}
|
|
51082
|
+
return JSON.stringify({ backedUp: true, files: result.files });
|
|
51083
|
+
} catch (err) {
|
|
51084
|
+
return JSON.stringify({ backedUp: false, error: errorText(err) });
|
|
51085
|
+
}
|
|
51086
|
+
}
|
|
51087
|
+
case "restore": {
|
|
51088
|
+
if (!input.type) return "type is required";
|
|
51089
|
+
let restored;
|
|
51090
|
+
let failure3;
|
|
51091
|
+
try {
|
|
51092
|
+
restored = await catalog2.restorePlugin(tenantId2, input.type);
|
|
51093
|
+
} catch (err) {
|
|
51094
|
+
failure3 = errorText(err);
|
|
51095
|
+
}
|
|
51096
|
+
try {
|
|
51097
|
+
await catalog2.reload(tenantId2);
|
|
51098
|
+
void publishPluginCatalogChanged(tenantId2);
|
|
51099
|
+
} catch {
|
|
51100
|
+
}
|
|
51101
|
+
if (failure3) return JSON.stringify({ restored: false, error: failure3 });
|
|
51102
|
+
return JSON.stringify({ restored: true, files: restored?.files ?? 0 });
|
|
51103
|
+
}
|
|
51104
|
+
case "create": {
|
|
51105
|
+
if (!input.type) return "type is required";
|
|
50587
51106
|
const known = new Set(
|
|
50588
51107
|
(catalog2.getCached(tenantId2)?.plugins ?? []).map((p) => p.meta.type).filter((t) => t !== input.type)
|
|
50589
51108
|
);
|
|
51109
|
+
const stub = {
|
|
51110
|
+
schemaVersion: 1,
|
|
51111
|
+
type: input.type,
|
|
51112
|
+
name: input.name ?? input.type,
|
|
51113
|
+
description: input.description ?? "",
|
|
51114
|
+
version: "0.1.0",
|
|
51115
|
+
tools: []
|
|
51116
|
+
};
|
|
50590
51117
|
const check = validateSandboxPluginManifest({
|
|
50591
|
-
raw:
|
|
51118
|
+
raw: stub,
|
|
50592
51119
|
dirName: input.type,
|
|
50593
51120
|
reservedTypes: new Set(PluginRegistry.list()),
|
|
50594
51121
|
knownTypes: known,
|
|
50595
|
-
handlerExists: (
|
|
51122
|
+
handlerExists: () => true
|
|
50596
51123
|
});
|
|
50597
|
-
if (!check.manifest)
|
|
50598
|
-
|
|
51124
|
+
if (!check.manifest) {
|
|
51125
|
+
return JSON.stringify({ created: false, diagnostics: check.diagnostics });
|
|
51126
|
+
}
|
|
51127
|
+
const files = await catalog2.listPluginFiles(tenantId2, input.type);
|
|
51128
|
+
if (files.includes("plugin.json")) {
|
|
51129
|
+
return JSON.stringify({ created: false, error: "plugin already exists" });
|
|
51130
|
+
}
|
|
51131
|
+
await catalog2.writePlugin(tenantId2, input.type, check.manifest, {});
|
|
50599
51132
|
const loaded = await catalog2.reload(tenantId2);
|
|
50600
51133
|
void publishPluginCatalogChanged(tenantId2);
|
|
50601
|
-
return JSON.stringify({
|
|
51134
|
+
return JSON.stringify({
|
|
51135
|
+
created: true,
|
|
51136
|
+
path: `${PLUGINS_ROOT3}/${input.type}`,
|
|
51137
|
+
diagnostics: loaded.diagnostics
|
|
51138
|
+
});
|
|
50602
51139
|
}
|
|
50603
51140
|
case "delete": {
|
|
50604
51141
|
if (!input.type) return "type is required";
|
|
@@ -50609,18 +51146,8 @@ var sandboxPluginsPlugin = {
|
|
|
50609
51146
|
}
|
|
50610
51147
|
case "validate": {
|
|
50611
51148
|
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) });
|
|
51149
|
+
const result = await catalog2.validatePluginFromDisk(tenantId2, input.type);
|
|
51150
|
+
return JSON.stringify({ diagnostics: result.diagnostics, valid: result.valid });
|
|
50624
51151
|
}
|
|
50625
51152
|
case "test": {
|
|
50626
51153
|
if (!input.type || !input.target) return "type and target are required";
|
|
@@ -50694,7 +51221,16 @@ var sandboxPluginsPlugin = {
|
|
|
50694
51221
|
assistantId: typeof runCfg.assistant_id === "string" ? runCfg.assistant_id : void 0
|
|
50695
51222
|
}
|
|
50696
51223
|
});
|
|
50697
|
-
|
|
51224
|
+
const text = handlerResultText(redactSecretValues(result, secrets));
|
|
51225
|
+
const ui = toolDef.ui;
|
|
51226
|
+
if (!ui?.resource) return text;
|
|
51227
|
+
const uiRef = {
|
|
51228
|
+
kind: "plugin",
|
|
51229
|
+
pluginType: input.type,
|
|
51230
|
+
resource: ui.resource,
|
|
51231
|
+
displayMode: ui.displayMode ?? "inline"
|
|
51232
|
+
};
|
|
51233
|
+
return appendUiFence(text, uiRef);
|
|
50698
51234
|
} catch (err) {
|
|
50699
51235
|
return JSON.stringify(redactSecretValues({ ok: false, error: errorText(err) }, secrets));
|
|
50700
51236
|
}
|
|
@@ -50766,7 +51302,7 @@ var sandboxPluginsPlugin = {
|
|
|
50766
51302
|
},
|
|
50767
51303
|
{
|
|
50768
51304
|
name: "manage_plugin",
|
|
50769
|
-
description: "Author and manage tenant sandbox plugins (list/get/validate/create/
|
|
51305
|
+
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
51306
|
schema: managePluginSchema
|
|
50771
51307
|
}
|
|
50772
51308
|
)
|
|
@@ -51950,6 +52486,7 @@ registerBuiltinPlugins();
|
|
|
51950
52486
|
buildTableName,
|
|
51951
52487
|
buildTaskMutationFallback,
|
|
51952
52488
|
buildTaskNormalizationFallback,
|
|
52489
|
+
canConvergeOrphan,
|
|
51953
52490
|
checkEmptyContent,
|
|
51954
52491
|
clearEncryptionKeyCache,
|
|
51955
52492
|
clearEvalRunService,
|
|
@@ -51999,6 +52536,7 @@ registerBuiltinPlugins();
|
|
|
51999
52536
|
createTaskLifecycleService,
|
|
52000
52537
|
createTaskMiddleware,
|
|
52001
52538
|
createTaskRunBridge,
|
|
52539
|
+
createTaskRunLiveness,
|
|
52002
52540
|
createTeamMiddleware,
|
|
52003
52541
|
createTeammateTools,
|
|
52004
52542
|
createTrustedAgentMessageDispatcher,
|
|
@@ -52096,6 +52634,7 @@ registerBuiltinPlugins();
|
|
|
52096
52634
|
isMcpConnectionError,
|
|
52097
52635
|
isPluginSkillAuthorizedInSandbox,
|
|
52098
52636
|
isPluginSkillManagedInSandbox,
|
|
52637
|
+
isTaskRunLeaseHeld,
|
|
52099
52638
|
isUsingDefaultKey,
|
|
52100
52639
|
isValidCronExpression,
|
|
52101
52640
|
isValidSandboxName,
|