@hasna/todos 0.15.20 → 0.15.24
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/ai-tools.d.ts +80 -0
- package/dist/ai-tools.d.ts.map +1 -0
- package/dist/ai.d.ts +313 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/cli/cloud-router.d.ts +10 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/ai-commands.d.ts +3 -0
- package/dist/cli/commands/ai-commands.d.ts.map +1 -0
- package/dist/cli/commands/help-commands.d.ts +2 -1
- package/dist/cli/commands/help-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +2856 -547
- package/dist/cli/stage-a.d.ts +27 -16
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +638 -18
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3142 -1089
- package/dist/lib/cli-help.d.ts +2 -2
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/saved-search-views.d.ts.map +1 -1
- package/dist/lib/stale-lock-handoff.d.ts.map +1 -1
- package/dist/mcp/index.js +127 -72
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +67 -29
- package/dist/registry.js +603 -18
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/v1.generated.d.ts +2 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +133 -78
- package/dist/server/openapi.d.ts +7 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage.js +60 -20
- package/dist/task-manifest.js +19 -3
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2123,7 +2123,7 @@ var package_default;
|
|
|
2123
2123
|
var init_package = __esm(() => {
|
|
2124
2124
|
package_default = {
|
|
2125
2125
|
name: "@hasna/todos",
|
|
2126
|
-
version: "0.15.
|
|
2126
|
+
version: "0.15.24",
|
|
2127
2127
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
2128
2128
|
type: "module",
|
|
2129
2129
|
main: "dist/index.js",
|
|
@@ -2600,6 +2600,7 @@ __export(exports_config, {
|
|
|
2600
2600
|
resetConfig: () => resetConfig,
|
|
2601
2601
|
normalizeApiUrl: () => normalizeApiUrl,
|
|
2602
2602
|
loadConfig: () => loadConfig,
|
|
2603
|
+
getTodosAiConfig: () => getTodosAiConfig,
|
|
2603
2604
|
getTaskPrefixConfig: () => getTaskPrefixConfig,
|
|
2604
2605
|
getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
|
|
2605
2606
|
getLocalApiConfig: () => getLocalApiConfig,
|
|
@@ -2609,7 +2610,7 @@ __export(exports_config, {
|
|
|
2609
2610
|
getAgentTaskListId: () => getAgentTaskListId,
|
|
2610
2611
|
getAgentPoolForProject: () => getAgentPoolForProject
|
|
2611
2612
|
});
|
|
2612
|
-
import { existsSync as existsSync2 } from "fs";
|
|
2613
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
2613
2614
|
import { dirname, join as join2 } from "path";
|
|
2614
2615
|
function getConfigPath() {
|
|
2615
2616
|
return join2(getTodosGlobalDir(), "config.json");
|
|
@@ -2644,6 +2645,22 @@ function saveConfig(config) {
|
|
|
2644
2645
|
function updateConfig(patch) {
|
|
2645
2646
|
return saveConfig({ ...loadConfig(), ...patch });
|
|
2646
2647
|
}
|
|
2648
|
+
function getTodosAiConfig() {
|
|
2649
|
+
const configPath = getConfigPath();
|
|
2650
|
+
if (!existsSync2(configPath))
|
|
2651
|
+
return {};
|
|
2652
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
2653
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2654
|
+
throw new Error("Todos config must be a JSON object");
|
|
2655
|
+
}
|
|
2656
|
+
const ai = parsed["ai"];
|
|
2657
|
+
if (ai === undefined)
|
|
2658
|
+
return {};
|
|
2659
|
+
if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
|
|
2660
|
+
throw new Error("Todos AI configuration must be an object");
|
|
2661
|
+
}
|
|
2662
|
+
return { ...ai };
|
|
2663
|
+
}
|
|
2647
2664
|
function normalizeApiUrl(value) {
|
|
2648
2665
|
const trimmed = value?.trim();
|
|
2649
2666
|
if (!trimmed)
|
|
@@ -5998,8 +6015,12 @@ async function cloudGetTask(client, id) {
|
|
|
5998
6015
|
const raw = await client.get("tasks", id);
|
|
5999
6016
|
return raw == null ? null : unwrapTask(raw);
|
|
6000
6017
|
}
|
|
6001
|
-
async function cloudCreateTask(client, input) {
|
|
6018
|
+
async function cloudCreateTask(client, input, verification = {}) {
|
|
6002
6019
|
const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
|
|
6020
|
+
const expectedCreatedBy = typeof verification.expectedCreatedBy === "string" && verification.expectedCreatedBy.trim() ? verification.expectedCreatedBy : null;
|
|
6021
|
+
if (expectedCreatedBy !== null) {
|
|
6022
|
+
await requireTaskCreatorCapability(client);
|
|
6023
|
+
}
|
|
6003
6024
|
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () => client.transport.post("/tasks", input, {
|
|
6004
6025
|
idempotencyKey: randomUUID(),
|
|
6005
6026
|
retry: false
|
|
@@ -6008,8 +6029,9 @@ async function cloudCreateTask(client, input) {
|
|
|
6008
6029
|
throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
|
|
6009
6030
|
}
|
|
6010
6031
|
const persisted = await cloudGetTask(client, created.id);
|
|
6011
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId) {
|
|
6012
|
-
|
|
6032
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId || expectedCreatedBy !== null && persisted.created_by !== expectedCreatedBy) {
|
|
6033
|
+
const creatorDetail = expectedCreatedBy === null ? "" : ` and explicit created_by=${JSON.stringify(expectedCreatedBy)} ` + `(readback ${JSON.stringify(persisted?.created_by ?? null)})`;
|
|
6034
|
+
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + `stored task id and parent_id${creatorDetail}; no success row or local SQLite fallback is permitted`);
|
|
6013
6035
|
}
|
|
6014
6036
|
return persisted;
|
|
6015
6037
|
}
|
|
@@ -6115,6 +6137,33 @@ function resolveOpenApiSchema(document, schema) {
|
|
|
6115
6137
|
}
|
|
6116
6138
|
return current && typeof current === "object" && !Array.isArray(current) ? current : null;
|
|
6117
6139
|
}
|
|
6140
|
+
function openApiSchemaProperty(document, schemaName, propertyName) {
|
|
6141
|
+
const schema = resolveOpenApiSchema(document, {
|
|
6142
|
+
$ref: `#/components/schemas/${schemaName}`
|
|
6143
|
+
});
|
|
6144
|
+
const properties = schema?.["properties"];
|
|
6145
|
+
if (!properties || typeof properties !== "object" || Array.isArray(properties))
|
|
6146
|
+
return null;
|
|
6147
|
+
const property = properties[propertyName];
|
|
6148
|
+
return property && typeof property === "object" && !Array.isArray(property) ? property : null;
|
|
6149
|
+
}
|
|
6150
|
+
async function fetchTaskCreatorCapability(client) {
|
|
6151
|
+
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6152
|
+
const inputProperty = openApiSchemaProperty(document, "CreateTaskInput", "created_by");
|
|
6153
|
+
const outputProperty = openApiSchemaProperty(document, "Task", "created_by");
|
|
6154
|
+
return inputProperty?.["type"] === "string" && outputProperty?.["type"] === "string" && outputProperty?.["nullable"] === true;
|
|
6155
|
+
}
|
|
6156
|
+
async function requireTaskCreatorCapability(client) {
|
|
6157
|
+
const authority = remoteAuthorityBase(client);
|
|
6158
|
+
let capability = taskCreatorCapabilityCache.get(authority);
|
|
6159
|
+
if (!capability) {
|
|
6160
|
+
capability = fetchTaskCreatorCapability(client);
|
|
6161
|
+
taskCreatorCapabilityCache.set(authority, capability);
|
|
6162
|
+
}
|
|
6163
|
+
if (!await capability) {
|
|
6164
|
+
throw new Error(`REMOTE_CREATED_BY_UNSUPPORTED: configured Todos authority ${authority} does not advertise created_by ` + "on both CreateTaskInput and Task in /v1/openapi.json; no task mutation was sent; deploy a compatible " + "@hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6118
6167
|
async function fetchRetryCapability(client) {
|
|
6119
6168
|
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6120
6169
|
if (!document || typeof document !== "object" || Array.isArray(document))
|
|
@@ -6667,6 +6716,30 @@ function openApiHasOperation(document, path, method) {
|
|
|
6667
6716
|
const route = paths[path];
|
|
6668
6717
|
return Boolean(route && typeof route === "object" && !Array.isArray(route) && route[method] && typeof route[method] === "object");
|
|
6669
6718
|
}
|
|
6719
|
+
async function fetchTodosRemoteCommandCapabilities(client) {
|
|
6720
|
+
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6721
|
+
const supported = new Set;
|
|
6722
|
+
if (openApiHasOperation(document, "/v1/tasks/{id}/stale-lock-handoff", "post")) {
|
|
6723
|
+
supported.add("stale-lock-handoff");
|
|
6724
|
+
}
|
|
6725
|
+
return supported;
|
|
6726
|
+
}
|
|
6727
|
+
async function getTodosRemoteCommandCapabilities(client) {
|
|
6728
|
+
const authority = remoteAuthorityBase(client);
|
|
6729
|
+
let capabilities = remoteCommandCapabilityCache.get(authority);
|
|
6730
|
+
if (!capabilities) {
|
|
6731
|
+
capabilities = fetchTodosRemoteCommandCapabilities(client);
|
|
6732
|
+
remoteCommandCapabilityCache.set(authority, capabilities);
|
|
6733
|
+
}
|
|
6734
|
+
return capabilities;
|
|
6735
|
+
}
|
|
6736
|
+
async function requireStaleLockHandoffCapability(client) {
|
|
6737
|
+
const authority = remoteAuthorityBase(client);
|
|
6738
|
+
const capabilities = await getTodosRemoteCommandCapabilities(client);
|
|
6739
|
+
if (!capabilities.has("stale-lock-handoff")) {
|
|
6740
|
+
throw new Error(`REMOTE_STALE_LOCK_HANDOFF_UNSUPPORTED: configured Todos authority ${authority} does not advertise ` + "POST /v1/tasks/{id}/stale-lock-handoff; no lock mutation was sent; deploy a compatible " + "@hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6670
6743
|
async function fetchGitRefCapabilities(client) {
|
|
6671
6744
|
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6672
6745
|
const supported = new Set;
|
|
@@ -6766,6 +6839,7 @@ async function cloudUnlockTask(client, id, agentId, force = false) {
|
|
|
6766
6839
|
return true;
|
|
6767
6840
|
}
|
|
6768
6841
|
async function cloudHandoffStaleTaskLock(client, input) {
|
|
6842
|
+
await requireStaleLockHandoffCapability(client);
|
|
6769
6843
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(input.task_id)}/stale-lock-handoff`, {
|
|
6770
6844
|
expected_holder: input.expected_holder,
|
|
6771
6845
|
expected_lock_version: input.expected_lock_version,
|
|
@@ -7213,7 +7287,7 @@ async function cloudTimeline(client, options = {}) {
|
|
|
7213
7287
|
const limit = options.limit ?? 50;
|
|
7214
7288
|
return { entries: entries.slice(offset, offset + limit), total, limit, offset };
|
|
7215
7289
|
}
|
|
7216
|
-
var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, retryCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, PLAN_COMPLETION_PROTECTED_FIELDS, RELATION_HYDRATION_CONCURRENCY = 6;
|
|
7290
|
+
var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, PLAN_COMPLETION_PROTECTED_FIELDS, RELATION_HYDRATION_CONCURRENCY = 6;
|
|
7217
7291
|
var init_cloud_router = __esm(() => {
|
|
7218
7292
|
init_types();
|
|
7219
7293
|
init_redaction();
|
|
@@ -7239,7 +7313,9 @@ var init_cloud_router = __esm(() => {
|
|
|
7239
7313
|
];
|
|
7240
7314
|
completionCapabilityCache = new Map;
|
|
7241
7315
|
retryCapabilityCache = new Map;
|
|
7316
|
+
taskCreatorCapabilityCache = new Map;
|
|
7242
7317
|
gitRefCapabilityCache = new Map;
|
|
7318
|
+
remoteCommandCapabilityCache = new Map;
|
|
7243
7319
|
SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
|
|
7244
7320
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
7245
7321
|
listTagsCapabilityCache = new Map;
|
|
@@ -7264,23 +7340,38 @@ function applyTodosCliAuthorityEnvironment(authority, env = process.env) {
|
|
|
7264
7340
|
env.HASNA_TODOS_STORAGE_MODE = "sqlite";
|
|
7265
7341
|
env.TODOS_STORAGE_MODE = "sqlite";
|
|
7266
7342
|
}
|
|
7267
|
-
function isTodosCliCommandVisibleForRoute(command, route) {
|
|
7343
|
+
function isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities = new Set) {
|
|
7268
7344
|
if (route === "local")
|
|
7269
7345
|
return true;
|
|
7270
7346
|
const owner = COMMAND_CAPABILITY_MATRIX.get(command);
|
|
7271
7347
|
if (!owner)
|
|
7272
7348
|
return true;
|
|
7273
|
-
|
|
7349
|
+
if (owner === "local-only")
|
|
7350
|
+
return false;
|
|
7351
|
+
const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(command);
|
|
7352
|
+
return requiredCapability ? remoteCapabilities.has(requiredCapability) : true;
|
|
7274
7353
|
}
|
|
7275
|
-
function applyTodosCliHelpVisibility(program2, route) {
|
|
7354
|
+
function applyTodosCliHelpVisibility(program2, route, remoteCapabilities = new Set) {
|
|
7276
7355
|
if (route === "local")
|
|
7277
7356
|
return;
|
|
7278
7357
|
program2.configureHelp({
|
|
7279
7358
|
visibleCommands(command) {
|
|
7280
|
-
return Help.prototype.visibleCommands.call(this, command).filter((subcommand) => isTodosCliCommandVisibleForRoute(subcommand.name(), route));
|
|
7359
|
+
return Help.prototype.visibleCommands.call(this, command).filter((subcommand) => isTodosCliCommandVisibleForRoute(subcommand.name(), route, remoteCapabilities));
|
|
7281
7360
|
}
|
|
7282
7361
|
});
|
|
7283
7362
|
}
|
|
7363
|
+
function getUnavailableTodosCliRemoteMetadataCommand(route, remoteCapabilities = new Set, args = []) {
|
|
7364
|
+
if (route === "local")
|
|
7365
|
+
return null;
|
|
7366
|
+
const invocation = parseInvocation([...args]);
|
|
7367
|
+
if (!isMetadataInvocation([...args], invocation))
|
|
7368
|
+
return null;
|
|
7369
|
+
const requestedCommand = invocation.command === "help" ? positionalArgs(invocation.commandArgs)[0] : invocation.command;
|
|
7370
|
+
if (!requestedCommand)
|
|
7371
|
+
return null;
|
|
7372
|
+
const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(requestedCommand);
|
|
7373
|
+
return requiredCapability && !remoteCapabilities.has(requiredCapability) ? requestedCommand : null;
|
|
7374
|
+
}
|
|
7284
7375
|
function parseInvocation(args) {
|
|
7285
7376
|
const localTokens = [];
|
|
7286
7377
|
const globalOptions = new Set;
|
|
@@ -7337,6 +7428,12 @@ function hasOption(args, option) {
|
|
|
7337
7428
|
function positionalArgs(args) {
|
|
7338
7429
|
return args.filter((arg) => !arg.startsWith("-"));
|
|
7339
7430
|
}
|
|
7431
|
+
function isHostedLocalInvocation(invocation) {
|
|
7432
|
+
if (invocation.command !== "redaction")
|
|
7433
|
+
return false;
|
|
7434
|
+
const subcommand = positionalArgs(invocation.commandArgs)[0];
|
|
7435
|
+
return subcommand === "status" || subcommand === "add" || subcommand === "scan";
|
|
7436
|
+
}
|
|
7340
7437
|
function isBundledStaticInvocation(invocation) {
|
|
7341
7438
|
const command = invocation.command;
|
|
7342
7439
|
if (!command)
|
|
@@ -7492,7 +7589,7 @@ function assertInvocationRoutable(invocation) {
|
|
|
7492
7589
|
function assertRemoteCommandSupported(invocation, owner) {
|
|
7493
7590
|
const command = invocation.command;
|
|
7494
7591
|
if (command && owner === "local-only") {
|
|
7495
|
-
throw new Error(`
|
|
7592
|
+
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: \`${command}\` is a local-only command and the Todos /v1 authority does not ` + "serve it; local SQLite fallback is disabled. Run `todos --help` to see the commands this route supports.");
|
|
7496
7593
|
}
|
|
7497
7594
|
if (!command || !commandSupportsRemote(invocation)) {
|
|
7498
7595
|
const blame = command ? disqualifyingArgument(invocation) : null;
|
|
@@ -7511,7 +7608,7 @@ function initializeTodosCliAuthority(args = process.argv.slice(2), env = process
|
|
|
7511
7608
|
return { route: "remote-diagnostic", v1_base_url: status.v1_base_url };
|
|
7512
7609
|
}
|
|
7513
7610
|
const owner = assertInvocationRoutable(invocation);
|
|
7514
|
-
if (owner === "local-only") {
|
|
7611
|
+
if (owner === "local-only" && isHostedLocalInvocation(invocation)) {
|
|
7515
7612
|
return { route: "local", v1_base_url: null, selected_by: "local-only-command" };
|
|
7516
7613
|
}
|
|
7517
7614
|
assertRemoteCommandSupported(invocation, owner);
|
|
@@ -7521,7 +7618,7 @@ function initializeTodosCliAuthority(args = process.argv.slice(2), env = process
|
|
|
7521
7618
|
}
|
|
7522
7619
|
return { route: "remote-http", v1_base_url: client.baseUrl };
|
|
7523
7620
|
}
|
|
7524
|
-
var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, BUNDLED_STATIC_COMMANDS, BUNDLED_STATIC_STORE_BACKED_OPTIONS, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS, dropIt = (blame) => ({ blame, remedy: "re-run without it" });
|
|
7621
|
+
var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, BUNDLED_STATIC_COMMANDS, BUNDLED_STATIC_STORE_BACKED_OPTIONS, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, REMOTE_COMMAND_CAPABILITIES, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS, dropIt = (blame) => ({ blame, remedy: "re-run without it" });
|
|
7525
7622
|
var init_stage_a = __esm(() => {
|
|
7526
7623
|
init_esm();
|
|
7527
7624
|
init_cloud_router();
|
|
@@ -7533,6 +7630,7 @@ var init_stage_a = __esm(() => {
|
|
|
7533
7630
|
"agent-update",
|
|
7534
7631
|
"agents",
|
|
7535
7632
|
"agents-normalize",
|
|
7633
|
+
"ai",
|
|
7536
7634
|
"api-keys",
|
|
7537
7635
|
"approvals",
|
|
7538
7636
|
"approve",
|
|
@@ -7740,6 +7838,7 @@ var init_stage_a = __esm(() => {
|
|
|
7740
7838
|
"add",
|
|
7741
7839
|
"agent",
|
|
7742
7840
|
"agents",
|
|
7841
|
+
"ai",
|
|
7743
7842
|
"approve",
|
|
7744
7843
|
"assign",
|
|
7745
7844
|
"bulk",
|
|
@@ -7793,6 +7892,16 @@ var init_stage_a = __esm(() => {
|
|
|
7793
7892
|
"untag",
|
|
7794
7893
|
"update"
|
|
7795
7894
|
]);
|
|
7895
|
+
REMOTE_COMMAND_CAPABILITIES = new Map([
|
|
7896
|
+
["stale-lock-handoff", "stale-lock-handoff"]
|
|
7897
|
+
]);
|
|
7898
|
+
for (const [canonical, aliases] of Object.entries(TODOS_CLI_COMMAND_ALIASES)) {
|
|
7899
|
+
const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(canonical);
|
|
7900
|
+
if (!requiredCapability)
|
|
7901
|
+
continue;
|
|
7902
|
+
for (const alias of aliases)
|
|
7903
|
+
REMOTE_COMMAND_CAPABILITIES.set(alias, requiredCapability);
|
|
7904
|
+
}
|
|
7796
7905
|
COMMAND_CAPABILITY_MATRIX = new Map;
|
|
7797
7906
|
for (const command of REGISTERED_CANONICAL_COMMANDS)
|
|
7798
7907
|
COMMAND_CAPABILITY_MATRIX.set(command, "local-only");
|
|
@@ -12804,7 +12913,7 @@ __export(exports_helpers, {
|
|
|
12804
12913
|
});
|
|
12805
12914
|
import chalk from "chalk";
|
|
12806
12915
|
import { execSync } from "child_process";
|
|
12807
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as
|
|
12916
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, writeSync } from "fs";
|
|
12808
12917
|
import { tmpdir } from "os";
|
|
12809
12918
|
import { dirname as dirname3, join as join4, resolve as resolve3, sep } from "path";
|
|
12810
12919
|
function jsonModeRequested(argv = process.argv) {
|
|
@@ -12885,7 +12994,7 @@ function readCloudTaskIdCache() {
|
|
|
12885
12994
|
if (!path || !existsSync5(path))
|
|
12886
12995
|
return { version: 1, tasks: [] };
|
|
12887
12996
|
try {
|
|
12888
|
-
const parsed = JSON.parse(
|
|
12997
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
12889
12998
|
if (parsed.version !== 1 || !Array.isArray(parsed.tasks))
|
|
12890
12999
|
return { version: 1, tasks: [] };
|
|
12891
13000
|
return {
|
|
@@ -16438,7 +16547,7 @@ function requireCanonicalLockVersion(value) {
|
|
|
16438
16547
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must be the exact canonical locked_at timestamp (YYYY-MM-DDTHH:mm:ss.sssZ)", { field: "expected_lock_version" });
|
|
16439
16548
|
}
|
|
16440
16549
|
const parsed = Date.parse(value);
|
|
16441
|
-
if (Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
16550
|
+
if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
16442
16551
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
|
|
16443
16552
|
}
|
|
16444
16553
|
return value;
|
|
@@ -16463,9 +16572,6 @@ function prepareStaleLockHandoff(input, options = {}) {
|
|
|
16463
16572
|
if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
|
|
16464
16573
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
|
|
16465
16574
|
}
|
|
16466
|
-
if (canonicalAgentRef(expectedHolder) === canonicalAgentRef(newHolder)) {
|
|
16467
|
-
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "new_holder must differ from expected_holder", { field: "new_holder" });
|
|
16468
|
-
}
|
|
16469
16575
|
const operationTimestamp = options.now ?? new Date().toISOString();
|
|
16470
16576
|
if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
|
|
16471
16577
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
|
|
@@ -19201,7 +19307,7 @@ var init_boards = __esm(() => {
|
|
|
19201
19307
|
|
|
19202
19308
|
// src/lib/artifact-store.ts
|
|
19203
19309
|
import { createHash as createHash5 } from "crypto";
|
|
19204
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as
|
|
19310
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
19205
19311
|
import { basename as basename2, dirname as dirname5, join as join7, resolve as resolve8 } from "path";
|
|
19206
19312
|
import { tmpdir as tmpdir3 } from "os";
|
|
19207
19313
|
function isInMemoryDb2(path) {
|
|
@@ -19268,7 +19374,7 @@ function storeArtifactContent(input) {
|
|
|
19268
19374
|
const sourceStat = statSync2(sourcePath);
|
|
19269
19375
|
if (!sourceStat.isFile())
|
|
19270
19376
|
throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
|
|
19271
|
-
const sourceBuffer =
|
|
19377
|
+
const sourceBuffer = readFileSync4(sourcePath);
|
|
19272
19378
|
const sourceSha = sha2562(sourceBuffer);
|
|
19273
19379
|
const textLike = isTextLike(sourceBuffer, input.path);
|
|
19274
19380
|
let storedBuffer = sourceBuffer;
|
|
@@ -19355,7 +19461,7 @@ function verifyStoredArtifact(input) {
|
|
|
19355
19461
|
message: "stored artifact content is missing"
|
|
19356
19462
|
};
|
|
19357
19463
|
}
|
|
19358
|
-
const buffer =
|
|
19464
|
+
const buffer = readFileSync4(storedPath);
|
|
19359
19465
|
const actualSha = sha2562(buffer);
|
|
19360
19466
|
const actualSize = buffer.length;
|
|
19361
19467
|
const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
|
|
@@ -19375,7 +19481,7 @@ function exportStoredArtifactContent(input) {
|
|
|
19375
19481
|
const report = verifyStoredArtifact(input);
|
|
19376
19482
|
if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
|
|
19377
19483
|
return null;
|
|
19378
|
-
const content =
|
|
19484
|
+
const content = readFileSync4(artifactStorePath(report.relative_path));
|
|
19379
19485
|
return {
|
|
19380
19486
|
artifact_id: input.id,
|
|
19381
19487
|
sha256: report.actual_sha256,
|
|
@@ -20934,7 +21040,7 @@ function normalizeAgentNameInput(name) {
|
|
|
20934
21040
|
}
|
|
20935
21041
|
|
|
20936
21042
|
// src/lib/assignee-validation.ts
|
|
20937
|
-
import { readFileSync as
|
|
21043
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
20938
21044
|
import { homedir as homedir3 } from "os";
|
|
20939
21045
|
import { join as join8 } from "path";
|
|
20940
21046
|
function describeAssigneeFilter(input, ctx) {
|
|
@@ -20959,7 +21065,7 @@ function defaultSeatRosterPath() {
|
|
|
20959
21065
|
}
|
|
20960
21066
|
function loadSeatSlugs(path = defaultSeatRosterPath()) {
|
|
20961
21067
|
try {
|
|
20962
|
-
const parsed = JSON.parse(
|
|
21068
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
20963
21069
|
const slugs = new Set;
|
|
20964
21070
|
for (const agent of parsed.agents ?? []) {
|
|
20965
21071
|
if (typeof agent?.slug === "string" && agent.slug.trim()) {
|
|
@@ -22114,6 +22220,21 @@ function warnMissingProject(resolvedProjectId, optedOut) {
|
|
|
22114
22220
|
return;
|
|
22115
22221
|
console.error(chalk3.yellow("Warning: task filed with no project \u2014 it will not appear in any project list, per-seat report, or drain. " + "Pass --project <id-or-slug>, or --no-project if filing it globally is deliberate."));
|
|
22116
22222
|
}
|
|
22223
|
+
function warnTaskRouting(task, deliberatelyUnassigned) {
|
|
22224
|
+
const ownerless = !task.assigned_to && !deliberatelyUnassigned;
|
|
22225
|
+
const unattributable = !task.created_by;
|
|
22226
|
+
if (ownerless && unattributable) {
|
|
22227
|
+
console.error(chalk3.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
|
|
22228
|
+
return;
|
|
22229
|
+
}
|
|
22230
|
+
if (ownerless) {
|
|
22231
|
+
console.error(chalk3.yellow("Warning: task is ownerless \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
|
|
22232
|
+
return;
|
|
22233
|
+
}
|
|
22234
|
+
if (unattributable) {
|
|
22235
|
+
console.error(chalk3.yellow("Warning: task is unattributable \u2014 created_by will be recorded as null. Export TODOS_AGENT_ID=<name> for this session, or pass --agent <agent>, to record who filed it."));
|
|
22236
|
+
}
|
|
22237
|
+
}
|
|
22117
22238
|
function parseStatus(value) {
|
|
22118
22239
|
if (!value)
|
|
22119
22240
|
return;
|
|
@@ -22287,6 +22408,27 @@ async function computeCloudReparent(cloud, current, opts) {
|
|
|
22287
22408
|
patch.task_list_id = taskListId;
|
|
22288
22409
|
return patch;
|
|
22289
22410
|
}
|
|
22411
|
+
async function cloudUpdateTaskWithVerifiedReparent(cloud, taskId, updatePatch, reparent) {
|
|
22412
|
+
const acknowledged = await cloudUpdateTask(cloud, taskId, updatePatch);
|
|
22413
|
+
if (reparent.project_id === undefined && reparent.task_list_id === undefined) {
|
|
22414
|
+
return acknowledged;
|
|
22415
|
+
}
|
|
22416
|
+
const persisted = await cloudGetTask(cloud, taskId);
|
|
22417
|
+
if (!persisted) {
|
|
22418
|
+
throw new Error(`TASK_REPARENT_PERSISTENCE_UNVERIFIED: PATCH /v1/tasks/${taskId} was acknowledged, ` + "but the authoritative read-back did not return the task.");
|
|
22419
|
+
}
|
|
22420
|
+
const mismatches = [];
|
|
22421
|
+
if (reparent.project_id !== undefined && persisted.project_id !== reparent.project_id) {
|
|
22422
|
+
mismatches.push(`project_id expected ${reparent.project_id}, received ${persisted.project_id ?? "null"}`);
|
|
22423
|
+
}
|
|
22424
|
+
if (reparent.task_list_id !== undefined && (persisted.task_list_id ?? null) !== reparent.task_list_id) {
|
|
22425
|
+
mismatches.push(`task_list_id expected ${reparent.task_list_id ?? "null"}, received ${persisted.task_list_id ?? "null"}`);
|
|
22426
|
+
}
|
|
22427
|
+
if (mismatches.length > 0) {
|
|
22428
|
+
throw new Error(`TASK_REPARENT_PERSISTENCE_UNVERIFIED: PATCH /v1/tasks/${taskId} was acknowledged, ` + `but authoritative read-back disagreed (${mismatches.join("; ")}).`);
|
|
22429
|
+
}
|
|
22430
|
+
return persisted;
|
|
22431
|
+
}
|
|
22290
22432
|
function computeLocalReparent(current, opts) {
|
|
22291
22433
|
const targetProjectId = opts.projectRef ? resolveProjectIdOrSlug(opts.projectRef) : undefined;
|
|
22292
22434
|
const scope = targetProjectId ?? current.project_id ?? null;
|
|
@@ -22317,12 +22459,6 @@ function registerTaskCommands(program2) {
|
|
|
22317
22459
|
const router = resolveWritableIdentity(opts.createdBy || globalOpts.agent);
|
|
22318
22460
|
const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`) : undefined;
|
|
22319
22461
|
const assignee = requestedAssign || (opts.unassigned ? undefined : router.agent_id || undefined);
|
|
22320
|
-
const ownerless = !assignee && !opts.unassigned;
|
|
22321
|
-
if (ownerless) {
|
|
22322
|
-
console.error(chalk3.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
|
|
22323
|
-
} else if (!router.agent_id) {
|
|
22324
|
-
console.error(chalk3.yellow("Warning: task is unattributable \u2014 created_by will be recorded as null. Export TODOS_AGENT_ID=<name> for this session, or pass --agent <agent>, to record who filed it."));
|
|
22325
|
-
}
|
|
22326
22462
|
const cloud = getTodosCloudClient();
|
|
22327
22463
|
if (cloud) {
|
|
22328
22464
|
let task3;
|
|
@@ -22338,7 +22474,7 @@ function registerTaskCommands(program2) {
|
|
|
22338
22474
|
if (opts.plan && !cloudPlan) {
|
|
22339
22475
|
throw new Error(`Could not resolve plan ID or slug: ${opts.plan}`);
|
|
22340
22476
|
}
|
|
22341
|
-
|
|
22477
|
+
const taskInput = {
|
|
22342
22478
|
title,
|
|
22343
22479
|
description: opts.description,
|
|
22344
22480
|
priority: parsePriority(opts.priority),
|
|
@@ -22359,10 +22495,12 @@ function registerTaskCommands(program2) {
|
|
|
22359
22495
|
recurrence_rule: opts.recurrence,
|
|
22360
22496
|
due_at: opts.due ? opts.due.length === 10 ? opts.due + "T00:00:00.000Z" : opts.due : undefined,
|
|
22361
22497
|
reason: opts.reason
|
|
22362
|
-
}
|
|
22498
|
+
};
|
|
22499
|
+
task3 = await cloudCreateTask(cloud, taskInput, opts.createdBy && router.agent_id ? { expectedCreatedBy: router.agent_id } : undefined);
|
|
22363
22500
|
} catch (e) {
|
|
22364
22501
|
handleError(e);
|
|
22365
22502
|
}
|
|
22503
|
+
warnTaskRouting(task3, Boolean(opts.unassigned));
|
|
22366
22504
|
if (globalOpts.json) {
|
|
22367
22505
|
output(task3, true);
|
|
22368
22506
|
} else {
|
|
@@ -22410,6 +22548,7 @@ function registerTaskCommands(program2) {
|
|
|
22410
22548
|
} catch (e) {
|
|
22411
22549
|
handleError(e);
|
|
22412
22550
|
}
|
|
22551
|
+
warnTaskRouting(task2, Boolean(opts.unassigned));
|
|
22413
22552
|
if (globalOpts.json) {
|
|
22414
22553
|
output(task2, true);
|
|
22415
22554
|
} else {
|
|
@@ -22827,8 +22966,10 @@ function registerTaskCommands(program2) {
|
|
|
22827
22966
|
} else {
|
|
22828
22967
|
const resolvedId = resolveTaskId(id);
|
|
22829
22968
|
task2 = applyLocalCommentPage(getTaskWithRelations(resolvedId), page);
|
|
22830
|
-
|
|
22831
|
-
|
|
22969
|
+
if (task2) {
|
|
22970
|
+
const { getTaskGitRefs: getTaskGitRefs2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
22971
|
+
task2.git_refs = getTaskGitRefs2(resolvedId);
|
|
22972
|
+
}
|
|
22832
22973
|
}
|
|
22833
22974
|
if (!task2) {
|
|
22834
22975
|
handleError(new Error(`Task not found: ${id}`));
|
|
@@ -23170,7 +23311,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
23170
23311
|
listRef: opts.list,
|
|
23171
23312
|
clearList: opts.clearList
|
|
23172
23313
|
});
|
|
23173
|
-
|
|
23314
|
+
const updatePatch = {
|
|
23174
23315
|
title: opts.title,
|
|
23175
23316
|
description: opts.description,
|
|
23176
23317
|
status: parseStatus(opts.status),
|
|
@@ -23186,7 +23327,8 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
23186
23327
|
due_at: opts.due !== undefined ? opts.due === "" ? null : opts.due.length === 10 ? opts.due + "T00:00:00.000Z" : opts.due : undefined,
|
|
23187
23328
|
recurrence_rule: opts.recurrence !== undefined ? opts.recurrence === "" ? null : opts.recurrence : undefined,
|
|
23188
23329
|
requires_approval: opts.clearApproval ? false : opts.approval !== undefined ? true : undefined
|
|
23189
|
-
}
|
|
23330
|
+
};
|
|
23331
|
+
task3 = await cloudUpdateTaskWithVerifiedReparent(cloud, currentId, updatePatch, reparent2);
|
|
23190
23332
|
} catch (e) {
|
|
23191
23333
|
handleError(e);
|
|
23192
23334
|
}
|
|
@@ -23265,7 +23407,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
23265
23407
|
if (reparent2.project_id === undefined && reparent2.task_list_id === undefined) {
|
|
23266
23408
|
throw new Error("Nothing to move: the task is already in the requested project/list.");
|
|
23267
23409
|
}
|
|
23268
|
-
task3 = await
|
|
23410
|
+
task3 = await cloudUpdateTaskWithVerifiedReparent(cloud, currentId, reparent2, reparent2);
|
|
23269
23411
|
} catch (e) {
|
|
23270
23412
|
handleError(e);
|
|
23271
23413
|
}
|
|
@@ -23704,7 +23846,7 @@ var init_task_commands = __esm(() => {
|
|
|
23704
23846
|
});
|
|
23705
23847
|
|
|
23706
23848
|
// src/lib/plan-artifacts.ts
|
|
23707
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as
|
|
23849
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
23708
23850
|
import { join as join9, resolve as resolve10 } from "path";
|
|
23709
23851
|
function assertSafePathSegment(value, label) {
|
|
23710
23852
|
const trimmed = value.trim();
|
|
@@ -23943,7 +24085,7 @@ function readPlanArtifact(plan, db) {
|
|
|
23943
24085
|
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
23944
24086
|
if (!path)
|
|
23945
24087
|
return null;
|
|
23946
|
-
const markdown =
|
|
24088
|
+
const markdown = readFileSync6(path, "utf8");
|
|
23947
24089
|
return {
|
|
23948
24090
|
path,
|
|
23949
24091
|
markdown,
|
|
@@ -23967,7 +24109,7 @@ function inspectPlanArtifact(plan, db) {
|
|
|
23967
24109
|
};
|
|
23968
24110
|
}
|
|
23969
24111
|
try {
|
|
23970
|
-
const artifact = parsePlanArtifactMarkdown(
|
|
24112
|
+
const artifact = parsePlanArtifactMarkdown(readFileSync6(path, "utf8"));
|
|
23971
24113
|
return {
|
|
23972
24114
|
path,
|
|
23973
24115
|
exists: true,
|
|
@@ -26543,13 +26685,13 @@ function registerPlanTemplateCommands(program2) {
|
|
|
26543
26685
|
});
|
|
26544
26686
|
program2.command("template-import [file]").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file (alternative to positional arg)").action(async (file, opts) => {
|
|
26545
26687
|
const globalOpts = program2.opts();
|
|
26546
|
-
const { readFileSync:
|
|
26688
|
+
const { readFileSync: readFileSync7 } = await import("fs");
|
|
26547
26689
|
try {
|
|
26548
26690
|
const filePath = file || opts.file;
|
|
26549
26691
|
if (!filePath) {
|
|
26550
26692
|
handleError(new Error("Provide a file path: todos template-import <file> or --file <path>"));
|
|
26551
26693
|
}
|
|
26552
|
-
const content =
|
|
26694
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
26553
26695
|
const json = JSON.parse(content);
|
|
26554
26696
|
const cloud = getTodosCloudClient();
|
|
26555
26697
|
const template = cloud ? await cloudCreateTemplate(cloud, json) : (await Promise.resolve().then(() => (init_templates(), exports_templates))).importTemplate(json);
|
|
@@ -26939,6 +27081,8 @@ function searchTaskEntities(filters, db) {
|
|
|
26939
27081
|
return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
|
|
26940
27082
|
}
|
|
26941
27083
|
function searchProjects(filters, db) {
|
|
27084
|
+
if (filters.agent_id)
|
|
27085
|
+
return [];
|
|
26942
27086
|
const params = [];
|
|
26943
27087
|
let sql = "SELECT * FROM projects WHERE 1=1";
|
|
26944
27088
|
if (filters.project_id) {
|
|
@@ -27144,7 +27288,7 @@ var init_saved_search_views = __esm(() => {
|
|
|
27144
27288
|
});
|
|
27145
27289
|
|
|
27146
27290
|
// src/lib/claude-tasks.ts
|
|
27147
|
-
import { existsSync as existsSync11, readFileSync as
|
|
27291
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
27148
27292
|
import { join as join11 } from "path";
|
|
27149
27293
|
function getTaskListDir(taskListId) {
|
|
27150
27294
|
return join11(HOME, ".claude", "tasks", taskListId);
|
|
@@ -27168,7 +27312,7 @@ function readPrefixCounter(dir) {
|
|
|
27168
27312
|
const path = join11(dir, ".prefix-counter");
|
|
27169
27313
|
if (!existsSync11(path))
|
|
27170
27314
|
return 0;
|
|
27171
|
-
const val = parseInt(
|
|
27315
|
+
const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
|
|
27172
27316
|
return isNaN(val) ? 0 : val;
|
|
27173
27317
|
}
|
|
27174
27318
|
function writePrefixCounter(dir, value) {
|
|
@@ -27940,7 +28084,7 @@ __export(exports_project_bootstrap, {
|
|
|
27940
28084
|
discoverProjectWorkspace: () => discoverProjectWorkspace,
|
|
27941
28085
|
bootstrapProject: () => bootstrapProject
|
|
27942
28086
|
});
|
|
27943
|
-
import { existsSync as existsSync13, readFileSync as
|
|
28087
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
27944
28088
|
import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
|
|
27945
28089
|
function safeStat(path) {
|
|
27946
28090
|
try {
|
|
@@ -27974,7 +28118,7 @@ function readPackageJson(path) {
|
|
|
27974
28118
|
if (!existsSync13(file))
|
|
27975
28119
|
return null;
|
|
27976
28120
|
try {
|
|
27977
|
-
const parsed = JSON.parse(
|
|
28121
|
+
const parsed = JSON.parse(readFileSync8(file, "utf-8"));
|
|
27978
28122
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
27979
28123
|
} catch {
|
|
27980
28124
|
return null;
|
|
@@ -28327,7 +28471,7 @@ __export(exports_extract, {
|
|
|
28327
28471
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
28328
28472
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
28329
28473
|
});
|
|
28330
|
-
import { existsSync as existsSync14, readFileSync as
|
|
28474
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
|
|
28331
28475
|
import { createHash as createHash7 } from "crypto";
|
|
28332
28476
|
import { relative as relative3, resolve as resolve12, join as join13 } from "path";
|
|
28333
28477
|
function stableHash(value) {
|
|
@@ -28342,7 +28486,7 @@ function readGitignorePatterns(basePath) {
|
|
|
28342
28486
|
if (!existsSync14(gitignorePath))
|
|
28343
28487
|
return [];
|
|
28344
28488
|
try {
|
|
28345
|
-
return
|
|
28489
|
+
return readFileSync9(gitignorePath, "utf-8").split(`
|
|
28346
28490
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
|
|
28347
28491
|
} catch {
|
|
28348
28492
|
return [];
|
|
@@ -28483,7 +28627,7 @@ function buildCodebaseIndex(options) {
|
|
|
28483
28627
|
for (const file of files) {
|
|
28484
28628
|
const fullPath = statSync5(basePath).isFile() ? basePath : join13(basePath, file);
|
|
28485
28629
|
try {
|
|
28486
|
-
const source =
|
|
28630
|
+
const source = readFileSync9(fullPath, "utf-8");
|
|
28487
28631
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
28488
28632
|
indexed.push({
|
|
28489
28633
|
file: relPath,
|
|
@@ -28514,7 +28658,7 @@ function extractTodos(options, db) {
|
|
|
28514
28658
|
for (const file of files) {
|
|
28515
28659
|
const fullPath = statSync5(basePath).isFile() ? basePath : join13(basePath, file);
|
|
28516
28660
|
try {
|
|
28517
|
-
const source =
|
|
28661
|
+
const source = readFileSync9(fullPath, "utf-8");
|
|
28518
28662
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
28519
28663
|
const comments = extractFromSource(source, relPath, tags);
|
|
28520
28664
|
allComments.push(...comments);
|
|
@@ -29844,7 +29988,7 @@ __export(exports_project_commands, {
|
|
|
29844
29988
|
registerProjectCommands: () => registerProjectCommands
|
|
29845
29989
|
});
|
|
29846
29990
|
import chalk5 from "chalk";
|
|
29847
|
-
import { readFileSync as
|
|
29991
|
+
import { readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
29848
29992
|
import { basename as basename5, resolve as resolve13, sep as sep3 } from "path";
|
|
29849
29993
|
function collectOption(value, previous = []) {
|
|
29850
29994
|
return [...previous, value];
|
|
@@ -30057,7 +30201,7 @@ function registerProjectCommands(program2) {
|
|
|
30057
30201
|
handleError(new Error(`Comment file "${opts.file}" must be a regular file.`));
|
|
30058
30202
|
}
|
|
30059
30203
|
try {
|
|
30060
|
-
content =
|
|
30204
|
+
content = readFileSync10(commentFilePath, "utf8");
|
|
30061
30205
|
} catch (error) {
|
|
30062
30206
|
handleError(new Error(`Unable to read comment file "${opts.file}".`, { cause: error }));
|
|
30063
30207
|
}
|
|
@@ -30836,10 +30980,10 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30836
30980
|
program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
|
|
30837
30981
|
const globalOpts = program2.opts();
|
|
30838
30982
|
try {
|
|
30839
|
-
const { readFileSync:
|
|
30983
|
+
const { readFileSync: readFileSync11 } = await import("fs");
|
|
30840
30984
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
30841
30985
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
30842
|
-
const parsed = JSON.parse(
|
|
30986
|
+
const parsed = JSON.parse(readFileSync11(resolve13(file), "utf-8"));
|
|
30843
30987
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
30844
30988
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
30845
30989
|
})() : parsed;
|
|
@@ -30873,9 +31017,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30873
31017
|
program2.command("todos-md-import <file>").alias("markdown-import").alias("import-md").description("Dry-run or apply a local todos.md Markdown import").option("--apply", "Apply the import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge embedded bridge task conflicts while preserving local divergent fields").action(async (file, opts) => {
|
|
30874
31018
|
const globalOpts = program2.opts();
|
|
30875
31019
|
try {
|
|
30876
|
-
const { readFileSync:
|
|
31020
|
+
const { readFileSync: readFileSync11 } = await import("fs");
|
|
30877
31021
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
30878
|
-
const result = importTodosMarkdown2(
|
|
31022
|
+
const result = importTodosMarkdown2(readFileSync11(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
30879
31023
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
30880
31024
|
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve13(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
|
|
30881
31025
|
if (globalOpts.json) {
|
|
@@ -31483,6 +31627,2449 @@ var init_agent_commands = __esm(() => {
|
|
|
31483
31627
|
init_cloud_router();
|
|
31484
31628
|
});
|
|
31485
31629
|
|
|
31630
|
+
// src/ai.ts
|
|
31631
|
+
function isRecord(value) {
|
|
31632
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31633
|
+
}
|
|
31634
|
+
function isJsonObject(value) {
|
|
31635
|
+
if (!isRecord(value))
|
|
31636
|
+
return false;
|
|
31637
|
+
const prototype = Object.getPrototypeOf(value);
|
|
31638
|
+
return prototype === Object.prototype || prototype === null;
|
|
31639
|
+
}
|
|
31640
|
+
function prototypeDefinesToJson(value) {
|
|
31641
|
+
let prototype = Object.getPrototypeOf(value);
|
|
31642
|
+
while (prototype !== null) {
|
|
31643
|
+
if (Object.getOwnPropertyDescriptor(prototype, "toJSON") !== undefined)
|
|
31644
|
+
return true;
|
|
31645
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
31646
|
+
}
|
|
31647
|
+
return false;
|
|
31648
|
+
}
|
|
31649
|
+
function isStableJsonArray(value, ancestors, depth) {
|
|
31650
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || prototypeDefinesToJson(value)) {
|
|
31651
|
+
return false;
|
|
31652
|
+
}
|
|
31653
|
+
const keys = Reflect.ownKeys(value);
|
|
31654
|
+
if (keys.length !== value.length + 1)
|
|
31655
|
+
return false;
|
|
31656
|
+
for (const key of keys) {
|
|
31657
|
+
if (key === "length")
|
|
31658
|
+
continue;
|
|
31659
|
+
if (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key))
|
|
31660
|
+
return false;
|
|
31661
|
+
const index = Number(key);
|
|
31662
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= value.length)
|
|
31663
|
+
return false;
|
|
31664
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
31665
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
31666
|
+
return false;
|
|
31667
|
+
if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
|
|
31668
|
+
return false;
|
|
31669
|
+
}
|
|
31670
|
+
return true;
|
|
31671
|
+
}
|
|
31672
|
+
function isStableJsonObject(value, ancestors, depth) {
|
|
31673
|
+
if (!isJsonObject(value) || prototypeDefinesToJson(value))
|
|
31674
|
+
return false;
|
|
31675
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
31676
|
+
if (typeof key !== "string")
|
|
31677
|
+
return false;
|
|
31678
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
31679
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
31680
|
+
return false;
|
|
31681
|
+
if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
|
|
31682
|
+
return false;
|
|
31683
|
+
}
|
|
31684
|
+
return true;
|
|
31685
|
+
}
|
|
31686
|
+
function isOneOf(value, values) {
|
|
31687
|
+
return typeof value === "string" && values.includes(value);
|
|
31688
|
+
}
|
|
31689
|
+
function selectedString(...values) {
|
|
31690
|
+
for (const value of values) {
|
|
31691
|
+
if (value === undefined || value === null)
|
|
31692
|
+
continue;
|
|
31693
|
+
const trimmed = value.trim();
|
|
31694
|
+
if (trimmed)
|
|
31695
|
+
return trimmed;
|
|
31696
|
+
}
|
|
31697
|
+
return null;
|
|
31698
|
+
}
|
|
31699
|
+
function parseEnum(value, values, field, fallback) {
|
|
31700
|
+
if (value === null)
|
|
31701
|
+
return fallback;
|
|
31702
|
+
if (isOneOf(value, values))
|
|
31703
|
+
return value;
|
|
31704
|
+
throw new TodosAiContractError("invalid_configuration", `${field} must be one of: ${values.join(", ")}`);
|
|
31705
|
+
}
|
|
31706
|
+
function parseBoundedInteger(value, field, fallback, min, max) {
|
|
31707
|
+
if (value === undefined || value === null || value === "")
|
|
31708
|
+
return fallback;
|
|
31709
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
31710
|
+
if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
|
|
31711
|
+
throw new TodosAiContractError("invalid_configuration", `${field} must be an integer between ${min} and ${max}`);
|
|
31712
|
+
}
|
|
31713
|
+
return parsed;
|
|
31714
|
+
}
|
|
31715
|
+
function normalizeApprovalRefs(values) {
|
|
31716
|
+
const refs = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
|
|
31717
|
+
if (refs.length > TODOS_AI_LIMITS.max_approval_refs) {
|
|
31718
|
+
throw new TodosAiContractError("invalid_configuration", `--approval may be repeated at most ${TODOS_AI_LIMITS.max_approval_refs} times`);
|
|
31719
|
+
}
|
|
31720
|
+
for (const ref of refs) {
|
|
31721
|
+
if (new TextEncoder().encode(ref).byteLength > TODOS_AI_LIMITS.max_approval_ref_bytes) {
|
|
31722
|
+
throw new TodosAiContractError("invalid_configuration", `--approval references may not exceed ${TODOS_AI_LIMITS.max_approval_ref_bytes} bytes`);
|
|
31723
|
+
}
|
|
31724
|
+
}
|
|
31725
|
+
return refs;
|
|
31726
|
+
}
|
|
31727
|
+
function resolveTodosAiCommandOptions(input) {
|
|
31728
|
+
const cli = input.cli ?? {};
|
|
31729
|
+
const config = input.config ?? {};
|
|
31730
|
+
const env = input.env ?? {};
|
|
31731
|
+
const format = parseEnum(selectedString(cli.format, env["TODOS_AI_FORMAT"], config.format), TODOS_AI_FORMATS, "format", TODOS_AI_DEFAULTS.format);
|
|
31732
|
+
const configuredWriteMode = parseEnum(selectedString(cli.writeMode, env["TODOS_AI_WRITE_MODE"], config.write_mode), TODOS_AI_WRITE_MODES, "write mode", TODOS_AI_DEFAULTS.write_mode);
|
|
31733
|
+
if (cli.dryRun && cli.writeMode === "execute") {
|
|
31734
|
+
throw new TodosAiContractError("invalid_configuration", "--dry-run cannot be combined with --write-mode execute");
|
|
31735
|
+
}
|
|
31736
|
+
const writeMode = cli.dryRun ? "plan" : configuredWriteMode;
|
|
31737
|
+
const explicitApproval = cli.dryRun ? selectedString(cli.approvalMode) : selectedString(cli.approvalMode, env["TODOS_AI_APPROVAL_MODE"], config.approval_mode);
|
|
31738
|
+
const defaultApproval = writeMode === "execute" ? input.interactive ? "prompt" : "required" : "deny";
|
|
31739
|
+
const approvalMode = cli.dryRun ? parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", "deny") : parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", defaultApproval);
|
|
31740
|
+
const approvalRefs = normalizeApprovalRefs(cli.approvalRefs);
|
|
31741
|
+
if (cli.dryRun && approvalMode !== "deny") {
|
|
31742
|
+
throw new TodosAiContractError("invalid_configuration", "--dry-run requires approval mode deny");
|
|
31743
|
+
}
|
|
31744
|
+
if (writeMode !== "execute" && approvalMode !== "deny") {
|
|
31745
|
+
throw new TodosAiContractError("invalid_configuration", "approval modes other than deny require --write-mode execute");
|
|
31746
|
+
}
|
|
31747
|
+
if (writeMode === "execute" && approvalMode === "deny") {
|
|
31748
|
+
throw new TodosAiContractError("invalid_configuration", "--write-mode execute requires approval mode required, prompt, or existing");
|
|
31749
|
+
}
|
|
31750
|
+
if (!input.interactive && approvalMode === "prompt") {
|
|
31751
|
+
throw new TodosAiContractError("invalid_configuration", "approval mode prompt is unavailable in non-interactive mode");
|
|
31752
|
+
}
|
|
31753
|
+
if (approvalMode === "existing" && approvalRefs.length === 0) {
|
|
31754
|
+
throw new TodosAiContractError("invalid_configuration", "approval mode existing requires at least one --approval reference");
|
|
31755
|
+
}
|
|
31756
|
+
if (approvalMode !== "existing" && approvalRefs.length > 0) {
|
|
31757
|
+
throw new TodosAiContractError("invalid_configuration", "--approval references require --approval-mode existing");
|
|
31758
|
+
}
|
|
31759
|
+
return {
|
|
31760
|
+
provider: selectedString(cli.provider, env["TODOS_AI_PROVIDER"], config.provider),
|
|
31761
|
+
model: selectedString(cli.model, env["TODOS_AI_MODEL"], config.model),
|
|
31762
|
+
profile: selectedString(cli.profile, env["TODOS_AI_PROFILE"], config.profile),
|
|
31763
|
+
format,
|
|
31764
|
+
max_steps: parseBoundedInteger(cli.maxSteps ?? env["TODOS_AI_MAX_STEPS"] ?? config.max_steps, "max steps", TODOS_AI_DEFAULTS.max_steps, TODOS_AI_LIMITS.min_steps, TODOS_AI_LIMITS.max_steps),
|
|
31765
|
+
timeout_ms: parseBoundedInteger(cli.timeoutMs ?? env["TODOS_AI_TIMEOUT_MS"] ?? config.timeout_ms, "timeout", TODOS_AI_DEFAULTS.timeout_ms, TODOS_AI_LIMITS.min_timeout_ms, TODOS_AI_LIMITS.max_timeout_ms),
|
|
31766
|
+
write_mode: writeMode,
|
|
31767
|
+
approval_mode: approvalMode,
|
|
31768
|
+
approval_refs: approvalRefs,
|
|
31769
|
+
dry_run: cli.dryRun === true,
|
|
31770
|
+
interactive: input.interactive
|
|
31771
|
+
};
|
|
31772
|
+
}
|
|
31773
|
+
function normalizeTodosAiPrompt(value) {
|
|
31774
|
+
const prompt = value.trim();
|
|
31775
|
+
if (new TextEncoder().encode(prompt).byteLength > TODOS_AI_LIMITS.max_prompt_bytes) {
|
|
31776
|
+
throw new TodosAiContractError("invalid_input", `prompt exceeds ${TODOS_AI_LIMITS.max_prompt_bytes} bytes`);
|
|
31777
|
+
}
|
|
31778
|
+
return prompt;
|
|
31779
|
+
}
|
|
31780
|
+
function parseTodosAiJson(value, field) {
|
|
31781
|
+
if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_json_bytes) {
|
|
31782
|
+
throw new TodosAiContractError("invalid_input", `${field} exceeds ${TODOS_AI_LIMITS.max_json_bytes} bytes`);
|
|
31783
|
+
}
|
|
31784
|
+
let parsed;
|
|
31785
|
+
try {
|
|
31786
|
+
parsed = JSON.parse(value);
|
|
31787
|
+
} catch (error) {
|
|
31788
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
31789
|
+
throw new TodosAiContractError("invalid_input", `${field} must be valid JSON: ${detail}`);
|
|
31790
|
+
}
|
|
31791
|
+
if (!isTodosAiJsonValue(parsed)) {
|
|
31792
|
+
throw new TodosAiContractError("invalid_input", `${field} must contain only stable JSON values`);
|
|
31793
|
+
}
|
|
31794
|
+
return parsed;
|
|
31795
|
+
}
|
|
31796
|
+
function parseTodosAiOutputSchema(value) {
|
|
31797
|
+
const parsed = parseTodosAiJson(value, "output schema");
|
|
31798
|
+
if (!isRecord(parsed)) {
|
|
31799
|
+
throw new TodosAiContractError("invalid_input", "output schema must be a JSON object");
|
|
31800
|
+
}
|
|
31801
|
+
return parsed;
|
|
31802
|
+
}
|
|
31803
|
+
function parseTodosAiVariables(values) {
|
|
31804
|
+
if (values.length > TODOS_AI_LIMITS.max_variable_count) {
|
|
31805
|
+
throw new TodosAiContractError("invalid_input", `--var may be repeated at most ${TODOS_AI_LIMITS.max_variable_count} times`);
|
|
31806
|
+
}
|
|
31807
|
+
const variables = Object.create(null);
|
|
31808
|
+
for (const entry of values) {
|
|
31809
|
+
const separator = entry.indexOf("=");
|
|
31810
|
+
const key = separator >= 0 ? entry.slice(0, separator).trim() : "";
|
|
31811
|
+
const value = separator >= 0 ? entry.slice(separator + 1) : "";
|
|
31812
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/.test(key)) {
|
|
31813
|
+
throw new TodosAiContractError("invalid_input", `invalid --var entry ${JSON.stringify(entry)}; expected non-secret key=value`);
|
|
31814
|
+
}
|
|
31815
|
+
if (SENSITIVE_VARIABLE_KEY.test(key)) {
|
|
31816
|
+
throw new TodosAiContractError("invalid_input", `--var ${key} is credential-shaped; provide credentials through the runtime's secret configuration`);
|
|
31817
|
+
}
|
|
31818
|
+
if (Object.hasOwn(variables, key)) {
|
|
31819
|
+
throw new TodosAiContractError("invalid_input", `duplicate --var key: ${key}`);
|
|
31820
|
+
}
|
|
31821
|
+
if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_variable_value_bytes) {
|
|
31822
|
+
throw new TodosAiContractError("invalid_input", `--var ${key} exceeds ${TODOS_AI_LIMITS.max_variable_value_bytes} bytes`);
|
|
31823
|
+
}
|
|
31824
|
+
variables[key] = value;
|
|
31825
|
+
}
|
|
31826
|
+
return variables;
|
|
31827
|
+
}
|
|
31828
|
+
function isTodosAiJsonValue(value) {
|
|
31829
|
+
return isTodosAiJsonValueInternal(value, new Set, 0);
|
|
31830
|
+
}
|
|
31831
|
+
function isTodosAiUpdateTaskResult(value) {
|
|
31832
|
+
if (!isTodosAiJsonValue(value) || !isRecord(value) || !hasOnlyKeys(value, [
|
|
31833
|
+
"schema",
|
|
31834
|
+
"operation",
|
|
31835
|
+
"mode",
|
|
31836
|
+
"applied",
|
|
31837
|
+
"readback_verified",
|
|
31838
|
+
"source",
|
|
31839
|
+
"target",
|
|
31840
|
+
"changed_fields",
|
|
31841
|
+
"approval_ref",
|
|
31842
|
+
"payload_digest",
|
|
31843
|
+
"idempotency"
|
|
31844
|
+
]) || value["schema"] !== TODOS_AI_UPDATE_TASK_RESULT_SCHEMA || value["operation"] !== "update_task" || value["mode"] !== "plan" && value["mode"] !== "execute" || value["source"] !== "sqlite" && value["source"] !== "http") {
|
|
31845
|
+
return false;
|
|
31846
|
+
}
|
|
31847
|
+
const changedFields = value["changed_fields"];
|
|
31848
|
+
const target = value["target"];
|
|
31849
|
+
const idempotency = value["idempotency"];
|
|
31850
|
+
const payloadDigest = value["payload_digest"];
|
|
31851
|
+
const approvalRef = value["approval_ref"];
|
|
31852
|
+
if (!Array.isArray(changedFields) || changedFields.length === 0 || changedFields.length > TODOS_AI_UPDATE_TASK_FIELDS.length || !changedFields.every((field) => typeof field === "string" && TODOS_AI_UPDATE_TASK_FIELDS.includes(field)) || new Set(changedFields).size !== changedFields.length || !TODOS_AI_UPDATE_TASK_FIELDS.filter((field) => changedFields.includes(field)).every((field, index) => changedFields[index] === field) || !isRecord(target) || !hasOnlyKeys(target, ["task_id", "expected_version", "result_version"]) || typeof target["task_id"] !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target["task_id"]) || !Number.isSafeInteger(target["expected_version"]) || target["expected_version"] < 0 || typeof payloadDigest !== "string" || !/^[0-9a-f]{64}$/.test(payloadDigest) || approvalRef !== `todos-ai:update_task:${payloadDigest}` || !isRecord(idempotency) || !hasOnlyKeys(idempotency, ["key", "scope", "replay"]) || !boundedUtf8String(idempotency["key"], TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes) || new TextEncoder().encode(idempotency["key"]).byteLength < TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes || !/^[A-Za-z0-9._:-]+$/.test(idempotency["key"]) || idempotency["scope"] !== "run" || typeof idempotency["replay"] !== "boolean" || new TextEncoder().encode(JSON.stringify(value)).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_result_bytes) {
|
|
31853
|
+
return false;
|
|
31854
|
+
}
|
|
31855
|
+
if (value["mode"] === "plan") {
|
|
31856
|
+
return value["applied"] === false && value["readback_verified"] === false && target["result_version"] === null;
|
|
31857
|
+
}
|
|
31858
|
+
return value["applied"] === true && value["readback_verified"] === true && Number.isSafeInteger(target["result_version"]) && target["result_version"] === target["expected_version"] + 1;
|
|
31859
|
+
}
|
|
31860
|
+
function isTodosAiJsonValueInternal(value, ancestors, depth) {
|
|
31861
|
+
if (depth > 64)
|
|
31862
|
+
return false;
|
|
31863
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
31864
|
+
return true;
|
|
31865
|
+
if (typeof value === "number")
|
|
31866
|
+
return Number.isFinite(value);
|
|
31867
|
+
if (typeof value !== "object")
|
|
31868
|
+
return false;
|
|
31869
|
+
if (ancestors.has(value))
|
|
31870
|
+
return false;
|
|
31871
|
+
ancestors.add(value);
|
|
31872
|
+
try {
|
|
31873
|
+
return Array.isArray(value) ? isStableJsonArray(value, ancestors, depth) : isStableJsonObject(value, ancestors, depth);
|
|
31874
|
+
} catch {
|
|
31875
|
+
return false;
|
|
31876
|
+
} finally {
|
|
31877
|
+
ancestors.delete(value);
|
|
31878
|
+
}
|
|
31879
|
+
}
|
|
31880
|
+
function isUsage(value) {
|
|
31881
|
+
if (!isRecord(value))
|
|
31882
|
+
return false;
|
|
31883
|
+
return ["input_tokens", "output_tokens", "total_tokens"].every((key) => Number.isSafeInteger(value[key]) && value[key] >= 0);
|
|
31884
|
+
}
|
|
31885
|
+
function hasOnlyKeys(value, keys) {
|
|
31886
|
+
const allowed = new Set(keys);
|
|
31887
|
+
return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.has(key));
|
|
31888
|
+
}
|
|
31889
|
+
function boundedUtf8String(value, maximum, allowEmpty = false) {
|
|
31890
|
+
return typeof value === "string" && (allowEmpty || value.length > 0) && new TextEncoder().encode(value).byteLength <= maximum;
|
|
31891
|
+
}
|
|
31892
|
+
function isPendingInput(value) {
|
|
31893
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["prompt", "fields"]))
|
|
31894
|
+
return false;
|
|
31895
|
+
if (!boundedUtf8String(value["prompt"], TODOS_AI_LIMITS.max_pending_input_prompt_bytes)) {
|
|
31896
|
+
return false;
|
|
31897
|
+
}
|
|
31898
|
+
if (!Array.isArray(value["fields"]) || value["fields"].length === 0 || value["fields"].length > TODOS_AI_LIMITS.max_pending_input_fields) {
|
|
31899
|
+
return false;
|
|
31900
|
+
}
|
|
31901
|
+
const fields = value["fields"];
|
|
31902
|
+
const unique4 = new Set;
|
|
31903
|
+
for (const field of fields) {
|
|
31904
|
+
if (!boundedUtf8String(field, TODOS_AI_LIMITS.max_pending_input_field_bytes) || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(field) || unique4.has(field)) {
|
|
31905
|
+
return false;
|
|
31906
|
+
}
|
|
31907
|
+
unique4.add(field);
|
|
31908
|
+
}
|
|
31909
|
+
return true;
|
|
31910
|
+
}
|
|
31911
|
+
function isPendingApproval(value) {
|
|
31912
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["id", "summary", "operations"])) {
|
|
31913
|
+
return false;
|
|
31914
|
+
}
|
|
31915
|
+
if (!boundedUtf8String(value["id"], TODOS_AI_LIMITS.max_pending_approval_id_bytes) || !boundedUtf8String(value["summary"], TODOS_AI_LIMITS.max_pending_approval_summary_bytes) || !Array.isArray(value["operations"]) || value["operations"].length === 0 || value["operations"].length > TODOS_AI_LIMITS.max_pending_approval_operations || !value["operations"].every(isRecord) || !value["operations"].every(isTodosAiJsonValue)) {
|
|
31916
|
+
return false;
|
|
31917
|
+
}
|
|
31918
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength <= TODOS_AI_LIMITS.max_pending_approval_bytes;
|
|
31919
|
+
}
|
|
31920
|
+
function isAiError(value) {
|
|
31921
|
+
if (!isRecord(value))
|
|
31922
|
+
return false;
|
|
31923
|
+
const code = value["code"];
|
|
31924
|
+
const validCode = [
|
|
31925
|
+
"invalid_input",
|
|
31926
|
+
"invalid_configuration",
|
|
31927
|
+
"runtime_unavailable",
|
|
31928
|
+
"runtime_incompatible",
|
|
31929
|
+
"runtime_invalid_result",
|
|
31930
|
+
"needs_input",
|
|
31931
|
+
"needs_approval",
|
|
31932
|
+
"timeout",
|
|
31933
|
+
"interrupted",
|
|
31934
|
+
"provider_error",
|
|
31935
|
+
"tool_error",
|
|
31936
|
+
"schema_error",
|
|
31937
|
+
"internal_error"
|
|
31938
|
+
];
|
|
31939
|
+
return typeof code === "string" && validCode.includes(code) && typeof value["message"] === "string" && typeof value["retryable"] === "boolean" && (value["details"] === null || isRecord(value["details"]) && isTodosAiJsonValue(value["details"]));
|
|
31940
|
+
}
|
|
31941
|
+
function isTodosAiRunResult(value) {
|
|
31942
|
+
if (!isRecord(value) || !isTodosAiJsonValue(value))
|
|
31943
|
+
return false;
|
|
31944
|
+
const structurallyValid = value["schema_version"] === TODOS_AI_SCHEMA_VERSION && typeof value["run_id"] === "string" && Boolean(value["run_id"]) && isOneOf(value["status"], TODOS_AI_RUN_STATUSES) && (value["answer"] === null || typeof value["answer"] === "string") && isTodosAiJsonValue(value["data"]) && Number.isSafeInteger(value["steps"]) && value["steps"] >= 0 && (value["usage"] === null || isUsage(value["usage"])) && (value["pending_input"] === null || isPendingInput(value["pending_input"])) && (value["pending_approval"] === null || isPendingApproval(value["pending_approval"])) && (value["error"] === null || isAiError(value["error"]));
|
|
31945
|
+
if (!structurallyValid)
|
|
31946
|
+
return false;
|
|
31947
|
+
switch (value["status"]) {
|
|
31948
|
+
case "answered":
|
|
31949
|
+
return typeof value["answer"] === "string" && value["pending_input"] === null && value["pending_approval"] === null && value["error"] === null;
|
|
31950
|
+
case "completed":
|
|
31951
|
+
return isTodosAiUpdateTaskResult(value["data"]) && value["data"]["mode"] === "execute" && value["data"]["applied"] === true && value["data"]["readback_verified"] === true && value["pending_input"] === null && value["pending_approval"] === null && value["error"] === null;
|
|
31952
|
+
case "needs_input":
|
|
31953
|
+
return value["pending_input"] !== null && value["pending_approval"] === null && value["error"] === null;
|
|
31954
|
+
case "needs_approval":
|
|
31955
|
+
return value["pending_input"] === null && value["pending_approval"] !== null && value["error"] === null;
|
|
31956
|
+
case "failed":
|
|
31957
|
+
return value["pending_input"] === null && value["pending_approval"] === null && value["error"] !== null;
|
|
31958
|
+
}
|
|
31959
|
+
return false;
|
|
31960
|
+
}
|
|
31961
|
+
function assertTodosAiRunResult(value) {
|
|
31962
|
+
if (!isTodosAiRunResult(value)) {
|
|
31963
|
+
throw new TodosAiContractError("runtime_invalid_result", "optional AI runtime returned a result that does not satisfy the Todos AI protocol", TODOS_AI_EXIT_CODES.failed);
|
|
31964
|
+
}
|
|
31965
|
+
return value;
|
|
31966
|
+
}
|
|
31967
|
+
function isTodosAiRuntimeEvent(value) {
|
|
31968
|
+
if (!isRecord(value) || !isTodosAiJsonValue(value))
|
|
31969
|
+
return false;
|
|
31970
|
+
return value["schema_version"] === TODOS_AI_SCHEMA_VERSION && typeof value["run_id"] === "string" && Boolean(value["run_id"]) && Number.isSafeInteger(value["sequence"]) && value["sequence"] >= 0 && isOneOf(value["type"], TODOS_AI_RUNTIME_EVENT_TYPES) && typeof value["timestamp"] === "string" && isRecord(value["data"]) && isTodosAiJsonValue(value["data"]);
|
|
31971
|
+
}
|
|
31972
|
+
function assertTodosAiRuntimeModule(value) {
|
|
31973
|
+
if (!isRecord(value) || value["TODOS_AI_RUNTIME_PROTOCOL_VERSION"] !== TODOS_AI_RUNTIME_PROTOCOL_VERSION || typeof value["createTodosAiRuntime"] !== "function") {
|
|
31974
|
+
throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
|
|
31975
|
+
}
|
|
31976
|
+
return value;
|
|
31977
|
+
}
|
|
31978
|
+
function assertTodosAiRuntime(value) {
|
|
31979
|
+
if (!isRecord(value) || typeof value["run"] !== "function") {
|
|
31980
|
+
throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
|
|
31981
|
+
}
|
|
31982
|
+
return value;
|
|
31983
|
+
}
|
|
31984
|
+
async function loadTodosAiRuntime(context, importer = defaultTodosAiRuntimeImporter) {
|
|
31985
|
+
let imported;
|
|
31986
|
+
try {
|
|
31987
|
+
imported = await importer(TODOS_AI_RUNTIME_SPECIFIER);
|
|
31988
|
+
} catch (cause) {
|
|
31989
|
+
throw new TodosAiContractError("runtime_unavailable", `optional AI runtime is unavailable; install a compatible ${TODOS_AI_RUNTIME_SPECIFIER}`, TODOS_AI_EXIT_CODES.runtime_unavailable, { cause });
|
|
31990
|
+
}
|
|
31991
|
+
const runtimeModule = assertTodosAiRuntimeModule(imported);
|
|
31992
|
+
return assertTodosAiRuntime(await runtimeModule.createTodosAiRuntime(context));
|
|
31993
|
+
}
|
|
31994
|
+
function createTodosAiFailureResult(runId, code, message, retryable = false, details = null) {
|
|
31995
|
+
return {
|
|
31996
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
31997
|
+
run_id: runId,
|
|
31998
|
+
status: "failed",
|
|
31999
|
+
answer: null,
|
|
32000
|
+
data: null,
|
|
32001
|
+
steps: 0,
|
|
32002
|
+
usage: null,
|
|
32003
|
+
pending_input: null,
|
|
32004
|
+
pending_approval: null,
|
|
32005
|
+
error: { code, message, retryable, details }
|
|
32006
|
+
};
|
|
32007
|
+
}
|
|
32008
|
+
function createTodosAiNeedsInputResult(runId, message) {
|
|
32009
|
+
return {
|
|
32010
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
32011
|
+
run_id: runId,
|
|
32012
|
+
status: "needs_input",
|
|
32013
|
+
answer: null,
|
|
32014
|
+
data: null,
|
|
32015
|
+
steps: 0,
|
|
32016
|
+
usage: null,
|
|
32017
|
+
pending_input: { prompt: message, fields: ["prompt"] },
|
|
32018
|
+
pending_approval: null,
|
|
32019
|
+
error: null
|
|
32020
|
+
};
|
|
32021
|
+
}
|
|
32022
|
+
function todosAiExitCodeForResult(result) {
|
|
32023
|
+
if (result.status === "answered" || result.status === "completed")
|
|
32024
|
+
return TODOS_AI_EXIT_CODES.success;
|
|
32025
|
+
if (result.status === "needs_input")
|
|
32026
|
+
return TODOS_AI_EXIT_CODES.needs_input;
|
|
32027
|
+
if (result.status === "needs_approval")
|
|
32028
|
+
return TODOS_AI_EXIT_CODES.needs_approval;
|
|
32029
|
+
switch (result.error?.code) {
|
|
32030
|
+
case "invalid_input":
|
|
32031
|
+
case "invalid_configuration":
|
|
32032
|
+
return TODOS_AI_EXIT_CODES.usage;
|
|
32033
|
+
case "runtime_unavailable":
|
|
32034
|
+
case "runtime_incompatible":
|
|
32035
|
+
return TODOS_AI_EXIT_CODES.runtime_unavailable;
|
|
32036
|
+
case "timeout":
|
|
32037
|
+
return TODOS_AI_EXIT_CODES.timeout;
|
|
32038
|
+
case "interrupted":
|
|
32039
|
+
return TODOS_AI_EXIT_CODES.interrupted;
|
|
32040
|
+
default:
|
|
32041
|
+
return TODOS_AI_EXIT_CODES.failed;
|
|
32042
|
+
}
|
|
32043
|
+
}
|
|
32044
|
+
var TODOS_AI_SCHEMA_VERSION = 1, TODOS_AI_RUNTIME_PROTOCOL_VERSION = 1, TODOS_AI_RUNTIME_SPECIFIER = "@hasna/todos-ai/runtime", TODOS_AI_UPDATE_TASK_RESULT_SCHEMA = "todos.ai.update_task.v1", TODOS_AI_FORMATS, TODOS_AI_WRITE_MODES, TODOS_AI_APPROVAL_MODES, TODOS_AI_RUN_STATUSES, TODOS_AI_RUNTIME_EVENT_TYPES, TODOS_AI_UPDATE_TASK_FIELDS, TODOS_AI_UPDATE_TASK_LIMITS, TODOS_AI_DEFAULTS, TODOS_AI_LIMITS, TODOS_AI_EXIT_CODES, TodosAiContractError, TodosAiNeedsInputSignal, TodosAiNeedsApprovalSignal, SENSITIVE_VARIABLE_KEY, defaultTodosAiRuntimeImporter = async (specifier) => import(specifier);
|
|
32045
|
+
var init_ai = __esm(() => {
|
|
32046
|
+
TODOS_AI_FORMATS = ["text", "json", "stream-json"];
|
|
32047
|
+
TODOS_AI_WRITE_MODES = ["read-only", "plan", "execute"];
|
|
32048
|
+
TODOS_AI_APPROVAL_MODES = ["deny", "required", "prompt", "existing"];
|
|
32049
|
+
TODOS_AI_RUN_STATUSES = [
|
|
32050
|
+
"answered",
|
|
32051
|
+
"needs_input",
|
|
32052
|
+
"needs_approval",
|
|
32053
|
+
"completed",
|
|
32054
|
+
"failed"
|
|
32055
|
+
];
|
|
32056
|
+
TODOS_AI_RUNTIME_EVENT_TYPES = [
|
|
32057
|
+
"run.started",
|
|
32058
|
+
"run.progress",
|
|
32059
|
+
"text.delta",
|
|
32060
|
+
"tool.started",
|
|
32061
|
+
"tool.completed",
|
|
32062
|
+
"input.required",
|
|
32063
|
+
"approval.required"
|
|
32064
|
+
];
|
|
32065
|
+
TODOS_AI_UPDATE_TASK_FIELDS = [
|
|
32066
|
+
"title",
|
|
32067
|
+
"description",
|
|
32068
|
+
"status",
|
|
32069
|
+
"priority",
|
|
32070
|
+
"assigned_to",
|
|
32071
|
+
"tags",
|
|
32072
|
+
"due_at"
|
|
32073
|
+
];
|
|
32074
|
+
TODOS_AI_UPDATE_TASK_LIMITS = {
|
|
32075
|
+
max_title_bytes: 1024,
|
|
32076
|
+
max_description_bytes: 8192,
|
|
32077
|
+
max_assignee_bytes: 256,
|
|
32078
|
+
max_tags: 16,
|
|
32079
|
+
max_tag_bytes: 128,
|
|
32080
|
+
max_due_at_bytes: 128,
|
|
32081
|
+
min_idempotency_key_bytes: 8,
|
|
32082
|
+
max_idempotency_key_bytes: 128,
|
|
32083
|
+
max_result_bytes: 65536
|
|
32084
|
+
};
|
|
32085
|
+
TODOS_AI_DEFAULTS = {
|
|
32086
|
+
format: "text",
|
|
32087
|
+
max_steps: 8,
|
|
32088
|
+
timeout_ms: 60000,
|
|
32089
|
+
write_mode: "read-only"
|
|
32090
|
+
};
|
|
32091
|
+
TODOS_AI_LIMITS = {
|
|
32092
|
+
max_prompt_bytes: 1048576,
|
|
32093
|
+
max_json_bytes: 1048576,
|
|
32094
|
+
max_result_bytes: 4194304,
|
|
32095
|
+
max_variable_count: 100,
|
|
32096
|
+
max_variable_value_bytes: 65536,
|
|
32097
|
+
max_approval_refs: 32,
|
|
32098
|
+
max_approval_ref_bytes: 1024,
|
|
32099
|
+
max_resume_run_id_bytes: 1024,
|
|
32100
|
+
max_pending_input_prompt_bytes: 1024,
|
|
32101
|
+
max_pending_input_fields: 16,
|
|
32102
|
+
max_pending_input_field_bytes: 128,
|
|
32103
|
+
max_pending_approval_id_bytes: 256,
|
|
32104
|
+
max_pending_approval_summary_bytes: 1024,
|
|
32105
|
+
max_pending_approval_operations: 4,
|
|
32106
|
+
max_pending_approval_bytes: 8192,
|
|
32107
|
+
max_stream_events: 1000,
|
|
32108
|
+
max_stream_record_bytes: 262144,
|
|
32109
|
+
max_stream_bytes: 8388608,
|
|
32110
|
+
min_steps: 1,
|
|
32111
|
+
max_steps: 20,
|
|
32112
|
+
min_timeout_ms: 1000,
|
|
32113
|
+
max_timeout_ms: 600000
|
|
32114
|
+
};
|
|
32115
|
+
TODOS_AI_EXIT_CODES = {
|
|
32116
|
+
success: 0,
|
|
32117
|
+
usage: 2,
|
|
32118
|
+
needs_input: 3,
|
|
32119
|
+
needs_approval: 4,
|
|
32120
|
+
runtime_unavailable: 5,
|
|
32121
|
+
failed: 6,
|
|
32122
|
+
timeout: 124,
|
|
32123
|
+
interrupted: 130
|
|
32124
|
+
};
|
|
32125
|
+
TodosAiContractError = class TodosAiContractError extends Error {
|
|
32126
|
+
code;
|
|
32127
|
+
exitCode;
|
|
32128
|
+
constructor(code, message, exitCode = TODOS_AI_EXIT_CODES.usage, options) {
|
|
32129
|
+
super(message, options);
|
|
32130
|
+
this.code = code;
|
|
32131
|
+
this.exitCode = exitCode;
|
|
32132
|
+
this.name = "TodosAiContractError";
|
|
32133
|
+
}
|
|
32134
|
+
};
|
|
32135
|
+
TodosAiNeedsInputSignal = class TodosAiNeedsInputSignal extends Error {
|
|
32136
|
+
pending_input;
|
|
32137
|
+
constructor(pendingInput) {
|
|
32138
|
+
if (!isTodosAiJsonValue(pendingInput) || !isPendingInput(pendingInput)) {
|
|
32139
|
+
throw new TodosAiContractError("invalid_input", "Todos AI pending input must be bounded stable control data");
|
|
32140
|
+
}
|
|
32141
|
+
super("Todos AI input required");
|
|
32142
|
+
this.name = "TodosAiNeedsInputSignal";
|
|
32143
|
+
this.pending_input = {
|
|
32144
|
+
prompt: pendingInput.prompt,
|
|
32145
|
+
fields: [...pendingInput.fields]
|
|
32146
|
+
};
|
|
32147
|
+
}
|
|
32148
|
+
};
|
|
32149
|
+
TodosAiNeedsApprovalSignal = class TodosAiNeedsApprovalSignal extends Error {
|
|
32150
|
+
pending_approval;
|
|
32151
|
+
constructor(pendingApproval) {
|
|
32152
|
+
if (!isTodosAiJsonValue(pendingApproval) || !isPendingApproval(pendingApproval)) {
|
|
32153
|
+
throw new TodosAiContractError("invalid_input", "Todos AI pending approval must be bounded stable control data");
|
|
32154
|
+
}
|
|
32155
|
+
super("Todos AI approval required");
|
|
32156
|
+
this.name = "TodosAiNeedsApprovalSignal";
|
|
32157
|
+
this.pending_approval = {
|
|
32158
|
+
id: pendingApproval.id,
|
|
32159
|
+
summary: pendingApproval.summary,
|
|
32160
|
+
operations: pendingApproval.operations.map((operation) => JSON.parse(JSON.stringify(operation)))
|
|
32161
|
+
};
|
|
32162
|
+
}
|
|
32163
|
+
};
|
|
32164
|
+
SENSITIVE_VARIABLE_KEY = /(?:^|[_.-])(api[_-]?key|credential|password|secret|token)(?:$|[_.-])/i;
|
|
32165
|
+
});
|
|
32166
|
+
|
|
32167
|
+
// src/lib/access-profiles.ts
|
|
32168
|
+
function resolveAccessProfile(envValue) {
|
|
32169
|
+
const raw = (envValue ?? process.env["TODOS_PROFILE"] ?? "full").toLowerCase();
|
|
32170
|
+
if (ACCESS_PROFILES.includes(raw))
|
|
32171
|
+
return raw;
|
|
32172
|
+
if (raw === "readonly")
|
|
32173
|
+
return "read_only";
|
|
32174
|
+
if (raw === "agent-safe")
|
|
32175
|
+
return "agent_safe";
|
|
32176
|
+
return "full";
|
|
32177
|
+
}
|
|
32178
|
+
function shouldRegisterToolForProfile(toolName, profile) {
|
|
32179
|
+
const p = profile ?? resolveAccessProfile();
|
|
32180
|
+
if (ADMIN_ONLY_TOOLS.has(toolName) && p !== "admin")
|
|
32181
|
+
return false;
|
|
32182
|
+
switch (p) {
|
|
32183
|
+
case "read_only":
|
|
32184
|
+
return READ_ONLY_TOOLS.has(toolName);
|
|
32185
|
+
case "agent_safe":
|
|
32186
|
+
return AGENT_SAFE_TOOLS.has(toolName);
|
|
32187
|
+
case "minimal":
|
|
32188
|
+
return MINIMAL_TOOLS.has(toolName);
|
|
32189
|
+
case "standard":
|
|
32190
|
+
return !STANDARD_EXCLUDED.has(toolName);
|
|
32191
|
+
case "admin":
|
|
32192
|
+
case "full":
|
|
32193
|
+
default:
|
|
32194
|
+
return true;
|
|
32195
|
+
}
|
|
32196
|
+
}
|
|
32197
|
+
var ACCESS_PROFILES, READ_ONLY_TOOLS, MINIMAL_TOOLS, AGENT_SAFE_TOOLS, STANDARD_EXCLUDED, ADMIN_ONLY_TOOLS, DANGEROUS_TOOLS;
|
|
32198
|
+
var init_access_profiles = __esm(() => {
|
|
32199
|
+
ACCESS_PROFILES = ["read_only", "agent_safe", "minimal", "standard", "full", "admin"];
|
|
32200
|
+
READ_ONLY_TOOLS = new Set([
|
|
32201
|
+
"get_task",
|
|
32202
|
+
"list_tasks",
|
|
32203
|
+
"get_status",
|
|
32204
|
+
"get_context",
|
|
32205
|
+
"search_tasks",
|
|
32206
|
+
"list_projects",
|
|
32207
|
+
"get_project",
|
|
32208
|
+
"list_plans",
|
|
32209
|
+
"get_plan",
|
|
32210
|
+
"list_agents",
|
|
32211
|
+
"get_agent",
|
|
32212
|
+
"list_task_lists",
|
|
32213
|
+
"get_task_list",
|
|
32214
|
+
"get_task_commits",
|
|
32215
|
+
"get_task_traceability",
|
|
32216
|
+
"list_pending_approvals",
|
|
32217
|
+
"get_task_gate_status",
|
|
32218
|
+
"list_active_leases",
|
|
32219
|
+
"list_agent_runs",
|
|
32220
|
+
"list_agent_adapters",
|
|
32221
|
+
"discover_workspace",
|
|
32222
|
+
"get_bootstrap_status",
|
|
32223
|
+
"get_agent_workflow_demo_docs",
|
|
32224
|
+
"get_feature_manifest",
|
|
32225
|
+
"get_capability_discovery",
|
|
32226
|
+
"get_feature_manifest_docs",
|
|
32227
|
+
"get_cli_mcp_parity",
|
|
32228
|
+
"list_secret_patterns",
|
|
32229
|
+
"list_sandbox_profiles",
|
|
32230
|
+
"list_workspace_trust_profiles",
|
|
32231
|
+
"list_verification_providers",
|
|
32232
|
+
"list_verification_records",
|
|
32233
|
+
"get_tasks_changed_since",
|
|
32234
|
+
"get_stale_tasks",
|
|
32235
|
+
"get_next_task",
|
|
32236
|
+
"find_duplicate_tasks",
|
|
32237
|
+
"describe_tools",
|
|
32238
|
+
"search_tools",
|
|
32239
|
+
"inspect_git_commit",
|
|
32240
|
+
"list_task_findings",
|
|
32241
|
+
"scan_text_for_secrets",
|
|
32242
|
+
"check_workspace_permission",
|
|
32243
|
+
"check_sandbox_command",
|
|
32244
|
+
"parse_mentions",
|
|
32245
|
+
"resolve_mention",
|
|
32246
|
+
"resolve_mentions_in_text",
|
|
32247
|
+
"generate_release_notes",
|
|
32248
|
+
"format_release_notes_markdown",
|
|
32249
|
+
"analyze_branch_work",
|
|
32250
|
+
"generate_branch_work_plan",
|
|
32251
|
+
"get_branch_work_plan_docs"
|
|
32252
|
+
]);
|
|
32253
|
+
MINIMAL_TOOLS = new Set([
|
|
32254
|
+
"claim_next_task",
|
|
32255
|
+
"complete_task",
|
|
32256
|
+
"fail_task",
|
|
32257
|
+
"get_status",
|
|
32258
|
+
"get_context",
|
|
32259
|
+
"get_task",
|
|
32260
|
+
"start_task",
|
|
32261
|
+
"add_comment",
|
|
32262
|
+
"get_next_task",
|
|
32263
|
+
"bootstrap",
|
|
32264
|
+
"get_tasks_changed_since",
|
|
32265
|
+
"heartbeat",
|
|
32266
|
+
"release_agent",
|
|
32267
|
+
"begin_task_run_transaction",
|
|
32268
|
+
"finish_task_run",
|
|
32269
|
+
"upsert_task_finding",
|
|
32270
|
+
"list_task_findings",
|
|
32271
|
+
"resolve_missing_task_findings"
|
|
32272
|
+
]);
|
|
32273
|
+
AGENT_SAFE_TOOLS = new Set([
|
|
32274
|
+
...READ_ONLY_TOOLS,
|
|
32275
|
+
...MINIMAL_TOOLS,
|
|
32276
|
+
"create_task",
|
|
32277
|
+
"update_task",
|
|
32278
|
+
"lock_task",
|
|
32279
|
+
"unlock_task",
|
|
32280
|
+
"enqueue_agent_run",
|
|
32281
|
+
"claim_next_agent_run",
|
|
32282
|
+
"complete_agent_run",
|
|
32283
|
+
"fail_agent_run",
|
|
32284
|
+
"acquire_task_lease",
|
|
32285
|
+
"renew_task_lease",
|
|
32286
|
+
"release_task_lease",
|
|
32287
|
+
"request_approval",
|
|
32288
|
+
"run_verification",
|
|
32289
|
+
"link_task_git_trace",
|
|
32290
|
+
"assign_label_to_task",
|
|
32291
|
+
"set_task_custom_field",
|
|
32292
|
+
"log_progress"
|
|
32293
|
+
]);
|
|
32294
|
+
STANDARD_EXCLUDED = new Set([
|
|
32295
|
+
"rename_agent",
|
|
32296
|
+
"delete_agent",
|
|
32297
|
+
"unarchive_agent",
|
|
32298
|
+
"create_webhook",
|
|
32299
|
+
"list_webhooks",
|
|
32300
|
+
"delete_webhook",
|
|
32301
|
+
"create_template",
|
|
32302
|
+
"list_templates",
|
|
32303
|
+
"create_task_from_template",
|
|
32304
|
+
"delete_template",
|
|
32305
|
+
"update_template",
|
|
32306
|
+
"init_templates",
|
|
32307
|
+
"preview_template",
|
|
32308
|
+
"export_template",
|
|
32309
|
+
"import_template",
|
|
32310
|
+
"template_history",
|
|
32311
|
+
"approve_task",
|
|
32312
|
+
"migrate_pg"
|
|
32313
|
+
]);
|
|
32314
|
+
ADMIN_ONLY_TOOLS = new Set([
|
|
32315
|
+
"migrate_pg",
|
|
32316
|
+
"delete_task",
|
|
32317
|
+
"delete_project",
|
|
32318
|
+
"delete_agent",
|
|
32319
|
+
"delete_plan",
|
|
32320
|
+
"delete_task_list",
|
|
32321
|
+
"delete_webhook",
|
|
32322
|
+
"delete_template",
|
|
32323
|
+
"steal_task_lease",
|
|
32324
|
+
"recover_stale_leases"
|
|
32325
|
+
]);
|
|
32326
|
+
DANGEROUS_TOOLS = new Set([
|
|
32327
|
+
...ADMIN_ONLY_TOOLS,
|
|
32328
|
+
"bulk_update_tasks",
|
|
32329
|
+
"merge_tasks",
|
|
32330
|
+
"cancel_agent_run"
|
|
32331
|
+
]);
|
|
32332
|
+
});
|
|
32333
|
+
|
|
32334
|
+
// src/db/checkpoints.ts
|
|
32335
|
+
function upsertCheckpoint(task_id, step, updates, db) {
|
|
32336
|
+
const d = db || getDatabase();
|
|
32337
|
+
const timestamp2 = now();
|
|
32338
|
+
const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
|
|
32339
|
+
const id = existing?.id ?? uuid();
|
|
32340
|
+
const agentId = updates.agent_id ?? null;
|
|
32341
|
+
const status = updates.status ?? "pending";
|
|
32342
|
+
const data = updates.data ? JSON.stringify(updates.data) : JSON.stringify({});
|
|
32343
|
+
const error = updates.error ?? null;
|
|
32344
|
+
const attempt = updates.attempt ?? 1;
|
|
32345
|
+
const maxAttempts = updates.max_attempts ?? 1;
|
|
32346
|
+
const startedAt = updates.started_at ?? null;
|
|
32347
|
+
const completedAt = updates.completed_at ?? null;
|
|
32348
|
+
d.run(`INSERT INTO task_checkpoints (id, task_id, agent_id, step, status, data, error, attempt, max_attempts, started_at, completed_at, updated_at)
|
|
32349
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
32350
|
+
ON CONFLICT(id) DO UPDATE SET status=?, data=?, error=?, attempt=?, max_attempts=?, started_at=COALESCE(started_at,?), completed_at=?, updated_at=?`, [
|
|
32351
|
+
id,
|
|
32352
|
+
task_id,
|
|
32353
|
+
agentId,
|
|
32354
|
+
step,
|
|
32355
|
+
status,
|
|
32356
|
+
data,
|
|
32357
|
+
error,
|
|
32358
|
+
attempt,
|
|
32359
|
+
maxAttempts,
|
|
32360
|
+
startedAt,
|
|
32361
|
+
completedAt,
|
|
32362
|
+
timestamp2,
|
|
32363
|
+
status,
|
|
32364
|
+
data,
|
|
32365
|
+
error,
|
|
32366
|
+
attempt,
|
|
32367
|
+
maxAttempts,
|
|
32368
|
+
startedAt,
|
|
32369
|
+
completedAt,
|
|
32370
|
+
timestamp2
|
|
32371
|
+
]);
|
|
32372
|
+
return rowToCheckpoint(d.query("SELECT * FROM task_checkpoints WHERE id = ?").get(id));
|
|
32373
|
+
}
|
|
32374
|
+
function getCheckpoint(taskId, step, db) {
|
|
32375
|
+
const d = db || getDatabase();
|
|
32376
|
+
const row = d.query("SELECT * FROM task_checkpoints WHERE task_id = ? AND step = ?").get(taskId, step);
|
|
32377
|
+
return row ? rowToCheckpoint(row) : null;
|
|
32378
|
+
}
|
|
32379
|
+
function getTaskCheckpoints(taskId, db) {
|
|
32380
|
+
const d = db || getDatabase();
|
|
32381
|
+
return d.query("SELECT * FROM task_checkpoints WHERE task_id = ? ORDER BY created_at ASC").all(taskId).map(rowToCheckpoint);
|
|
32382
|
+
}
|
|
32383
|
+
function rowToCheckpoint(row) {
|
|
32384
|
+
if (!row)
|
|
32385
|
+
return null;
|
|
32386
|
+
return {
|
|
32387
|
+
...row,
|
|
32388
|
+
data: JSON.parse(row.data || "{}"),
|
|
32389
|
+
status: row.status,
|
|
32390
|
+
agent_id: row.agent_id || null,
|
|
32391
|
+
error: row.error || null,
|
|
32392
|
+
started_at: row.started_at || null,
|
|
32393
|
+
completed_at: row.completed_at || null
|
|
32394
|
+
};
|
|
32395
|
+
}
|
|
32396
|
+
var init_checkpoints = __esm(() => {
|
|
32397
|
+
init_database();
|
|
32398
|
+
});
|
|
32399
|
+
|
|
32400
|
+
// src/lib/approval-gates.ts
|
|
32401
|
+
var exports_approval_gates = {};
|
|
32402
|
+
__export(exports_approval_gates, {
|
|
32403
|
+
requestApprovalGate: () => requestApprovalGate,
|
|
32404
|
+
rejectApprovalGate: () => rejectApprovalGate,
|
|
32405
|
+
listApprovalGates: () => listApprovalGates,
|
|
32406
|
+
expireApprovalGate: () => expireApprovalGate,
|
|
32407
|
+
checkApprovalGate: () => checkApprovalGate,
|
|
32408
|
+
assertApprovalGate: () => assertApprovalGate,
|
|
32409
|
+
approveApprovalGate: () => approveApprovalGate
|
|
32410
|
+
});
|
|
32411
|
+
function stepForGate(gate) {
|
|
32412
|
+
const trimmed = gate.trim();
|
|
32413
|
+
if (!trimmed)
|
|
32414
|
+
throw new Error("Approval gate name is required");
|
|
32415
|
+
return `approval:${trimmed}`;
|
|
32416
|
+
}
|
|
32417
|
+
function approvalStatusToCheckpointStatus(status) {
|
|
32418
|
+
if (status === "approved")
|
|
32419
|
+
return "completed";
|
|
32420
|
+
if (status === "pending")
|
|
32421
|
+
return "pending";
|
|
32422
|
+
return "failed";
|
|
32423
|
+
}
|
|
32424
|
+
function checkpointStatusToApprovalStatus(checkpoint) {
|
|
32425
|
+
const value = checkpoint.data["approval_status"];
|
|
32426
|
+
if (value === "approved" || value === "rejected" || value === "expired" || value === "pending")
|
|
32427
|
+
return value;
|
|
32428
|
+
if (checkpoint.status === "completed")
|
|
32429
|
+
return "approved";
|
|
32430
|
+
if (checkpoint.status === "failed")
|
|
32431
|
+
return "rejected";
|
|
32432
|
+
return "pending";
|
|
32433
|
+
}
|
|
32434
|
+
function ensureTask(taskId, db) {
|
|
32435
|
+
if (!getTask(taskId, db))
|
|
32436
|
+
throw new TaskNotFoundError(taskId);
|
|
32437
|
+
}
|
|
32438
|
+
function isExpired(expiresAt, at = new Date) {
|
|
32439
|
+
return Boolean(expiresAt && new Date(expiresAt).getTime() <= at.getTime());
|
|
32440
|
+
}
|
|
32441
|
+
function gateFromCheckpoint(checkpoint) {
|
|
32442
|
+
const status = checkpointStatusToApprovalStatus(checkpoint);
|
|
32443
|
+
return {
|
|
32444
|
+
id: checkpoint.id,
|
|
32445
|
+
task_id: checkpoint.task_id,
|
|
32446
|
+
gate: String(checkpoint.data["approval_gate_name"] || checkpoint.step.replace(/^approval:/, "")),
|
|
32447
|
+
status,
|
|
32448
|
+
reviewer: typeof checkpoint.data["reviewer"] === "string" ? checkpoint.data["reviewer"] : null,
|
|
32449
|
+
requester: typeof checkpoint.data["requester"] === "string" ? checkpoint.data["requester"] : null,
|
|
32450
|
+
reason: typeof checkpoint.data["reason"] === "string" ? checkpoint.data["reason"] : null,
|
|
32451
|
+
note: typeof checkpoint.data["note"] === "string" ? checkpoint.data["note"] : null,
|
|
32452
|
+
plan_id: typeof checkpoint.data["plan_id"] === "string" ? checkpoint.data["plan_id"] : null,
|
|
32453
|
+
run_id: typeof checkpoint.data["run_id"] === "string" ? checkpoint.data["run_id"] : null,
|
|
32454
|
+
expires_at: typeof checkpoint.data["expires_at"] === "string" ? checkpoint.data["expires_at"] : null,
|
|
32455
|
+
decided_by: typeof checkpoint.data["decided_by"] === "string" ? checkpoint.data["decided_by"] : null,
|
|
32456
|
+
decided_at: typeof checkpoint.data["decided_at"] === "string" ? checkpoint.data["decided_at"] : null,
|
|
32457
|
+
created_at: checkpoint.created_at,
|
|
32458
|
+
updated_at: checkpoint.updated_at,
|
|
32459
|
+
checkpoint
|
|
32460
|
+
};
|
|
32461
|
+
}
|
|
32462
|
+
function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
32463
|
+
const payload = JSON.stringify({
|
|
32464
|
+
gate: gate.gate,
|
|
32465
|
+
status: gate.status,
|
|
32466
|
+
reviewer: gate.reviewer,
|
|
32467
|
+
requester: gate.requester,
|
|
32468
|
+
plan_id: gate.plan_id,
|
|
32469
|
+
run_id: gate.run_id,
|
|
32470
|
+
expires_at: gate.expires_at,
|
|
32471
|
+
decided_by: gate.decided_by,
|
|
32472
|
+
decided_at: gate.decided_at
|
|
32473
|
+
});
|
|
32474
|
+
logTaskChange(taskId, `approval_gate.${action}`, "approval_gate", null, payload, agentId, db);
|
|
32475
|
+
if (gate.run_id && getTaskRun(gate.run_id, db)) {
|
|
32476
|
+
addTaskRunEvent({
|
|
32477
|
+
run_id: gate.run_id,
|
|
32478
|
+
event_type: "progress",
|
|
32479
|
+
message: `approval gate ${action}: ${gate.gate}`,
|
|
32480
|
+
data: JSON.parse(payload),
|
|
32481
|
+
agent_id: agentId
|
|
32482
|
+
}, db);
|
|
32483
|
+
}
|
|
32484
|
+
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
32485
|
+
emitLocalEventHooksQuiet({
|
|
32486
|
+
type: "approval.decided",
|
|
32487
|
+
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
|
|
32488
|
+
databasePath: databasePathFromDatabase(db)
|
|
32489
|
+
});
|
|
32490
|
+
}
|
|
32491
|
+
}
|
|
32492
|
+
function writeGate(input, status, decision, db) {
|
|
32493
|
+
const d = db || getDatabase();
|
|
32494
|
+
ensureTask(input.task_id, d);
|
|
32495
|
+
const step = stepForGate(input.gate);
|
|
32496
|
+
const existing = getCheckpoint(input.task_id, step, d);
|
|
32497
|
+
const existingData = existing?.data || {};
|
|
32498
|
+
const timestamp2 = decision?.decided_at || now();
|
|
32499
|
+
const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
|
|
32500
|
+
const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
|
|
32501
|
+
const data = {
|
|
32502
|
+
...existingData,
|
|
32503
|
+
...input.metadata || {},
|
|
32504
|
+
approval_gate: true,
|
|
32505
|
+
approval_gate_name: input.gate.trim(),
|
|
32506
|
+
approval_status: status,
|
|
32507
|
+
requester: input.requester ?? existingData["requester"] ?? null,
|
|
32508
|
+
reviewer: input.reviewer ?? decision?.decided_by ?? existingData["reviewer"] ?? null,
|
|
32509
|
+
reason: decision?.reason ?? input.reason ?? existingData["reason"] ?? null,
|
|
32510
|
+
note: decision?.note ?? existingData["note"] ?? null,
|
|
32511
|
+
plan_id: input.plan_id ?? existingData["plan_id"] ?? null,
|
|
32512
|
+
run_id: runId ?? null,
|
|
32513
|
+
expires_at: input.expires_at ?? existingData["expires_at"] ?? null,
|
|
32514
|
+
decided_by: decision?.decided_by ?? existingData["decided_by"] ?? null,
|
|
32515
|
+
decided_at: decision?.decided_at ?? existingData["decided_at"] ?? null
|
|
32516
|
+
};
|
|
32517
|
+
const checkpoint = upsertCheckpoint(input.task_id, step, {
|
|
32518
|
+
agent_id: decision?.decided_by || input.requester || input.reviewer,
|
|
32519
|
+
status: approvalStatusToCheckpointStatus(status),
|
|
32520
|
+
data,
|
|
32521
|
+
error: status === "rejected" || status === "expired" ? String(data.reason || status) : null,
|
|
32522
|
+
started_at: existing?.started_at || timestamp2,
|
|
32523
|
+
completed_at: status === "pending" ? null : timestamp2
|
|
32524
|
+
}, d);
|
|
32525
|
+
return gateFromCheckpoint(checkpoint);
|
|
32526
|
+
}
|
|
32527
|
+
function currentGate(taskId, gate, db) {
|
|
32528
|
+
const checkpoint = getCheckpoint(taskId, stepForGate(gate), db);
|
|
32529
|
+
return checkpoint ? gateFromCheckpoint(checkpoint) : null;
|
|
32530
|
+
}
|
|
32531
|
+
function requestApprovalGate(input, db) {
|
|
32532
|
+
const d = db || getDatabase();
|
|
32533
|
+
const existing = currentGate(input.task_id, input.gate, d);
|
|
32534
|
+
if (existing && existing.status !== "pending") {
|
|
32535
|
+
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
32536
|
+
}
|
|
32537
|
+
const gate = writeGate(input, "pending", undefined, d);
|
|
32538
|
+
logApprovalEvent(input.task_id, "requested", gate, input.requester || input.reviewer, d);
|
|
32539
|
+
return gate;
|
|
32540
|
+
}
|
|
32541
|
+
function approveApprovalGate(input, db) {
|
|
32542
|
+
const d = db || getDatabase();
|
|
32543
|
+
const existing = currentGate(input.task_id, input.gate, d);
|
|
32544
|
+
if (!existing)
|
|
32545
|
+
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
32546
|
+
if (existing.status !== "pending")
|
|
32547
|
+
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
32548
|
+
if (isExpired(existing.expires_at))
|
|
32549
|
+
throw new Error(`Approval gate ${input.gate} is expired`);
|
|
32550
|
+
const gate = writeGate({
|
|
32551
|
+
task_id: input.task_id,
|
|
32552
|
+
gate: input.gate,
|
|
32553
|
+
requester: existing.requester || undefined,
|
|
32554
|
+
reviewer: input.reviewer || existing.reviewer || undefined,
|
|
32555
|
+
reason: existing.reason || undefined,
|
|
32556
|
+
plan_id: existing.plan_id || undefined,
|
|
32557
|
+
run_id: existing.run_id || undefined,
|
|
32558
|
+
expires_at: existing.expires_at || undefined
|
|
32559
|
+
}, "approved", { decided_by: input.reviewer, decided_at: now(), note: input.note }, d);
|
|
32560
|
+
logApprovalEvent(input.task_id, "approved", gate, input.reviewer, d);
|
|
32561
|
+
return gate;
|
|
32562
|
+
}
|
|
32563
|
+
function rejectApprovalGate(input, db) {
|
|
32564
|
+
const d = db || getDatabase();
|
|
32565
|
+
const existing = currentGate(input.task_id, input.gate, d);
|
|
32566
|
+
if (!existing)
|
|
32567
|
+
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
32568
|
+
if (existing.status !== "pending")
|
|
32569
|
+
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
32570
|
+
const gate = writeGate({
|
|
32571
|
+
task_id: input.task_id,
|
|
32572
|
+
gate: input.gate,
|
|
32573
|
+
requester: existing.requester || undefined,
|
|
32574
|
+
reviewer: input.reviewer || existing.reviewer || undefined,
|
|
32575
|
+
reason: existing.reason || undefined,
|
|
32576
|
+
plan_id: existing.plan_id || undefined,
|
|
32577
|
+
run_id: existing.run_id || undefined,
|
|
32578
|
+
expires_at: existing.expires_at || undefined
|
|
32579
|
+
}, "rejected", { decided_by: input.reviewer, decided_at: now(), note: input.note, reason: input.reason || input.note }, d);
|
|
32580
|
+
logApprovalEvent(input.task_id, "rejected", gate, input.reviewer, d);
|
|
32581
|
+
return gate;
|
|
32582
|
+
}
|
|
32583
|
+
function expireApprovalGate(input, db) {
|
|
32584
|
+
const d = db || getDatabase();
|
|
32585
|
+
const existing = currentGate(input.task_id, input.gate, d);
|
|
32586
|
+
if (!existing)
|
|
32587
|
+
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
32588
|
+
if (existing.status !== "pending")
|
|
32589
|
+
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
32590
|
+
const gate = writeGate({
|
|
32591
|
+
task_id: input.task_id,
|
|
32592
|
+
gate: input.gate,
|
|
32593
|
+
requester: existing.requester || undefined,
|
|
32594
|
+
reviewer: existing.reviewer || undefined,
|
|
32595
|
+
reason: existing.reason || undefined,
|
|
32596
|
+
plan_id: existing.plan_id || undefined,
|
|
32597
|
+
run_id: existing.run_id || undefined,
|
|
32598
|
+
expires_at: existing.expires_at || undefined
|
|
32599
|
+
}, "expired", { decided_by: input.reviewer, decided_at: now(), reason: input.reason || "expired" }, d);
|
|
32600
|
+
logApprovalEvent(input.task_id, "expired", gate, input.reviewer, d);
|
|
32601
|
+
return gate;
|
|
32602
|
+
}
|
|
32603
|
+
function listApprovalGates(taskId, db) {
|
|
32604
|
+
const d = db || getDatabase();
|
|
32605
|
+
ensureTask(taskId, d);
|
|
32606
|
+
return getTaskCheckpoints(taskId, d).filter((checkpoint) => checkpoint.data["approval_gate"] === true || checkpoint.step.startsWith("approval:")).map(gateFromCheckpoint);
|
|
32607
|
+
}
|
|
32608
|
+
function checkApprovalGate(taskId, gateName, db) {
|
|
32609
|
+
const d = db || getDatabase();
|
|
32610
|
+
ensureTask(taskId, d);
|
|
32611
|
+
const gate = currentGate(taskId, gateName, d);
|
|
32612
|
+
const reasons = [];
|
|
32613
|
+
if (!gate)
|
|
32614
|
+
reasons.push(`approval gate is required: ${gateName}`);
|
|
32615
|
+
else if (gate.status !== "approved")
|
|
32616
|
+
reasons.push(`approval gate ${gateName} is ${gate.status}`);
|
|
32617
|
+
if (gate && gate.status === "pending" && isExpired(gate.expires_at))
|
|
32618
|
+
reasons.push(`approval gate ${gateName} is expired`);
|
|
32619
|
+
return { allowed: reasons.length === 0, gate, reasons };
|
|
32620
|
+
}
|
|
32621
|
+
function assertApprovalGate(taskId, gateName, db) {
|
|
32622
|
+
const result = checkApprovalGate(taskId, gateName, db);
|
|
32623
|
+
if (!result.allowed)
|
|
32624
|
+
throw new Error(result.reasons.join("; "));
|
|
32625
|
+
return result.gate;
|
|
32626
|
+
}
|
|
32627
|
+
var init_approval_gates = __esm(() => {
|
|
32628
|
+
init_audit();
|
|
32629
|
+
init_checkpoints();
|
|
32630
|
+
init_database();
|
|
32631
|
+
init_task_runs();
|
|
32632
|
+
init_tasks();
|
|
32633
|
+
init_types();
|
|
32634
|
+
init_event_emission_safety();
|
|
32635
|
+
init_event_hooks();
|
|
32636
|
+
});
|
|
32637
|
+
|
|
32638
|
+
// src/ai-tools.ts
|
|
32639
|
+
import { createHash as createHash9 } from "crypto";
|
|
32640
|
+
function createLocalTodosAiReadAdapter(database) {
|
|
32641
|
+
return {
|
|
32642
|
+
source: "sqlite",
|
|
32643
|
+
getTask: (id) => getTask(id, database),
|
|
32644
|
+
listTasks: (filter) => listTasks(filter, database),
|
|
32645
|
+
listProjects: () => listProjects(database),
|
|
32646
|
+
listPlans: (projectId) => listPlans(projectId, database),
|
|
32647
|
+
updateTask: (id, patch) => updateTask(id, patch, database),
|
|
32648
|
+
verifyApproval: (input) => {
|
|
32649
|
+
const result = checkApprovalGate(input.task_id, input.ref, database);
|
|
32650
|
+
if (!result.gate)
|
|
32651
|
+
return null;
|
|
32652
|
+
return {
|
|
32653
|
+
ref: result.gate.gate,
|
|
32654
|
+
task_id: result.gate.task_id,
|
|
32655
|
+
operation: input.operation,
|
|
32656
|
+
payload_digest: input.payload_digest,
|
|
32657
|
+
status: result.gate.status,
|
|
32658
|
+
expires_at: result.gate.expires_at
|
|
32659
|
+
};
|
|
32660
|
+
}
|
|
32661
|
+
};
|
|
32662
|
+
}
|
|
32663
|
+
function createHttpTodosAiReadAdapter(client) {
|
|
32664
|
+
return {
|
|
32665
|
+
source: "http",
|
|
32666
|
+
getTask: (id) => cloudGetTask(client, id),
|
|
32667
|
+
listTasks: (filter) => cloudListTasks(client, filter),
|
|
32668
|
+
listProjects: () => cloudListProjects(client),
|
|
32669
|
+
listPlans: (projectId) => cloudListPlans(client, projectId),
|
|
32670
|
+
updateTask: (id, patch) => cloudUpdateTask(client, id, patch)
|
|
32671
|
+
};
|
|
32672
|
+
}
|
|
32673
|
+
function resolveAdapter(options) {
|
|
32674
|
+
if (options.adapter)
|
|
32675
|
+
return options.adapter;
|
|
32676
|
+
const client = getTodosCloudClient(options.env);
|
|
32677
|
+
return client ? createHttpTodosAiReadAdapter(client) : createLocalTodosAiReadAdapter(options.database);
|
|
32678
|
+
}
|
|
32679
|
+
function resolvePermission(options) {
|
|
32680
|
+
if (options.workspacePermission)
|
|
32681
|
+
return options.workspacePermission;
|
|
32682
|
+
const path = options.workspacePath ?? process.cwd();
|
|
32683
|
+
return (permission) => checkWorkspacePermission({ path, tool: permission }).allowed;
|
|
32684
|
+
}
|
|
32685
|
+
function configuredProfileIsKnown(value) {
|
|
32686
|
+
if (value === undefined || value.trim() === "")
|
|
32687
|
+
return true;
|
|
32688
|
+
const normalized = value.trim().toLowerCase();
|
|
32689
|
+
return ACCESS_PROFILES.includes(normalized) || normalized === "readonly" || normalized === "agent-safe";
|
|
32690
|
+
}
|
|
32691
|
+
function createTodosAiToolSource(options = {}) {
|
|
32692
|
+
const adapter = resolveAdapter(options);
|
|
32693
|
+
const configuredProfile = options.env ? options.env["TODOS_PROFILE"] : process.env["TODOS_PROFILE"];
|
|
32694
|
+
const profile = options.accessProfile ?? resolveAccessProfile(configuredProfile ?? "minimal");
|
|
32695
|
+
const permission = resolvePermission(options);
|
|
32696
|
+
const enabled = new Set(TODOS_AI_READ_TOOL_NAMES.filter((name) => shouldRegisterToolForProfile(name, profile) && permission(name === "get_task" ? "read" : "list", name)));
|
|
32697
|
+
const hostAllowsUpdate = (options.accessProfile !== undefined || configuredProfileIsKnown(configuredProfile)) && shouldRegisterToolForProfile("update_task", profile) && permission("write", "update_task");
|
|
32698
|
+
const approvalVerifier = options.approvalVerifier ?? adapter.verifyApproval;
|
|
32699
|
+
return ({ request }) => {
|
|
32700
|
+
const state = {
|
|
32701
|
+
calls: 0,
|
|
32702
|
+
evidence: [],
|
|
32703
|
+
idempotency: new Map,
|
|
32704
|
+
now: options.now ?? (() => new Date)
|
|
32705
|
+
};
|
|
32706
|
+
return createTools(adapter, enabled, state, request, hostAllowsUpdate, approvalVerifier);
|
|
32707
|
+
};
|
|
32708
|
+
}
|
|
32709
|
+
function createTools(adapter, enabled, state, request, hostAllowsUpdate, approvalVerifier) {
|
|
32710
|
+
const tools = [];
|
|
32711
|
+
if (enabled.has("get_task")) {
|
|
32712
|
+
tools.push({
|
|
32713
|
+
name: "get_task",
|
|
32714
|
+
description: "Read one Todos task by its stable identifier.",
|
|
32715
|
+
effect: "read",
|
|
32716
|
+
inputSchema: {
|
|
32717
|
+
type: "object",
|
|
32718
|
+
properties: {
|
|
32719
|
+
id: {
|
|
32720
|
+
type: "string",
|
|
32721
|
+
minLength: 1,
|
|
32722
|
+
maxLength: TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes
|
|
32723
|
+
}
|
|
32724
|
+
},
|
|
32725
|
+
required: ["id"],
|
|
32726
|
+
additionalProperties: false
|
|
32727
|
+
},
|
|
32728
|
+
execute: (input) => executeGetTask(adapter, state, request, input)
|
|
32729
|
+
});
|
|
32730
|
+
}
|
|
32731
|
+
if (enabled.has("list_tasks")) {
|
|
32732
|
+
tools.push({
|
|
32733
|
+
name: "list_tasks",
|
|
32734
|
+
description: "List a bounded set of Todos tasks using read-only filters.",
|
|
32735
|
+
effect: "read",
|
|
32736
|
+
inputSchema: {
|
|
32737
|
+
type: "object",
|
|
32738
|
+
properties: {
|
|
32739
|
+
project_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
|
|
32740
|
+
plan_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
|
|
32741
|
+
task_list_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
|
|
32742
|
+
assigned_to: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes),
|
|
32743
|
+
status: { type: "string", enum: [...TASK_STATUSES] },
|
|
32744
|
+
priority: { type: "string", enum: [...TASK_PRIORITIES] },
|
|
32745
|
+
tags: {
|
|
32746
|
+
type: "array",
|
|
32747
|
+
maxItems: TODOS_AI_READ_TOOL_LIMITS.max_tags,
|
|
32748
|
+
items: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes)
|
|
32749
|
+
},
|
|
32750
|
+
limit: {
|
|
32751
|
+
type: "integer",
|
|
32752
|
+
minimum: 1,
|
|
32753
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
|
|
32754
|
+
default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
|
|
32755
|
+
},
|
|
32756
|
+
offset: {
|
|
32757
|
+
type: "integer",
|
|
32758
|
+
minimum: 0,
|
|
32759
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
|
|
32760
|
+
}
|
|
32761
|
+
},
|
|
32762
|
+
additionalProperties: false
|
|
32763
|
+
},
|
|
32764
|
+
execute: (input) => executeListTasks(adapter, state, request, input)
|
|
32765
|
+
});
|
|
32766
|
+
}
|
|
32767
|
+
if (enabled.has("list_projects")) {
|
|
32768
|
+
tools.push({
|
|
32769
|
+
name: "list_projects",
|
|
32770
|
+
description: "List a bounded set of Todos projects.",
|
|
32771
|
+
effect: "read",
|
|
32772
|
+
inputSchema: listOnlySchema(),
|
|
32773
|
+
execute: (input) => executeListProjects(adapter, state, request, input)
|
|
32774
|
+
});
|
|
32775
|
+
}
|
|
32776
|
+
if (enabled.has("list_plans")) {
|
|
32777
|
+
tools.push({
|
|
32778
|
+
name: "list_plans",
|
|
32779
|
+
description: "List a bounded set of Todos plans, optionally scoped to a project.",
|
|
32780
|
+
effect: "read",
|
|
32781
|
+
inputSchema: {
|
|
32782
|
+
type: "object",
|
|
32783
|
+
properties: {
|
|
32784
|
+
project_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
|
|
32785
|
+
limit: {
|
|
32786
|
+
type: "integer",
|
|
32787
|
+
minimum: 1,
|
|
32788
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
|
|
32789
|
+
default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
|
|
32790
|
+
},
|
|
32791
|
+
offset: {
|
|
32792
|
+
type: "integer",
|
|
32793
|
+
minimum: 0,
|
|
32794
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
|
|
32795
|
+
}
|
|
32796
|
+
},
|
|
32797
|
+
additionalProperties: false
|
|
32798
|
+
},
|
|
32799
|
+
execute: (input) => executeListPlans(adapter, state, request, input)
|
|
32800
|
+
});
|
|
32801
|
+
}
|
|
32802
|
+
tools.push({
|
|
32803
|
+
name: "request_input",
|
|
32804
|
+
description: "Stop without mutation and request bounded clarification fields.",
|
|
32805
|
+
effect: "control",
|
|
32806
|
+
inputSchema: {
|
|
32807
|
+
type: "object",
|
|
32808
|
+
properties: {
|
|
32809
|
+
prompt: boundedStringSchema(TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes),
|
|
32810
|
+
fields: {
|
|
32811
|
+
type: "array",
|
|
32812
|
+
minItems: 1,
|
|
32813
|
+
maxItems: TODOS_AI_CONTROL_TOOL_LIMITS.max_fields,
|
|
32814
|
+
items: boundedStringSchema(TODOS_AI_CONTROL_TOOL_LIMITS.max_field_bytes)
|
|
32815
|
+
}
|
|
32816
|
+
},
|
|
32817
|
+
required: ["prompt", "fields"],
|
|
32818
|
+
additionalProperties: false
|
|
32819
|
+
},
|
|
32820
|
+
execute: (input) => executeRequestInput(state, input)
|
|
32821
|
+
});
|
|
32822
|
+
const authorityAllowsUpdate = request.authority.write_mode === "plan" || request.authority.write_mode === "execute" && request.authority.approval_mode !== "deny";
|
|
32823
|
+
if (hostAllowsUpdate && authorityAllowsUpdate) {
|
|
32824
|
+
tools.push({
|
|
32825
|
+
name: "update_task",
|
|
32826
|
+
description: "Plan or execute one approved, version-checked update of one exact Todos task.",
|
|
32827
|
+
effect: "write",
|
|
32828
|
+
inputSchema: updateTaskInputSchema(),
|
|
32829
|
+
execute: (input, context) => executeUpdateTask(adapter, approvalVerifier, state, request, input, context.signal)
|
|
32830
|
+
});
|
|
32831
|
+
}
|
|
32832
|
+
return tools;
|
|
32833
|
+
}
|
|
32834
|
+
function boundedStringSchema(maxLength) {
|
|
32835
|
+
return {
|
|
32836
|
+
type: "string",
|
|
32837
|
+
minLength: 1,
|
|
32838
|
+
maxLength
|
|
32839
|
+
};
|
|
32840
|
+
}
|
|
32841
|
+
function listOnlySchema() {
|
|
32842
|
+
return {
|
|
32843
|
+
type: "object",
|
|
32844
|
+
properties: {
|
|
32845
|
+
limit: {
|
|
32846
|
+
type: "integer",
|
|
32847
|
+
minimum: 1,
|
|
32848
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
|
|
32849
|
+
default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
|
|
32850
|
+
},
|
|
32851
|
+
offset: {
|
|
32852
|
+
type: "integer",
|
|
32853
|
+
minimum: 0,
|
|
32854
|
+
maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
|
|
32855
|
+
}
|
|
32856
|
+
},
|
|
32857
|
+
additionalProperties: false
|
|
32858
|
+
};
|
|
32859
|
+
}
|
|
32860
|
+
function updateTaskInputSchema() {
|
|
32861
|
+
return {
|
|
32862
|
+
type: "object",
|
|
32863
|
+
properties: {
|
|
32864
|
+
task_id: {
|
|
32865
|
+
type: "string",
|
|
32866
|
+
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
|
32867
|
+
},
|
|
32868
|
+
expected_version: {
|
|
32869
|
+
type: "integer",
|
|
32870
|
+
minimum: 0
|
|
32871
|
+
},
|
|
32872
|
+
patch: {
|
|
32873
|
+
type: "object",
|
|
32874
|
+
minProperties: 1,
|
|
32875
|
+
properties: {
|
|
32876
|
+
title: boundedStringSchema(TODOS_AI_UPDATE_TASK_LIMITS.max_title_bytes),
|
|
32877
|
+
description: {
|
|
32878
|
+
type: "string",
|
|
32879
|
+
maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_description_bytes
|
|
32880
|
+
},
|
|
32881
|
+
status: { type: "string", enum: [...TASK_STATUSES] },
|
|
32882
|
+
priority: { type: "string", enum: [...TASK_PRIORITIES] },
|
|
32883
|
+
assigned_to: {
|
|
32884
|
+
type: ["string", "null"],
|
|
32885
|
+
maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_assignee_bytes
|
|
32886
|
+
},
|
|
32887
|
+
tags: {
|
|
32888
|
+
type: "array",
|
|
32889
|
+
maxItems: TODOS_AI_UPDATE_TASK_LIMITS.max_tags,
|
|
32890
|
+
items: boundedStringSchema(TODOS_AI_UPDATE_TASK_LIMITS.max_tag_bytes)
|
|
32891
|
+
},
|
|
32892
|
+
due_at: {
|
|
32893
|
+
type: ["string", "null"],
|
|
32894
|
+
maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_due_at_bytes
|
|
32895
|
+
}
|
|
32896
|
+
},
|
|
32897
|
+
additionalProperties: false
|
|
32898
|
+
},
|
|
32899
|
+
idempotency_key: {
|
|
32900
|
+
type: "string",
|
|
32901
|
+
minLength: TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes,
|
|
32902
|
+
maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes,
|
|
32903
|
+
pattern: "^[A-Za-z0-9._:-]+$"
|
|
32904
|
+
}
|
|
32905
|
+
},
|
|
32906
|
+
required: ["task_id", "expected_version", "patch", "idempotency_key"],
|
|
32907
|
+
additionalProperties: false
|
|
32908
|
+
};
|
|
32909
|
+
}
|
|
32910
|
+
function deriveTodosAiUpdateTaskApprovalIdentity(input) {
|
|
32911
|
+
if (!UUID_RE2.test(input.task_id) || !Number.isSafeInteger(input.expected_version) || input.expected_version < 0) {
|
|
32912
|
+
throw new Error("update_task approval identity requires an exact task UUID and version");
|
|
32913
|
+
}
|
|
32914
|
+
const patch = normalizeUpdatePatch(input.patch);
|
|
32915
|
+
const canonical = JSON.stringify({
|
|
32916
|
+
operation: "update_task",
|
|
32917
|
+
task_id: input.task_id,
|
|
32918
|
+
expected_version: input.expected_version,
|
|
32919
|
+
patch
|
|
32920
|
+
});
|
|
32921
|
+
const payloadDigest = createHash9("sha256").update(canonical).digest("hex");
|
|
32922
|
+
return {
|
|
32923
|
+
ref: `todos-ai:update_task:${payloadDigest}`,
|
|
32924
|
+
payload_digest: payloadDigest
|
|
32925
|
+
};
|
|
32926
|
+
}
|
|
32927
|
+
function executeRequestInput(state, input) {
|
|
32928
|
+
beginCall(state);
|
|
32929
|
+
const record = assertInputObject(input, "request_input", ["prompt", "fields"]);
|
|
32930
|
+
const prompt = boundedRequiredString(record, "prompt", TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes);
|
|
32931
|
+
const rawFields = record["fields"];
|
|
32932
|
+
if (!Array.isArray(rawFields) || rawFields.length === 0 || rawFields.length > TODOS_AI_CONTROL_TOOL_LIMITS.max_fields) {
|
|
32933
|
+
throw new Error(`fields must contain 1 to ${TODOS_AI_CONTROL_TOOL_LIMITS.max_fields} strings`);
|
|
32934
|
+
}
|
|
32935
|
+
const fields = [];
|
|
32936
|
+
const seen = new Set;
|
|
32937
|
+
for (const [index, field] of rawFields.entries()) {
|
|
32938
|
+
if (typeof field !== "string" || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(field) || ENCODER.encode(field).byteLength > TODOS_AI_CONTROL_TOOL_LIMITS.max_field_bytes || seen.has(field)) {
|
|
32939
|
+
throw new Error(`fields[${index}] must be a unique bounded field name`);
|
|
32940
|
+
}
|
|
32941
|
+
seen.add(field);
|
|
32942
|
+
fields.push(field);
|
|
32943
|
+
}
|
|
32944
|
+
const redactedPrompt = truncateUtf8(redactEvidenceText(prompt), TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes);
|
|
32945
|
+
throw new TodosAiNeedsInputSignal({
|
|
32946
|
+
prompt: redactedPrompt || "Additional input is required.",
|
|
32947
|
+
fields
|
|
32948
|
+
});
|
|
32949
|
+
}
|
|
32950
|
+
async function executeUpdateTask(adapter, approvalVerifier, state, request, input, signal) {
|
|
32951
|
+
beginCall(state);
|
|
32952
|
+
const normalized = normalizeUpdateTaskInput(input);
|
|
32953
|
+
const existing = state.idempotency.get(normalized.idempotency_key);
|
|
32954
|
+
if (existing) {
|
|
32955
|
+
if (existing.payload_digest !== normalized.payload_digest) {
|
|
32956
|
+
throw new Error("update_task idempotency key was reused with a different payload");
|
|
32957
|
+
}
|
|
32958
|
+
return replayUpdateTaskResult(await existing.promise);
|
|
32959
|
+
}
|
|
32960
|
+
const claim = {
|
|
32961
|
+
payload_digest: normalized.payload_digest,
|
|
32962
|
+
mutation_started: false,
|
|
32963
|
+
promise: Promise.resolve({})
|
|
32964
|
+
};
|
|
32965
|
+
claim.promise = performUpdateTask(adapter, approvalVerifier, state, request, normalized, signal, claim);
|
|
32966
|
+
state.idempotency.set(normalized.idempotency_key, claim);
|
|
32967
|
+
try {
|
|
32968
|
+
return await claim.promise;
|
|
32969
|
+
} catch (error) {
|
|
32970
|
+
if (!claim.mutation_started) {
|
|
32971
|
+
state.idempotency.delete(normalized.idempotency_key);
|
|
32972
|
+
}
|
|
32973
|
+
throw error;
|
|
32974
|
+
}
|
|
32975
|
+
}
|
|
32976
|
+
async function performUpdateTask(adapter, approvalVerifier, state, request, normalized, signal, claim) {
|
|
32977
|
+
throwIfToolAborted(signal);
|
|
32978
|
+
const before = await adapter.getTask(normalized.task_id);
|
|
32979
|
+
throwIfToolAborted(signal);
|
|
32980
|
+
assertExactTaskVersion(before, normalized.task_id, normalized.expected_version);
|
|
32981
|
+
if (taskMatchesPatch(before, normalized.patch)) {
|
|
32982
|
+
throw new Error("update_task patch does not change the authoritative task");
|
|
32983
|
+
}
|
|
32984
|
+
if (request.authority.write_mode === "plan") {
|
|
32985
|
+
return updateTaskResult(normalized, {
|
|
32986
|
+
mode: "plan",
|
|
32987
|
+
applied: false,
|
|
32988
|
+
readback_verified: false,
|
|
32989
|
+
result_version: null,
|
|
32990
|
+
replay: false,
|
|
32991
|
+
source: adapter.source
|
|
32992
|
+
});
|
|
32993
|
+
}
|
|
32994
|
+
if (request.authority.write_mode !== "execute") {
|
|
32995
|
+
throw new Error("update_task is unavailable without plan or execute authority");
|
|
32996
|
+
}
|
|
32997
|
+
if (request.authority.approval_mode === "required" || request.authority.approval_mode === "prompt") {
|
|
32998
|
+
throw new TodosAiNeedsApprovalSignal({
|
|
32999
|
+
id: normalized.approval_ref,
|
|
33000
|
+
summary: "Approve one exact version-checked task update.",
|
|
33001
|
+
operations: [approvalOperation(normalized)]
|
|
33002
|
+
});
|
|
33003
|
+
}
|
|
33004
|
+
if (request.authority.approval_mode !== "existing") {
|
|
33005
|
+
throw new Error("update_task execution requires a verified existing approval");
|
|
33006
|
+
}
|
|
33007
|
+
await verifyExistingApproval(approvalVerifier, request, normalized, state.now());
|
|
33008
|
+
if (!adapter.updateTask) {
|
|
33009
|
+
throw new Error("update_task is unavailable for the selected authority");
|
|
33010
|
+
}
|
|
33011
|
+
throwIfToolAborted(signal);
|
|
33012
|
+
claim.mutation_started = true;
|
|
33013
|
+
let updateFailure = null;
|
|
33014
|
+
try {
|
|
33015
|
+
await adapter.updateTask(normalized.task_id, {
|
|
33016
|
+
...normalized.patch,
|
|
33017
|
+
version: normalized.expected_version
|
|
33018
|
+
});
|
|
33019
|
+
} catch (error) {
|
|
33020
|
+
updateFailure = error;
|
|
33021
|
+
}
|
|
33022
|
+
const after = await adapter.getTask(normalized.task_id);
|
|
33023
|
+
if (after && after.id === normalized.task_id && after.version === normalized.expected_version + 1 && taskMatchesPatch(after, normalized.patch)) {
|
|
33024
|
+
return updateTaskResult(normalized, {
|
|
33025
|
+
mode: "execute",
|
|
33026
|
+
applied: true,
|
|
33027
|
+
readback_verified: true,
|
|
33028
|
+
result_version: after.version,
|
|
33029
|
+
replay: false,
|
|
33030
|
+
source: adapter.source
|
|
33031
|
+
});
|
|
33032
|
+
}
|
|
33033
|
+
if (updateFailure !== null) {
|
|
33034
|
+
throw new Error("update_task mutation could not be reconciled by authoritative readback");
|
|
33035
|
+
}
|
|
33036
|
+
throw new Error("update_task authoritative readback did not verify the applied patch");
|
|
33037
|
+
}
|
|
33038
|
+
async function verifyExistingApproval(approvalVerifier, request, normalized, at) {
|
|
33039
|
+
if (request.authority.approval_refs.length !== 1 || request.authority.approval_refs[0] !== normalized.approval_ref) {
|
|
33040
|
+
throw new Error("update_task approval reference does not match the exact operation");
|
|
33041
|
+
}
|
|
33042
|
+
if (!approvalVerifier) {
|
|
33043
|
+
throw new Error("update_task approval cannot be verified for the selected authority");
|
|
33044
|
+
}
|
|
33045
|
+
const verification = await approvalVerifier({
|
|
33046
|
+
ref: normalized.approval_ref,
|
|
33047
|
+
task_id: normalized.task_id,
|
|
33048
|
+
operation: "update_task",
|
|
33049
|
+
payload_digest: normalized.payload_digest
|
|
33050
|
+
});
|
|
33051
|
+
if (!verification || verification.ref !== normalized.approval_ref || verification.task_id !== normalized.task_id || verification.operation !== "update_task" || verification.payload_digest !== normalized.payload_digest || verification.status !== "approved") {
|
|
33052
|
+
throw new Error("update_task approval is missing, rejected, expired, or unverifiable");
|
|
33053
|
+
}
|
|
33054
|
+
if (verification.expires_at !== null) {
|
|
33055
|
+
const expiry = Date.parse(verification.expires_at);
|
|
33056
|
+
if (!Number.isFinite(expiry) || expiry <= at.getTime()) {
|
|
33057
|
+
throw new Error("update_task approval is expired or unverifiable");
|
|
33058
|
+
}
|
|
33059
|
+
}
|
|
33060
|
+
}
|
|
33061
|
+
function normalizeUpdateTaskInput(input) {
|
|
33062
|
+
const record = assertInputObject(input, "update_task", [
|
|
33063
|
+
"task_id",
|
|
33064
|
+
"expected_version",
|
|
33065
|
+
"patch",
|
|
33066
|
+
"idempotency_key"
|
|
33067
|
+
]);
|
|
33068
|
+
const taskId = boundedRequiredString(record, "task_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33069
|
+
if (!UUID_RE2.test(taskId)) {
|
|
33070
|
+
throw new Error("task_id must be one exact task UUID");
|
|
33071
|
+
}
|
|
33072
|
+
const expectedVersion = boundedInteger(record, "expected_version", -1, 0, Number.MAX_SAFE_INTEGER);
|
|
33073
|
+
if (!Object.hasOwn(record, "expected_version")) {
|
|
33074
|
+
throw new Error("expected_version is required");
|
|
33075
|
+
}
|
|
33076
|
+
const idempotencyKey = boundedRequiredString(record, "idempotency_key", TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes);
|
|
33077
|
+
const idempotencyBytes = ENCODER.encode(idempotencyKey).byteLength;
|
|
33078
|
+
if (idempotencyBytes < TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes || !/^[A-Za-z0-9._:-]+$/.test(idempotencyKey)) {
|
|
33079
|
+
throw new Error("idempotency_key must be a bounded stable identifier");
|
|
33080
|
+
}
|
|
33081
|
+
const patchValue = record["patch"];
|
|
33082
|
+
if (!isTodosAiJsonValue(patchValue) || patchValue === null || typeof patchValue !== "object" || Array.isArray(patchValue)) {
|
|
33083
|
+
throw new Error("patch must be a stable JSON object");
|
|
33084
|
+
}
|
|
33085
|
+
const patch = normalizeUpdatePatch(patchValue);
|
|
33086
|
+
const changedFields = Object.keys(patch);
|
|
33087
|
+
const identity = deriveTodosAiUpdateTaskApprovalIdentity({
|
|
33088
|
+
task_id: taskId,
|
|
33089
|
+
expected_version: expectedVersion,
|
|
33090
|
+
patch
|
|
33091
|
+
});
|
|
33092
|
+
return {
|
|
33093
|
+
task_id: taskId,
|
|
33094
|
+
expected_version: expectedVersion,
|
|
33095
|
+
patch,
|
|
33096
|
+
changed_fields: changedFields,
|
|
33097
|
+
idempotency_key: idempotencyKey,
|
|
33098
|
+
payload_digest: identity.payload_digest,
|
|
33099
|
+
approval_ref: identity.ref
|
|
33100
|
+
};
|
|
33101
|
+
}
|
|
33102
|
+
function normalizeUpdatePatch(value) {
|
|
33103
|
+
if (!isTodosAiJsonValue(value) || value === null || Array.isArray(value)) {
|
|
33104
|
+
throw new Error("patch must be a stable JSON object");
|
|
33105
|
+
}
|
|
33106
|
+
const allowed = new Set(TODOS_AI_UPDATE_TASK_FIELDS);
|
|
33107
|
+
const supplied = Object.keys(value);
|
|
33108
|
+
if (supplied.length === 0)
|
|
33109
|
+
throw new Error("patch must contain at least one field");
|
|
33110
|
+
for (const key of supplied) {
|
|
33111
|
+
if (!allowed.has(key)) {
|
|
33112
|
+
throw new Error(`update_task patch contains unsupported field: ${key}`);
|
|
33113
|
+
}
|
|
33114
|
+
}
|
|
33115
|
+
const patch = {};
|
|
33116
|
+
for (const field of TODOS_AI_UPDATE_TASK_FIELDS) {
|
|
33117
|
+
if (!Object.hasOwn(value, field))
|
|
33118
|
+
continue;
|
|
33119
|
+
const candidate = value[field];
|
|
33120
|
+
switch (field) {
|
|
33121
|
+
case "title":
|
|
33122
|
+
if (typeof candidate !== "string" || candidate.trim().length === 0 || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_title_bytes) {
|
|
33123
|
+
throw new Error("patch.title must be a bounded non-empty string");
|
|
33124
|
+
}
|
|
33125
|
+
patch[field] = candidate;
|
|
33126
|
+
break;
|
|
33127
|
+
case "description":
|
|
33128
|
+
if (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_description_bytes) {
|
|
33129
|
+
throw new Error("patch.description must be a bounded string");
|
|
33130
|
+
}
|
|
33131
|
+
patch[field] = candidate;
|
|
33132
|
+
break;
|
|
33133
|
+
case "status":
|
|
33134
|
+
if (typeof candidate !== "string" || !TASK_STATUSES.includes(candidate)) {
|
|
33135
|
+
throw new Error(`patch.status must be one of: ${TASK_STATUSES.join(", ")}`);
|
|
33136
|
+
}
|
|
33137
|
+
patch[field] = candidate;
|
|
33138
|
+
break;
|
|
33139
|
+
case "priority":
|
|
33140
|
+
if (typeof candidate !== "string" || !TASK_PRIORITIES.includes(candidate)) {
|
|
33141
|
+
throw new Error(`patch.priority must be one of: ${TASK_PRIORITIES.join(", ")}`);
|
|
33142
|
+
}
|
|
33143
|
+
patch[field] = candidate;
|
|
33144
|
+
break;
|
|
33145
|
+
case "assigned_to":
|
|
33146
|
+
if (candidate !== null && (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_assignee_bytes)) {
|
|
33147
|
+
throw new Error("patch.assigned_to must be null or a bounded string");
|
|
33148
|
+
}
|
|
33149
|
+
patch[field] = candidate;
|
|
33150
|
+
break;
|
|
33151
|
+
case "tags": {
|
|
33152
|
+
if (!Array.isArray(candidate) || candidate.length > TODOS_AI_UPDATE_TASK_LIMITS.max_tags) {
|
|
33153
|
+
throw new Error(`patch.tags must contain at most ${TODOS_AI_UPDATE_TASK_LIMITS.max_tags} strings`);
|
|
33154
|
+
}
|
|
33155
|
+
const tags = [];
|
|
33156
|
+
const seen = new Set;
|
|
33157
|
+
for (const [index, tag] of candidate.entries()) {
|
|
33158
|
+
if (typeof tag !== "string" || tag.length === 0 || ENCODER.encode(tag).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_tag_bytes || seen.has(tag)) {
|
|
33159
|
+
throw new Error(`patch.tags[${index}] must be a unique bounded string`);
|
|
33160
|
+
}
|
|
33161
|
+
seen.add(tag);
|
|
33162
|
+
tags.push(tag);
|
|
33163
|
+
}
|
|
33164
|
+
patch[field] = tags;
|
|
33165
|
+
break;
|
|
33166
|
+
}
|
|
33167
|
+
case "due_at":
|
|
33168
|
+
if (candidate !== null && (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_due_at_bytes || !Number.isFinite(Date.parse(candidate)))) {
|
|
33169
|
+
throw new Error("patch.due_at must be null or a bounded ISO date-time");
|
|
33170
|
+
}
|
|
33171
|
+
patch[field] = candidate;
|
|
33172
|
+
break;
|
|
33173
|
+
}
|
|
33174
|
+
}
|
|
33175
|
+
return patch;
|
|
33176
|
+
}
|
|
33177
|
+
function assertExactTaskVersion(task, taskId, expectedVersion) {
|
|
33178
|
+
if (!task || task.id !== taskId) {
|
|
33179
|
+
throw new Error("update_task exact target was not found");
|
|
33180
|
+
}
|
|
33181
|
+
if (task.version !== expectedVersion) {
|
|
33182
|
+
throw new Error("update_task expected_version is stale");
|
|
33183
|
+
}
|
|
33184
|
+
}
|
|
33185
|
+
function taskMatchesPatch(task, patch) {
|
|
33186
|
+
return Object.entries(patch).every(([field, expected]) => {
|
|
33187
|
+
const actual = task[field];
|
|
33188
|
+
return Array.isArray(expected) ? Array.isArray(actual) && JSON.stringify(actual) === JSON.stringify(expected) : actual === expected;
|
|
33189
|
+
});
|
|
33190
|
+
}
|
|
33191
|
+
function approvalOperation(normalized) {
|
|
33192
|
+
return {
|
|
33193
|
+
operation: "update_task",
|
|
33194
|
+
task_id: normalized.task_id,
|
|
33195
|
+
expected_version: normalized.expected_version,
|
|
33196
|
+
fields: normalized.changed_fields,
|
|
33197
|
+
payload_digest: normalized.payload_digest
|
|
33198
|
+
};
|
|
33199
|
+
}
|
|
33200
|
+
function updateTaskResult(normalized, result) {
|
|
33201
|
+
const value = {
|
|
33202
|
+
schema: TODOS_AI_UPDATE_TASK_RESULT_SCHEMA,
|
|
33203
|
+
operation: "update_task",
|
|
33204
|
+
mode: result.mode,
|
|
33205
|
+
applied: result.applied,
|
|
33206
|
+
readback_verified: result.readback_verified,
|
|
33207
|
+
source: result.source,
|
|
33208
|
+
target: {
|
|
33209
|
+
task_id: normalized.task_id,
|
|
33210
|
+
expected_version: normalized.expected_version,
|
|
33211
|
+
result_version: result.result_version
|
|
33212
|
+
},
|
|
33213
|
+
changed_fields: normalized.changed_fields,
|
|
33214
|
+
approval_ref: normalized.approval_ref,
|
|
33215
|
+
payload_digest: normalized.payload_digest,
|
|
33216
|
+
idempotency: {
|
|
33217
|
+
key: normalized.idempotency_key,
|
|
33218
|
+
scope: "run",
|
|
33219
|
+
replay: result.replay
|
|
33220
|
+
}
|
|
33221
|
+
};
|
|
33222
|
+
if (!isTodosAiUpdateTaskResult(value)) {
|
|
33223
|
+
throw new Error("update_task result does not satisfy its stable receipt contract");
|
|
33224
|
+
}
|
|
33225
|
+
return value;
|
|
33226
|
+
}
|
|
33227
|
+
function replayUpdateTaskResult(value) {
|
|
33228
|
+
const idempotency = value["idempotency"];
|
|
33229
|
+
if (!idempotency || typeof idempotency !== "object" || Array.isArray(idempotency)) {
|
|
33230
|
+
throw new Error("update_task cached result is invalid");
|
|
33231
|
+
}
|
|
33232
|
+
const replayed = {
|
|
33233
|
+
...value,
|
|
33234
|
+
idempotency: {
|
|
33235
|
+
...idempotency,
|
|
33236
|
+
replay: true
|
|
33237
|
+
}
|
|
33238
|
+
};
|
|
33239
|
+
if (!isTodosAiUpdateTaskResult(replayed)) {
|
|
33240
|
+
throw new Error("update_task cached result is invalid");
|
|
33241
|
+
}
|
|
33242
|
+
return replayed;
|
|
33243
|
+
}
|
|
33244
|
+
function throwIfToolAborted(signal) {
|
|
33245
|
+
if (signal.aborted)
|
|
33246
|
+
throw new DOMException("Aborted", "AbortError");
|
|
33247
|
+
}
|
|
33248
|
+
function beginCall(state) {
|
|
33249
|
+
if (state.calls >= TODOS_AI_READ_TOOL_LIMITS.max_tool_calls) {
|
|
33250
|
+
throw new Error(`Todos AI tool-call limit of ${TODOS_AI_READ_TOOL_LIMITS.max_tool_calls} exceeded`);
|
|
33251
|
+
}
|
|
33252
|
+
state.calls += 1;
|
|
33253
|
+
}
|
|
33254
|
+
function assertInputObject(input, tool, allowed) {
|
|
33255
|
+
if (!isTodosAiJsonValue(input) || input === null || Array.isArray(input)) {
|
|
33256
|
+
throw new Error(`${tool} input must be a stable JSON object`);
|
|
33257
|
+
}
|
|
33258
|
+
const record = input;
|
|
33259
|
+
const allowedKeys = new Set(allowed);
|
|
33260
|
+
for (const key of Object.keys(record)) {
|
|
33261
|
+
if (!allowedKeys.has(key))
|
|
33262
|
+
throw new Error(`${tool} input contains unsupported field: ${key}`);
|
|
33263
|
+
}
|
|
33264
|
+
return record;
|
|
33265
|
+
}
|
|
33266
|
+
function boundedRequiredString(record, key, maximum) {
|
|
33267
|
+
const value = record[key];
|
|
33268
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
33269
|
+
throw new Error(`${key} must be a non-empty string`);
|
|
33270
|
+
}
|
|
33271
|
+
if (ENCODER.encode(value).byteLength > maximum) {
|
|
33272
|
+
throw new Error(`${key} exceeds ${maximum} bytes`);
|
|
33273
|
+
}
|
|
33274
|
+
return value;
|
|
33275
|
+
}
|
|
33276
|
+
function boundedOptionalString(record, key, maximum) {
|
|
33277
|
+
if (!Object.hasOwn(record, key))
|
|
33278
|
+
return;
|
|
33279
|
+
return boundedRequiredString(record, key, maximum);
|
|
33280
|
+
}
|
|
33281
|
+
function boundedInteger(record, key, fallback, minimum, maximum) {
|
|
33282
|
+
if (!Object.hasOwn(record, key))
|
|
33283
|
+
return fallback;
|
|
33284
|
+
const value = record[key];
|
|
33285
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
33286
|
+
throw new Error(`${key} must be an integer from ${minimum} to ${maximum}`);
|
|
33287
|
+
}
|
|
33288
|
+
return value;
|
|
33289
|
+
}
|
|
33290
|
+
function boundedEnum(record, key, values) {
|
|
33291
|
+
if (!Object.hasOwn(record, key))
|
|
33292
|
+
return;
|
|
33293
|
+
const value = record[key];
|
|
33294
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
33295
|
+
throw new Error(`${key} must be one of: ${values.join(", ")}`);
|
|
33296
|
+
}
|
|
33297
|
+
return value;
|
|
33298
|
+
}
|
|
33299
|
+
function boundedTags(record) {
|
|
33300
|
+
if (!Object.hasOwn(record, "tags"))
|
|
33301
|
+
return;
|
|
33302
|
+
const value = record["tags"];
|
|
33303
|
+
if (!Array.isArray(value) || value.length > TODOS_AI_READ_TOOL_LIMITS.max_tags) {
|
|
33304
|
+
throw new Error(`tags must contain at most ${TODOS_AI_READ_TOOL_LIMITS.max_tags} strings`);
|
|
33305
|
+
}
|
|
33306
|
+
return value.map((tag, index) => {
|
|
33307
|
+
if (typeof tag !== "string" || tag.length === 0) {
|
|
33308
|
+
throw new Error(`tags[${index}] must be a non-empty string`);
|
|
33309
|
+
}
|
|
33310
|
+
if (ENCODER.encode(tag).byteLength > TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes) {
|
|
33311
|
+
throw new Error(`tags[${index}] exceeds ${TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes} bytes`);
|
|
33312
|
+
}
|
|
33313
|
+
return tag;
|
|
33314
|
+
});
|
|
33315
|
+
}
|
|
33316
|
+
async function executeGetTask(adapter, state, request, input) {
|
|
33317
|
+
beginCall(state);
|
|
33318
|
+
const record = assertInputObject(input, "get_task", ["id"]);
|
|
33319
|
+
const id = boundedRequiredString(record, "id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33320
|
+
const task = await adapter.getTask(id);
|
|
33321
|
+
const item = task ? compactTask(task) : null;
|
|
33322
|
+
const evidence = task ? [taskEvidence(task)] : [];
|
|
33323
|
+
return boundedSingleResult(adapter.source, "get_task", item, evidence, state, request);
|
|
33324
|
+
}
|
|
33325
|
+
async function executeListTasks(adapter, state, request, input) {
|
|
33326
|
+
beginCall(state);
|
|
33327
|
+
const record = assertInputObject(input, "list_tasks", [
|
|
33328
|
+
"project_id",
|
|
33329
|
+
"plan_id",
|
|
33330
|
+
"task_list_id",
|
|
33331
|
+
"assigned_to",
|
|
33332
|
+
"status",
|
|
33333
|
+
"priority",
|
|
33334
|
+
"tags",
|
|
33335
|
+
"limit",
|
|
33336
|
+
"offset"
|
|
33337
|
+
]);
|
|
33338
|
+
const { limit, offset } = listWindow(record);
|
|
33339
|
+
const filter = {
|
|
33340
|
+
limit: limit + 1,
|
|
33341
|
+
offset
|
|
33342
|
+
};
|
|
33343
|
+
const projectId = boundedOptionalString(record, "project_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33344
|
+
const planId = boundedOptionalString(record, "plan_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33345
|
+
const taskListId = boundedOptionalString(record, "task_list_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33346
|
+
const assignedTo = boundedOptionalString(record, "assigned_to", TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes);
|
|
33347
|
+
const status = boundedEnum(record, "status", TASK_STATUSES);
|
|
33348
|
+
const priority = boundedEnum(record, "priority", TASK_PRIORITIES);
|
|
33349
|
+
const tags = boundedTags(record);
|
|
33350
|
+
if (projectId !== undefined)
|
|
33351
|
+
filter.project_id = projectId;
|
|
33352
|
+
if (planId !== undefined)
|
|
33353
|
+
filter.plan_id = planId;
|
|
33354
|
+
if (taskListId !== undefined)
|
|
33355
|
+
filter.task_list_id = taskListId;
|
|
33356
|
+
if (assignedTo !== undefined)
|
|
33357
|
+
filter.assigned_to = assignedTo;
|
|
33358
|
+
if (status !== undefined)
|
|
33359
|
+
filter.status = status;
|
|
33360
|
+
if (priority !== undefined)
|
|
33361
|
+
filter.priority = priority;
|
|
33362
|
+
if (tags !== undefined)
|
|
33363
|
+
filter.tags = tags;
|
|
33364
|
+
const rows = await adapter.listTasks(filter);
|
|
33365
|
+
return boundedListResult(adapter.source, "list_tasks", rows.map(compactTask), rows.map(taskEvidence), limit, offset, state, request);
|
|
33366
|
+
}
|
|
33367
|
+
async function executeListProjects(adapter, state, request, input) {
|
|
33368
|
+
beginCall(state);
|
|
33369
|
+
const record = assertInputObject(input, "list_projects", ["limit", "offset"]);
|
|
33370
|
+
const { limit, offset } = listWindow(record);
|
|
33371
|
+
const rows = await adapter.listProjects();
|
|
33372
|
+
const window = rows.slice(offset, offset + limit + 1);
|
|
33373
|
+
return boundedListResult(adapter.source, "list_projects", window.map(compactProject), window.map(projectEvidence), limit, offset, state, request);
|
|
33374
|
+
}
|
|
33375
|
+
async function executeListPlans(adapter, state, request, input) {
|
|
33376
|
+
beginCall(state);
|
|
33377
|
+
const record = assertInputObject(input, "list_plans", [
|
|
33378
|
+
"project_id",
|
|
33379
|
+
"limit",
|
|
33380
|
+
"offset"
|
|
33381
|
+
]);
|
|
33382
|
+
const { limit, offset } = listWindow(record);
|
|
33383
|
+
const projectId = boundedOptionalString(record, "project_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
|
|
33384
|
+
const rows = await adapter.listPlans(projectId);
|
|
33385
|
+
const window = rows.slice(offset, offset + limit + 1);
|
|
33386
|
+
return boundedListResult(adapter.source, "list_plans", window.map(compactPlan), window.map(planEvidence), limit, offset, state, request);
|
|
33387
|
+
}
|
|
33388
|
+
function listWindow(record) {
|
|
33389
|
+
return {
|
|
33390
|
+
limit: boundedInteger(record, "limit", TODOS_AI_READ_TOOL_LIMITS.default_list_items, 1, TODOS_AI_READ_TOOL_LIMITS.max_list_items),
|
|
33391
|
+
offset: boundedInteger(record, "offset", 0, 0, TODOS_AI_READ_TOOL_LIMITS.max_list_offset)
|
|
33392
|
+
};
|
|
33393
|
+
}
|
|
33394
|
+
function compactTask(task) {
|
|
33395
|
+
if (!TASK_STATUSES.includes(task.status)) {
|
|
33396
|
+
throw new Error("Todos AI task result has an invalid status");
|
|
33397
|
+
}
|
|
33398
|
+
if (!TASK_PRIORITIES.includes(task.priority)) {
|
|
33399
|
+
throw new Error("Todos AI task result has an invalid priority");
|
|
33400
|
+
}
|
|
33401
|
+
return {
|
|
33402
|
+
id: boundedOutputText(task.id),
|
|
33403
|
+
short_id: boundedNullableOutputText(task.short_id),
|
|
33404
|
+
version: requiredNonNegativeInteger(task.version, "task version"),
|
|
33405
|
+
project_id: boundedNullableOutputText(task.project_id),
|
|
33406
|
+
parent_id: boundedNullableOutputText(task.parent_id),
|
|
33407
|
+
plan_id: boundedNullableOutputText(task.plan_id),
|
|
33408
|
+
task_list_id: boundedNullableOutputText(task.task_list_id),
|
|
33409
|
+
title: boundedOutputText(task.title),
|
|
33410
|
+
description: boundedNullableOutputText(task.description),
|
|
33411
|
+
status: task.status,
|
|
33412
|
+
priority: task.priority,
|
|
33413
|
+
assigned_to: boundedNullableOutputText(task.assigned_to),
|
|
33414
|
+
agent_id: boundedNullableOutputText(task.agent_id),
|
|
33415
|
+
tags: boundedOutputTags(task.tags),
|
|
33416
|
+
task_type: boundedNullableOutputText(task.task_type),
|
|
33417
|
+
created_by: boundedNullableOutputText(task.created_by),
|
|
33418
|
+
assigned_by: boundedNullableOutputText(task.assigned_by),
|
|
33419
|
+
created_at: boundedOutputText(task.created_at),
|
|
33420
|
+
updated_at: boundedOutputText(task.updated_at),
|
|
33421
|
+
started_at: boundedNullableOutputText(task.started_at),
|
|
33422
|
+
completed_at: boundedNullableOutputText(task.completed_at),
|
|
33423
|
+
due_at: boundedNullableOutputText(task.due_at)
|
|
33424
|
+
};
|
|
33425
|
+
}
|
|
33426
|
+
function compactProject(project) {
|
|
33427
|
+
return {
|
|
33428
|
+
id: boundedOutputText(project.id),
|
|
33429
|
+
name: boundedOutputText(project.name),
|
|
33430
|
+
description: boundedNullableOutputText(project.description),
|
|
33431
|
+
task_list_id: boundedNullableOutputText(project.task_list_id),
|
|
33432
|
+
task_prefix: boundedNullableOutputText(project.task_prefix),
|
|
33433
|
+
created_at: boundedOutputText(project.created_at),
|
|
33434
|
+
updated_at: boundedOutputText(project.updated_at)
|
|
33435
|
+
};
|
|
33436
|
+
}
|
|
33437
|
+
function compactPlan(plan) {
|
|
33438
|
+
if (!PLAN_STATUSES.includes(plan.status)) {
|
|
33439
|
+
throw new Error("Todos AI plan result has an invalid status");
|
|
33440
|
+
}
|
|
33441
|
+
return {
|
|
33442
|
+
id: boundedOutputText(plan.id),
|
|
33443
|
+
slug: boundedNullableOutputText(plan.slug),
|
|
33444
|
+
project_id: boundedNullableOutputText(plan.project_id),
|
|
33445
|
+
task_list_id: boundedNullableOutputText(plan.task_list_id),
|
|
33446
|
+
agent_id: boundedNullableOutputText(plan.agent_id),
|
|
33447
|
+
name: boundedOutputText(plan.name),
|
|
33448
|
+
description: boundedNullableOutputText(plan.description),
|
|
33449
|
+
status: plan.status,
|
|
33450
|
+
created_at: boundedOutputText(plan.created_at),
|
|
33451
|
+
updated_at: boundedOutputText(plan.updated_at)
|
|
33452
|
+
};
|
|
33453
|
+
}
|
|
33454
|
+
function taskEvidence(task) {
|
|
33455
|
+
return {
|
|
33456
|
+
resource: "task",
|
|
33457
|
+
id: boundedOutputText(task.id),
|
|
33458
|
+
version: requiredNonNegativeInteger(task.version, "task version")
|
|
33459
|
+
};
|
|
33460
|
+
}
|
|
33461
|
+
function projectEvidence(project) {
|
|
33462
|
+
return {
|
|
33463
|
+
resource: "project",
|
|
33464
|
+
id: boundedOutputText(project.id),
|
|
33465
|
+
version: boundedOutputText(project.updated_at)
|
|
33466
|
+
};
|
|
33467
|
+
}
|
|
33468
|
+
function planEvidence(plan) {
|
|
33469
|
+
return {
|
|
33470
|
+
resource: "plan",
|
|
33471
|
+
id: boundedOutputText(plan.id),
|
|
33472
|
+
version: boundedOutputText(plan.updated_at)
|
|
33473
|
+
};
|
|
33474
|
+
}
|
|
33475
|
+
function boundedOutputTags(tags) {
|
|
33476
|
+
if (!Array.isArray(tags))
|
|
33477
|
+
return [];
|
|
33478
|
+
return tags.filter((tag) => typeof tag === "string").slice(0, TODOS_AI_READ_TOOL_LIMITS.max_output_tags).map(boundedOutputText);
|
|
33479
|
+
}
|
|
33480
|
+
function boundedNullableOutputText(value) {
|
|
33481
|
+
return typeof value === "string" ? boundedOutputText(value) : null;
|
|
33482
|
+
}
|
|
33483
|
+
function boundedOutputText(value) {
|
|
33484
|
+
return truncateUtf8(redactEvidenceText(value), TODOS_AI_READ_TOOL_LIMITS.max_output_string_bytes);
|
|
33485
|
+
}
|
|
33486
|
+
function requiredNonNegativeInteger(value, field) {
|
|
33487
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
33488
|
+
throw new Error(`Todos AI result has an invalid ${field}`);
|
|
33489
|
+
}
|
|
33490
|
+
return value;
|
|
33491
|
+
}
|
|
33492
|
+
function truncateUtf8(value, maximum) {
|
|
33493
|
+
if (ENCODER.encode(value).byteLength <= maximum)
|
|
33494
|
+
return value;
|
|
33495
|
+
const suffix = "...";
|
|
33496
|
+
const suffixBytes = ENCODER.encode(suffix).byteLength;
|
|
33497
|
+
let output2 = "";
|
|
33498
|
+
let bytes = 0;
|
|
33499
|
+
for (const character of value) {
|
|
33500
|
+
const characterBytes = ENCODER.encode(character).byteLength;
|
|
33501
|
+
if (bytes + characterBytes + suffixBytes > maximum)
|
|
33502
|
+
break;
|
|
33503
|
+
output2 += character;
|
|
33504
|
+
bytes += characterBytes;
|
|
33505
|
+
}
|
|
33506
|
+
return `${output2}${suffix}`;
|
|
33507
|
+
}
|
|
33508
|
+
function boundedSingleResult(source, tool, item, evidence, state, request) {
|
|
33509
|
+
const mergedEvidence = mergeEvidence(state.evidence, evidence);
|
|
33510
|
+
const result = redactValue({
|
|
33511
|
+
source,
|
|
33512
|
+
tool,
|
|
33513
|
+
item,
|
|
33514
|
+
evidence,
|
|
33515
|
+
context: runContext(request, state.calls, mergedEvidence)
|
|
33516
|
+
});
|
|
33517
|
+
assertResultBound(result);
|
|
33518
|
+
state.evidence = mergedEvidence;
|
|
33519
|
+
return result;
|
|
33520
|
+
}
|
|
33521
|
+
function boundedListResult(source, tool, sourceItems, sourceEvidence, limit, offset, state, request) {
|
|
33522
|
+
const available = Math.min(sourceItems.length, sourceEvidence.length);
|
|
33523
|
+
let count = Math.min(limit, available);
|
|
33524
|
+
let truncated = available > count;
|
|
33525
|
+
while (count >= 0) {
|
|
33526
|
+
const items = sourceItems.slice(0, count);
|
|
33527
|
+
const evidence = sourceEvidence.slice(0, count);
|
|
33528
|
+
const mergedEvidence = mergeEvidence(state.evidence, evidence);
|
|
33529
|
+
const result = redactValue({
|
|
33530
|
+
source,
|
|
33531
|
+
tool,
|
|
33532
|
+
items,
|
|
33533
|
+
returned: items.length,
|
|
33534
|
+
offset,
|
|
33535
|
+
truncated,
|
|
33536
|
+
evidence,
|
|
33537
|
+
context: runContext(request, state.calls, mergedEvidence)
|
|
33538
|
+
});
|
|
33539
|
+
if (serializedBytes(result) <= TODOS_AI_READ_TOOL_LIMITS.max_result_bytes) {
|
|
33540
|
+
state.evidence = mergedEvidence;
|
|
33541
|
+
return result;
|
|
33542
|
+
}
|
|
33543
|
+
count -= 1;
|
|
33544
|
+
truncated = true;
|
|
33545
|
+
}
|
|
33546
|
+
throw new Error("Todos AI read result could not be bounded");
|
|
33547
|
+
}
|
|
33548
|
+
function runContext(request, calls, evidence) {
|
|
33549
|
+
return {
|
|
33550
|
+
project: boundedNullableOutputText(request.context.project),
|
|
33551
|
+
agent: boundedNullableOutputText(request.context.agent),
|
|
33552
|
+
session: boundedNullableOutputText(request.context.session),
|
|
33553
|
+
tool_calls: calls,
|
|
33554
|
+
evidence
|
|
33555
|
+
};
|
|
33556
|
+
}
|
|
33557
|
+
function mergeEvidence(existing, incoming) {
|
|
33558
|
+
const merged = [];
|
|
33559
|
+
const seen = new Set;
|
|
33560
|
+
for (const pointer of [...existing, ...incoming]) {
|
|
33561
|
+
const key = JSON.stringify(pointer);
|
|
33562
|
+
if (seen.has(key))
|
|
33563
|
+
continue;
|
|
33564
|
+
seen.add(key);
|
|
33565
|
+
merged.push(pointer);
|
|
33566
|
+
if (merged.length >= TODOS_AI_READ_TOOL_LIMITS.max_evidence_pointers)
|
|
33567
|
+
break;
|
|
33568
|
+
}
|
|
33569
|
+
return merged;
|
|
33570
|
+
}
|
|
33571
|
+
function assertResultBound(value) {
|
|
33572
|
+
if (serializedBytes(value) > TODOS_AI_READ_TOOL_LIMITS.max_result_bytes) {
|
|
33573
|
+
throw new Error(`Todos AI read result exceeds ${TODOS_AI_READ_TOOL_LIMITS.max_result_bytes} bytes`);
|
|
33574
|
+
}
|
|
33575
|
+
}
|
|
33576
|
+
function serializedBytes(value) {
|
|
33577
|
+
return ENCODER.encode(JSON.stringify(value)).byteLength;
|
|
33578
|
+
}
|
|
33579
|
+
var TODOS_AI_READ_TOOL_NAMES, TODOS_AI_READ_TOOL_LIMITS, TODOS_AI_CONTROL_TOOL_LIMITS, ENCODER, UUID_RE2;
|
|
33580
|
+
var init_ai_tools = __esm(() => {
|
|
33581
|
+
init_ai();
|
|
33582
|
+
init_ai();
|
|
33583
|
+
init_cloud_router();
|
|
33584
|
+
init_task_crud();
|
|
33585
|
+
init_projects();
|
|
33586
|
+
init_plans();
|
|
33587
|
+
init_access_profiles();
|
|
33588
|
+
init_approval_gates();
|
|
33589
|
+
init_redaction();
|
|
33590
|
+
init_workspace_trust();
|
|
33591
|
+
init_types();
|
|
33592
|
+
TODOS_AI_READ_TOOL_NAMES = [
|
|
33593
|
+
"get_task",
|
|
33594
|
+
"list_tasks",
|
|
33595
|
+
"list_projects",
|
|
33596
|
+
"list_plans"
|
|
33597
|
+
];
|
|
33598
|
+
TODOS_AI_READ_TOOL_LIMITS = {
|
|
33599
|
+
default_list_items: 20,
|
|
33600
|
+
max_list_items: 50,
|
|
33601
|
+
max_list_offset: 1e4,
|
|
33602
|
+
max_identifier_bytes: 256,
|
|
33603
|
+
max_filter_string_bytes: 256,
|
|
33604
|
+
max_tags: 16,
|
|
33605
|
+
max_output_tags: 16,
|
|
33606
|
+
max_output_string_bytes: 2048,
|
|
33607
|
+
max_result_bytes: 65536,
|
|
33608
|
+
max_tool_calls: 32,
|
|
33609
|
+
max_evidence_pointers: 32
|
|
33610
|
+
};
|
|
33611
|
+
TODOS_AI_CONTROL_TOOL_LIMITS = {
|
|
33612
|
+
max_prompt_bytes: 1024,
|
|
33613
|
+
max_fields: 16,
|
|
33614
|
+
max_field_bytes: 128
|
|
33615
|
+
};
|
|
33616
|
+
ENCODER = new TextEncoder;
|
|
33617
|
+
UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33618
|
+
});
|
|
33619
|
+
|
|
33620
|
+
// src/cli/commands/ai-commands.ts
|
|
33621
|
+
var exports_ai_commands = {};
|
|
33622
|
+
__export(exports_ai_commands, {
|
|
33623
|
+
registerAiCommands: () => registerAiCommands
|
|
33624
|
+
});
|
|
33625
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
33626
|
+
import { writeSync as writeSync2 } from "fs";
|
|
33627
|
+
function appendOption(value, previous) {
|
|
33628
|
+
return [...previous, value];
|
|
33629
|
+
}
|
|
33630
|
+
function globalOptions(program2) {
|
|
33631
|
+
const command = program2;
|
|
33632
|
+
return command.optsWithGlobals?.() ?? program2.opts();
|
|
33633
|
+
}
|
|
33634
|
+
function commandRawArgs(command) {
|
|
33635
|
+
let root = command;
|
|
33636
|
+
while (root.parent)
|
|
33637
|
+
root = root.parent;
|
|
33638
|
+
const rawArgs = root.rawArgs;
|
|
33639
|
+
return rawArgs && rawArgs.length > 0 ? rawArgs : process.argv;
|
|
33640
|
+
}
|
|
33641
|
+
function commanderJsonRequested(command) {
|
|
33642
|
+
const args = commandRawArgs(command);
|
|
33643
|
+
let globalJson = false;
|
|
33644
|
+
let format;
|
|
33645
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
33646
|
+
const arg = args[index];
|
|
33647
|
+
if (arg === undefined)
|
|
33648
|
+
continue;
|
|
33649
|
+
if (arg === "--")
|
|
33650
|
+
break;
|
|
33651
|
+
if (arg === "--json" || arg === "-j") {
|
|
33652
|
+
globalJson = true;
|
|
33653
|
+
} else if (arg.startsWith("--format=")) {
|
|
33654
|
+
format = arg.slice("--format=".length);
|
|
33655
|
+
} else if (arg === "--format") {
|
|
33656
|
+
format = args[index + 1];
|
|
33657
|
+
index += 1;
|
|
33658
|
+
}
|
|
33659
|
+
}
|
|
33660
|
+
return format === undefined ? globalJson : format === "json";
|
|
33661
|
+
}
|
|
33662
|
+
function optionalString(value) {
|
|
33663
|
+
if (typeof value !== "string")
|
|
33664
|
+
return null;
|
|
33665
|
+
const trimmed = value.trim();
|
|
33666
|
+
return trimmed || null;
|
|
33667
|
+
}
|
|
33668
|
+
function requestedErrorFormat(options, globalOpts, configFormat) {
|
|
33669
|
+
if (TODOS_AI_FORMATS.includes(options.format)) {
|
|
33670
|
+
return options.format;
|
|
33671
|
+
}
|
|
33672
|
+
if (globalOpts["json"] === true)
|
|
33673
|
+
return "json";
|
|
33674
|
+
const envFormat = process.env["TODOS_AI_FORMAT"];
|
|
33675
|
+
if (TODOS_AI_FORMATS.includes(envFormat))
|
|
33676
|
+
return envFormat;
|
|
33677
|
+
if (TODOS_AI_FORMATS.includes(configFormat)) {
|
|
33678
|
+
return configFormat;
|
|
33679
|
+
}
|
|
33680
|
+
return TODOS_AI_DEFAULTS.format;
|
|
33681
|
+
}
|
|
33682
|
+
function loadTodosAiStoredConfig() {
|
|
33683
|
+
let ai;
|
|
33684
|
+
try {
|
|
33685
|
+
ai = getTodosAiConfig();
|
|
33686
|
+
} catch (cause) {
|
|
33687
|
+
throw new TodosAiContractError("invalid_configuration", "Todos AI configuration could not be read", TODOS_AI_EXIT_CODES.usage, { cause });
|
|
33688
|
+
}
|
|
33689
|
+
if (ai !== undefined && (ai === null || typeof ai !== "object" || Array.isArray(ai))) {
|
|
33690
|
+
throw new TodosAiContractError("invalid_configuration", "Todos AI configuration must be an object");
|
|
33691
|
+
}
|
|
33692
|
+
if (ai === undefined)
|
|
33693
|
+
return;
|
|
33694
|
+
const record = ai;
|
|
33695
|
+
for (const field of [
|
|
33696
|
+
"provider",
|
|
33697
|
+
"model",
|
|
33698
|
+
"profile",
|
|
33699
|
+
"format",
|
|
33700
|
+
"write_mode",
|
|
33701
|
+
"approval_mode"
|
|
33702
|
+
]) {
|
|
33703
|
+
if (record[field] !== undefined && typeof record[field] !== "string") {
|
|
33704
|
+
throw new TodosAiContractError("invalid_configuration", `Todos AI configuration field ${field} must be a string`);
|
|
33705
|
+
}
|
|
33706
|
+
}
|
|
33707
|
+
for (const field of ["max_steps", "timeout_ms"]) {
|
|
33708
|
+
if (record[field] !== undefined && typeof record[field] !== "number") {
|
|
33709
|
+
throw new TodosAiContractError("invalid_configuration", `Todos AI configuration field ${field} must be a number`);
|
|
33710
|
+
}
|
|
33711
|
+
}
|
|
33712
|
+
return record;
|
|
33713
|
+
}
|
|
33714
|
+
async function readBoundedStdin() {
|
|
33715
|
+
const chunks = [];
|
|
33716
|
+
let bytes = 0;
|
|
33717
|
+
for await (const chunk of process.stdin) {
|
|
33718
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
33719
|
+
bytes += buffer.byteLength;
|
|
33720
|
+
if (bytes > TODOS_AI_LIMITS.max_prompt_bytes) {
|
|
33721
|
+
throw new TodosAiContractError("invalid_input", `stdin prompt exceeds ${TODOS_AI_LIMITS.max_prompt_bytes} bytes`);
|
|
33722
|
+
}
|
|
33723
|
+
chunks.push(buffer);
|
|
33724
|
+
}
|
|
33725
|
+
return Buffer.concat(chunks, bytes).toString("utf8");
|
|
33726
|
+
}
|
|
33727
|
+
function normalizeResumeRunId(value) {
|
|
33728
|
+
const runId = optionalString(value);
|
|
33729
|
+
if (!runId)
|
|
33730
|
+
return null;
|
|
33731
|
+
if (Buffer.byteLength(runId, "utf8") > TODOS_AI_LIMITS.max_resume_run_id_bytes) {
|
|
33732
|
+
throw new TodosAiContractError("invalid_input", `--resume may not exceed ${TODOS_AI_LIMITS.max_resume_run_id_bytes} bytes`);
|
|
33733
|
+
}
|
|
33734
|
+
return runId;
|
|
33735
|
+
}
|
|
33736
|
+
function resultJson(result) {
|
|
33737
|
+
const serialized = JSON.stringify(result);
|
|
33738
|
+
if (Buffer.byteLength(serialized, "utf8") > TODOS_AI_LIMITS.max_result_bytes) {
|
|
33739
|
+
throw new TodosAiContractError("runtime_invalid_result", `optional AI runtime result exceeds ${TODOS_AI_LIMITS.max_result_bytes} bytes`, TODOS_AI_EXIT_CODES.failed);
|
|
33740
|
+
}
|
|
33741
|
+
return serialized;
|
|
33742
|
+
}
|
|
33743
|
+
function renderTextResult(result) {
|
|
33744
|
+
switch (result.status) {
|
|
33745
|
+
case "answered":
|
|
33746
|
+
case "completed":
|
|
33747
|
+
if (result.answer) {
|
|
33748
|
+
console.log(result.answer);
|
|
33749
|
+
} else if (result.data !== null) {
|
|
33750
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
33751
|
+
} else {
|
|
33752
|
+
console.log(`AI run ${result.run_id} completed.`);
|
|
33753
|
+
}
|
|
33754
|
+
return;
|
|
33755
|
+
case "needs_input":
|
|
33756
|
+
console.log(result.pending_input?.prompt ?? "The AI runtime needs additional input.");
|
|
33757
|
+
return;
|
|
33758
|
+
case "needs_approval":
|
|
33759
|
+
console.log(result.pending_approval?.summary ?? "The AI runtime needs approval.");
|
|
33760
|
+
if (result.pending_approval?.id)
|
|
33761
|
+
console.log(`Approval: ${result.pending_approval.id}`);
|
|
33762
|
+
return;
|
|
33763
|
+
case "failed":
|
|
33764
|
+
console.error(result.error?.message ?? "The AI runtime failed.");
|
|
33765
|
+
}
|
|
33766
|
+
}
|
|
33767
|
+
function renderTerminalResult(result, format) {
|
|
33768
|
+
const serialized = resultJson(result);
|
|
33769
|
+
if (format === "json") {
|
|
33770
|
+
console.log(serialized);
|
|
33771
|
+
return;
|
|
33772
|
+
}
|
|
33773
|
+
if (format === "stream-json") {
|
|
33774
|
+
const record = {
|
|
33775
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
33776
|
+
kind: "result",
|
|
33777
|
+
result
|
|
33778
|
+
};
|
|
33779
|
+
console.log(JSON.stringify(record));
|
|
33780
|
+
return;
|
|
33781
|
+
}
|
|
33782
|
+
renderTextResult(result);
|
|
33783
|
+
}
|
|
33784
|
+
function errorResult(runId, error) {
|
|
33785
|
+
if (error instanceof TodosAiContractError) {
|
|
33786
|
+
return createTodosAiFailureResult(runId, error.code, error.message);
|
|
33787
|
+
}
|
|
33788
|
+
return createTodosAiFailureResult(runId, "internal_error", "optional AI runtime failed");
|
|
33789
|
+
}
|
|
33790
|
+
async function withLoadDeadline(operation, controller, timeoutMs) {
|
|
33791
|
+
return new Promise((resolve14, reject) => {
|
|
33792
|
+
let settled = false;
|
|
33793
|
+
const settle = (callback) => {
|
|
33794
|
+
if (settled)
|
|
33795
|
+
return;
|
|
33796
|
+
settled = true;
|
|
33797
|
+
clearTimeout(timer);
|
|
33798
|
+
callback();
|
|
33799
|
+
};
|
|
33800
|
+
const timer = setTimeout(() => {
|
|
33801
|
+
const error = new TodosAiContractError("timeout", `AI run timed out after ${timeoutMs}ms`, TODOS_AI_EXIT_CODES.timeout);
|
|
33802
|
+
controller.abort(error);
|
|
33803
|
+
settle(() => reject(error));
|
|
33804
|
+
}, timeoutMs);
|
|
33805
|
+
Promise.resolve().then(operation).then((value) => settle(() => resolve14(value)), (error) => settle(() => reject(error)));
|
|
33806
|
+
});
|
|
33807
|
+
}
|
|
33808
|
+
async function withInterrupt(operation, controller) {
|
|
33809
|
+
return new Promise((resolve14, reject) => {
|
|
33810
|
+
let settled = false;
|
|
33811
|
+
const settle = (callback) => {
|
|
33812
|
+
if (settled)
|
|
33813
|
+
return;
|
|
33814
|
+
settled = true;
|
|
33815
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
33816
|
+
callback();
|
|
33817
|
+
};
|
|
33818
|
+
const onInterrupt = () => {
|
|
33819
|
+
const error = new TodosAiContractError("interrupted", "AI run interrupted", TODOS_AI_EXIT_CODES.interrupted);
|
|
33820
|
+
controller.abort(error);
|
|
33821
|
+
settle(() => reject(error));
|
|
33822
|
+
};
|
|
33823
|
+
process.once("SIGINT", onInterrupt);
|
|
33824
|
+
Promise.resolve().then(operation).then((value) => settle(() => resolve14(value)), (error) => settle(() => reject(error)));
|
|
33825
|
+
});
|
|
33826
|
+
}
|
|
33827
|
+
function throwIfAborted(signal) {
|
|
33828
|
+
if (!signal.aborted)
|
|
33829
|
+
return;
|
|
33830
|
+
if (signal.reason instanceof Error)
|
|
33831
|
+
throw signal.reason;
|
|
33832
|
+
throw new TodosAiContractError("interrupted", "AI run interrupted", TODOS_AI_EXIT_CODES.interrupted);
|
|
33833
|
+
}
|
|
33834
|
+
function createRuntimeEventSink(format, controller, expectedRunId) {
|
|
33835
|
+
let eventCount = 0;
|
|
33836
|
+
let streamBytes = 0;
|
|
33837
|
+
let lastSequence = -1;
|
|
33838
|
+
let runId = expectedRunId;
|
|
33839
|
+
let failure = null;
|
|
33840
|
+
let closed = false;
|
|
33841
|
+
let rejectFailure;
|
|
33842
|
+
const failurePromise = new Promise((_resolve, reject) => {
|
|
33843
|
+
rejectFailure = reject;
|
|
33844
|
+
});
|
|
33845
|
+
failurePromise.catch(() => {
|
|
33846
|
+
return;
|
|
33847
|
+
});
|
|
33848
|
+
const fail = (message) => {
|
|
33849
|
+
if (failure)
|
|
33850
|
+
return failure;
|
|
33851
|
+
const error = new TodosAiContractError("runtime_invalid_result", message, TODOS_AI_EXIT_CODES.failed);
|
|
33852
|
+
failure = error;
|
|
33853
|
+
controller.abort(error);
|
|
33854
|
+
rejectFailure(error);
|
|
33855
|
+
return error;
|
|
33856
|
+
};
|
|
33857
|
+
return {
|
|
33858
|
+
emit(event) {
|
|
33859
|
+
if (closed)
|
|
33860
|
+
return;
|
|
33861
|
+
if (failure)
|
|
33862
|
+
return;
|
|
33863
|
+
if (!isTodosAiRuntimeEvent(event)) {
|
|
33864
|
+
fail("optional AI runtime emitted an invalid event");
|
|
33865
|
+
return;
|
|
33866
|
+
}
|
|
33867
|
+
if (runId !== null && event.run_id !== runId) {
|
|
33868
|
+
fail("optional AI runtime changed run_id while streaming");
|
|
33869
|
+
return;
|
|
33870
|
+
}
|
|
33871
|
+
if (event.sequence <= lastSequence) {
|
|
33872
|
+
fail("optional AI runtime event sequence must increase");
|
|
33873
|
+
return;
|
|
33874
|
+
}
|
|
33875
|
+
if (eventCount >= TODOS_AI_LIMITS.max_stream_events) {
|
|
33876
|
+
fail(`optional AI runtime exceeded ${TODOS_AI_LIMITS.max_stream_events} events`);
|
|
33877
|
+
return;
|
|
33878
|
+
}
|
|
33879
|
+
const record = {
|
|
33880
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
33881
|
+
kind: "event",
|
|
33882
|
+
event
|
|
33883
|
+
};
|
|
33884
|
+
let serialized;
|
|
33885
|
+
try {
|
|
33886
|
+
serialized = JSON.stringify(record);
|
|
33887
|
+
} catch {
|
|
33888
|
+
fail("optional AI runtime emitted an event that could not be serialized");
|
|
33889
|
+
return;
|
|
33890
|
+
}
|
|
33891
|
+
const recordBytes = Buffer.byteLength(serialized, "utf8");
|
|
33892
|
+
if (recordBytes > TODOS_AI_LIMITS.max_stream_record_bytes) {
|
|
33893
|
+
fail(`optional AI runtime event exceeds ${TODOS_AI_LIMITS.max_stream_record_bytes} bytes`);
|
|
33894
|
+
return;
|
|
33895
|
+
}
|
|
33896
|
+
if (streamBytes + recordBytes + 1 > TODOS_AI_LIMITS.max_stream_bytes) {
|
|
33897
|
+
fail(`optional AI runtime stream exceeds ${TODOS_AI_LIMITS.max_stream_bytes} bytes`);
|
|
33898
|
+
return;
|
|
33899
|
+
}
|
|
33900
|
+
runId = event.run_id;
|
|
33901
|
+
lastSequence = event.sequence;
|
|
33902
|
+
eventCount += 1;
|
|
33903
|
+
streamBytes += recordBytes + 1;
|
|
33904
|
+
if (format === "stream-json")
|
|
33905
|
+
process.stdout.write(`${serialized}
|
|
33906
|
+
`);
|
|
33907
|
+
},
|
|
33908
|
+
assertResult(result, maxSteps) {
|
|
33909
|
+
if (failure)
|
|
33910
|
+
throw failure;
|
|
33911
|
+
if (runId !== null && result.run_id !== runId) {
|
|
33912
|
+
throw fail("optional AI runtime result run_id does not match its streamed events");
|
|
33913
|
+
}
|
|
33914
|
+
if (result.steps > maxSteps) {
|
|
33915
|
+
throw fail(`optional AI runtime exceeded the max step limit of ${maxSteps}`);
|
|
33916
|
+
}
|
|
33917
|
+
},
|
|
33918
|
+
raceWithFailure(operation) {
|
|
33919
|
+
return Promise.race([failurePromise, operation]);
|
|
33920
|
+
},
|
|
33921
|
+
failureRunId(fallback) {
|
|
33922
|
+
return runId ?? fallback;
|
|
33923
|
+
},
|
|
33924
|
+
close() {
|
|
33925
|
+
closed = true;
|
|
33926
|
+
}
|
|
33927
|
+
};
|
|
33928
|
+
}
|
|
33929
|
+
async function runTodosAi(promptParts, options, program2) {
|
|
33930
|
+
const globalOpts = globalOptions(program2);
|
|
33931
|
+
let format = requestedErrorFormat(options, globalOpts);
|
|
33932
|
+
const hostRunId = randomUUID4();
|
|
33933
|
+
let eventSink = null;
|
|
33934
|
+
try {
|
|
33935
|
+
const config = loadTodosAiStoredConfig();
|
|
33936
|
+
format = requestedErrorFormat(options, globalOpts, config?.format);
|
|
33937
|
+
const interactive = options.nonInteractive !== true && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
33938
|
+
const resolved = resolveTodosAiCommandOptions({
|
|
33939
|
+
cli: {
|
|
33940
|
+
provider: options.provider,
|
|
33941
|
+
model: options.model,
|
|
33942
|
+
profile: options.profile,
|
|
33943
|
+
format: options.format ?? (globalOpts["json"] === true ? "json" : undefined),
|
|
33944
|
+
maxSteps: options.maxSteps,
|
|
33945
|
+
timeoutMs: options.timeoutMs,
|
|
33946
|
+
writeMode: options.writeMode,
|
|
33947
|
+
approvalMode: options.approvalMode,
|
|
33948
|
+
approvalRefs: options.approval,
|
|
33949
|
+
dryRun: options.dryRun
|
|
33950
|
+
},
|
|
33951
|
+
config,
|
|
33952
|
+
env: process.env,
|
|
33953
|
+
interactive
|
|
33954
|
+
});
|
|
33955
|
+
format = resolved.format;
|
|
33956
|
+
const resumeRunId = normalizeResumeRunId(options.resume);
|
|
33957
|
+
let rawPrompt = promptParts.join(" ");
|
|
33958
|
+
if (!rawPrompt.trim() && process.stdin.isTTY !== true) {
|
|
33959
|
+
rawPrompt = await readBoundedStdin();
|
|
33960
|
+
}
|
|
33961
|
+
const prompt = normalizeTodosAiPrompt(rawPrompt);
|
|
33962
|
+
if (!prompt && !resumeRunId) {
|
|
33963
|
+
if (!interactive) {
|
|
33964
|
+
throw new TodosAiContractError("invalid_input", "Provide a prompt as arguments or non-TTY stdin.");
|
|
33965
|
+
}
|
|
33966
|
+
const result2 = createTodosAiNeedsInputResult(hostRunId, "Provide a prompt as arguments or non-TTY stdin.");
|
|
33967
|
+
renderTerminalResult(result2, format);
|
|
33968
|
+
process.exitCode = todosAiExitCodeForResult(result2);
|
|
33969
|
+
return;
|
|
33970
|
+
}
|
|
33971
|
+
if (options.inputJson !== undefined && options.input !== undefined) {
|
|
33972
|
+
throw new TodosAiContractError("invalid_input", "Pass either --input-json or --input, not both.");
|
|
33973
|
+
}
|
|
33974
|
+
const inputJson = options.inputJson ?? options.input;
|
|
33975
|
+
const request = {
|
|
33976
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
33977
|
+
prompt,
|
|
33978
|
+
input: inputJson === undefined ? null : parseTodosAiJson(inputJson, "input"),
|
|
33979
|
+
variables: parseTodosAiVariables(options.var ?? []),
|
|
33980
|
+
output_schema: options.outputSchema === undefined ? null : parseTodosAiOutputSchema(options.outputSchema),
|
|
33981
|
+
provider: resolved.provider,
|
|
33982
|
+
model: resolved.model,
|
|
33983
|
+
profile: resolved.profile,
|
|
33984
|
+
format: resolved.format,
|
|
33985
|
+
interactive: resolved.interactive,
|
|
33986
|
+
context: {
|
|
33987
|
+
project: optionalString(globalOpts["project"]),
|
|
33988
|
+
agent: optionalString(globalOpts["agent"]),
|
|
33989
|
+
session: optionalString(globalOpts["session"])
|
|
33990
|
+
},
|
|
33991
|
+
authority: {
|
|
33992
|
+
write_mode: resolved.write_mode,
|
|
33993
|
+
approval_mode: resolved.approval_mode,
|
|
33994
|
+
approval_refs: resolved.approval_refs,
|
|
33995
|
+
dry_run: resolved.dry_run
|
|
33996
|
+
},
|
|
33997
|
+
limits: {
|
|
33998
|
+
max_steps: resolved.max_steps,
|
|
33999
|
+
timeout_ms: resolved.timeout_ms
|
|
34000
|
+
},
|
|
34001
|
+
resume_run_id: resumeRunId
|
|
34002
|
+
};
|
|
34003
|
+
const controller = new AbortController;
|
|
34004
|
+
const sink = createRuntimeEventSink(format, controller, resumeRunId);
|
|
34005
|
+
eventSink = sink;
|
|
34006
|
+
const runtimeResult = await withInterrupt(async () => {
|
|
34007
|
+
const runtime = await withLoadDeadline(() => loadTodosAiRuntime({
|
|
34008
|
+
package_name: "@hasna/todos",
|
|
34009
|
+
package_version: getPackageVersion(),
|
|
34010
|
+
protocol_version: TODOS_AI_RUNTIME_PROTOCOL_VERSION,
|
|
34011
|
+
tool_source: createTodosAiToolSource({
|
|
34012
|
+
env: process.env,
|
|
34013
|
+
workspacePath: process.cwd()
|
|
34014
|
+
})
|
|
34015
|
+
}), controller, resolved.timeout_ms);
|
|
34016
|
+
throwIfAborted(controller.signal);
|
|
34017
|
+
const runtimeOperation = Promise.resolve().then(() => {
|
|
34018
|
+
throwIfAborted(controller.signal);
|
|
34019
|
+
return runtime.run(request, {
|
|
34020
|
+
signal: controller.signal,
|
|
34021
|
+
emit: (event) => sink.emit(event)
|
|
34022
|
+
});
|
|
34023
|
+
});
|
|
34024
|
+
return sink.raceWithFailure(runtimeOperation);
|
|
34025
|
+
}, controller);
|
|
34026
|
+
const result = assertTodosAiRunResult(runtimeResult);
|
|
34027
|
+
eventSink.assertResult(result, resolved.max_steps);
|
|
34028
|
+
eventSink.close();
|
|
34029
|
+
renderTerminalResult(result, format);
|
|
34030
|
+
process.exitCode = todosAiExitCodeForResult(result);
|
|
34031
|
+
} catch (error) {
|
|
34032
|
+
eventSink?.close();
|
|
34033
|
+
const result = errorResult(eventSink?.failureRunId(hostRunId) ?? hostRunId, error);
|
|
34034
|
+
renderTerminalResult(result, format);
|
|
34035
|
+
process.exitCode = error instanceof TodosAiContractError ? error.exitCode : TODOS_AI_EXIT_CODES.failed;
|
|
34036
|
+
}
|
|
34037
|
+
}
|
|
34038
|
+
function registerAiCommands(program2) {
|
|
34039
|
+
const command = program2.command("ai [prompt...]").description("Run the optional provider-neutral Todos AI runtime").option("--format <format>", "Output format: text, json, or stream-json").option("--input-json <json>", "Structured JSON input supplied to the runtime").option("--input <json>", "Compatibility alias for --input-json").option("--var <key=value>", "Non-secret runtime variable; repeatable", appendOption, []).option("--output-schema <json>", "JSON Schema object for structured output").option("--provider <name>", "Runtime provider override").option("--model <name>", "Runtime model override").option("--profile <name>", "Runtime profile override").option("--write-mode <mode>", "Authority: read-only, plan, or execute").option("--approval-mode <mode>", "Approval handling: deny, required, prompt, or existing").option("--approval <reference>", "Existing approval reference; repeatable", appendOption, []).option("--dry-run", "Force plan-only authority without mutation").option("--max-steps <n>", `Maximum runtime steps (${TODOS_AI_LIMITS.min_steps}-${TODOS_AI_LIMITS.max_steps})`).option("--timeout-ms <ms>", `Timeout in milliseconds (${TODOS_AI_LIMITS.min_timeout_ms}-${TODOS_AI_LIMITS.max_timeout_ms})`).option("--resume <run-id>", "Resume a runtime run, optionally with a new prompt").option("--non-interactive", "Disable all runtime prompting");
|
|
34040
|
+
const defaultOutputError = command.configureOutput().outputError;
|
|
34041
|
+
command.configureOutput({
|
|
34042
|
+
outputError: (message, write) => {
|
|
34043
|
+
if (commanderJsonRequested(command))
|
|
34044
|
+
return;
|
|
34045
|
+
if (defaultOutputError) {
|
|
34046
|
+
defaultOutputError(message, write);
|
|
34047
|
+
} else {
|
|
34048
|
+
write(message);
|
|
34049
|
+
}
|
|
34050
|
+
}
|
|
34051
|
+
});
|
|
34052
|
+
command.exitOverride((error) => {
|
|
34053
|
+
if (error.exitCode === 0)
|
|
34054
|
+
process.exit(0);
|
|
34055
|
+
if (commanderJsonRequested(command)) {
|
|
34056
|
+
const result = createTodosAiFailureResult(randomUUID4(), "invalid_input", error.message);
|
|
34057
|
+
writeSync2(process.stdout.fd, `${resultJson(result)}
|
|
34058
|
+
`);
|
|
34059
|
+
}
|
|
34060
|
+
process.exit(TODOS_AI_EXIT_CODES.usage);
|
|
34061
|
+
});
|
|
34062
|
+
command.action(async (prompt, options) => {
|
|
34063
|
+
await runTodosAi(prompt ?? [], options, program2);
|
|
34064
|
+
});
|
|
34065
|
+
}
|
|
34066
|
+
var init_ai_commands = __esm(() => {
|
|
34067
|
+
init_ai();
|
|
34068
|
+
init_ai_tools();
|
|
34069
|
+
init_config();
|
|
34070
|
+
init_package_version();
|
|
34071
|
+
});
|
|
34072
|
+
|
|
31486
34073
|
// src/server/port.ts
|
|
31487
34074
|
var exports_port = {};
|
|
31488
34075
|
__export(exports_port, {
|
|
@@ -31522,7 +34109,7 @@ async function findFreePort(start) {
|
|
|
31522
34109
|
var DEFAULT_PORT = 19427;
|
|
31523
34110
|
|
|
31524
34111
|
// src/lib/db-backup.ts
|
|
31525
|
-
import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as
|
|
34112
|
+
import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync, statSync as statSync7, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
31526
34113
|
import { dirname as dirname7, join as join14, resolve as resolve14 } from "path";
|
|
31527
34114
|
import { Database as Database2 } from "bun:sqlite";
|
|
31528
34115
|
function resolveDbPath(dbPath) {
|
|
@@ -32250,7 +34837,7 @@ function addGroupTools(toolNames, groupName) {
|
|
|
32250
34837
|
toolNames.add(tool);
|
|
32251
34838
|
return true;
|
|
32252
34839
|
}
|
|
32253
|
-
function
|
|
34840
|
+
function shouldRegisterToolForProfile2(name, profileValue = process.env["TODOS_PROFILE"], groupValue = process.env["TODOS_TOOL_GROUPS"]) {
|
|
32254
34841
|
const profileTokens = splitTokens(profileValue || "minimal");
|
|
32255
34842
|
const groupTokens = splitTokens(groupValue);
|
|
32256
34843
|
if (profileTokens.includes("full") || profileTokens.includes("all"))
|
|
@@ -32295,7 +34882,7 @@ function truncateText(value, maxChars = 240) {
|
|
|
32295
34882
|
return value;
|
|
32296
34883
|
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
32297
34884
|
}
|
|
32298
|
-
function
|
|
34885
|
+
function compactTask2(task, maxDescriptionChars = 180) {
|
|
32299
34886
|
const summary = {
|
|
32300
34887
|
id: task.id,
|
|
32301
34888
|
short_id: task.short_id || task.id.slice(0, 8),
|
|
@@ -32838,7 +35425,7 @@ function profilesForTool(toolName) {
|
|
|
32838
35425
|
function getMcpToolNames(options = {}) {
|
|
32839
35426
|
const profile = options.profile ?? "minimal";
|
|
32840
35427
|
const groups = options.groups ?? "";
|
|
32841
|
-
return getAllMcpToolNames().filter((toolName) =>
|
|
35428
|
+
return getAllMcpToolNames().filter((toolName) => shouldRegisterToolForProfile2(toolName, profile, groups));
|
|
32842
35429
|
}
|
|
32843
35430
|
function createMcpManifest(options = {}) {
|
|
32844
35431
|
const version = options.version ?? getPackageVersion(import.meta.url);
|
|
@@ -32887,8 +35474,8 @@ __export(exports_local_extensions, {
|
|
|
32887
35474
|
getLocalExtension: () => getLocalExtension,
|
|
32888
35475
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
32889
35476
|
});
|
|
32890
|
-
import { createHash as
|
|
32891
|
-
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as
|
|
35477
|
+
import { createHash as createHash10, createVerify } from "crypto";
|
|
35478
|
+
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
32892
35479
|
import { basename as basename6, join as join16, resolve as resolve15 } from "path";
|
|
32893
35480
|
function isObject(value) {
|
|
32894
35481
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -32970,10 +35557,10 @@ function normalizeManifest(input) {
|
|
|
32970
35557
|
};
|
|
32971
35558
|
}
|
|
32972
35559
|
function parseJson(path) {
|
|
32973
|
-
return JSON.parse(
|
|
35560
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
32974
35561
|
}
|
|
32975
35562
|
function sha2564(bytes) {
|
|
32976
|
-
return `sha256:${
|
|
35563
|
+
return `sha256:${createHash10("sha256").update(bytes).digest("hex")}`;
|
|
32977
35564
|
}
|
|
32978
35565
|
function compareVersions(a, b) {
|
|
32979
35566
|
const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -33154,7 +35741,7 @@ function inspectExtensionSource(source2) {
|
|
|
33154
35741
|
const manifestPath = stat.isDirectory() ? [join16(resolved, "todos.extension.json"), join16(resolved, "extension.json")].find(existsSync17) : resolved;
|
|
33155
35742
|
if (!manifestPath)
|
|
33156
35743
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
33157
|
-
const raw =
|
|
35744
|
+
const raw = readFileSync12(manifestPath);
|
|
33158
35745
|
const parsed = parseJson(manifestPath);
|
|
33159
35746
|
const bundle = isObject(parsed) && isObject(parsed["manifest"]);
|
|
33160
35747
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -33793,310 +36380,6 @@ var init_policy_packs = __esm(() => {
|
|
|
33793
36380
|
init_config();
|
|
33794
36381
|
});
|
|
33795
36382
|
|
|
33796
|
-
// src/db/checkpoints.ts
|
|
33797
|
-
function upsertCheckpoint(task_id, step, updates, db) {
|
|
33798
|
-
const d = db || getDatabase();
|
|
33799
|
-
const timestamp2 = now();
|
|
33800
|
-
const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
|
|
33801
|
-
const id = existing?.id ?? uuid();
|
|
33802
|
-
const agentId = updates.agent_id ?? null;
|
|
33803
|
-
const status = updates.status ?? "pending";
|
|
33804
|
-
const data = updates.data ? JSON.stringify(updates.data) : JSON.stringify({});
|
|
33805
|
-
const error = updates.error ?? null;
|
|
33806
|
-
const attempt = updates.attempt ?? 1;
|
|
33807
|
-
const maxAttempts = updates.max_attempts ?? 1;
|
|
33808
|
-
const startedAt = updates.started_at ?? null;
|
|
33809
|
-
const completedAt = updates.completed_at ?? null;
|
|
33810
|
-
d.run(`INSERT INTO task_checkpoints (id, task_id, agent_id, step, status, data, error, attempt, max_attempts, started_at, completed_at, updated_at)
|
|
33811
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
33812
|
-
ON CONFLICT(id) DO UPDATE SET status=?, data=?, error=?, attempt=?, max_attempts=?, started_at=COALESCE(started_at,?), completed_at=?, updated_at=?`, [
|
|
33813
|
-
id,
|
|
33814
|
-
task_id,
|
|
33815
|
-
agentId,
|
|
33816
|
-
step,
|
|
33817
|
-
status,
|
|
33818
|
-
data,
|
|
33819
|
-
error,
|
|
33820
|
-
attempt,
|
|
33821
|
-
maxAttempts,
|
|
33822
|
-
startedAt,
|
|
33823
|
-
completedAt,
|
|
33824
|
-
timestamp2,
|
|
33825
|
-
status,
|
|
33826
|
-
data,
|
|
33827
|
-
error,
|
|
33828
|
-
attempt,
|
|
33829
|
-
maxAttempts,
|
|
33830
|
-
startedAt,
|
|
33831
|
-
completedAt,
|
|
33832
|
-
timestamp2
|
|
33833
|
-
]);
|
|
33834
|
-
return rowToCheckpoint(d.query("SELECT * FROM task_checkpoints WHERE id = ?").get(id));
|
|
33835
|
-
}
|
|
33836
|
-
function getCheckpoint(taskId, step, db) {
|
|
33837
|
-
const d = db || getDatabase();
|
|
33838
|
-
const row = d.query("SELECT * FROM task_checkpoints WHERE task_id = ? AND step = ?").get(taskId, step);
|
|
33839
|
-
return row ? rowToCheckpoint(row) : null;
|
|
33840
|
-
}
|
|
33841
|
-
function getTaskCheckpoints(taskId, db) {
|
|
33842
|
-
const d = db || getDatabase();
|
|
33843
|
-
return d.query("SELECT * FROM task_checkpoints WHERE task_id = ? ORDER BY created_at ASC").all(taskId).map(rowToCheckpoint);
|
|
33844
|
-
}
|
|
33845
|
-
function rowToCheckpoint(row) {
|
|
33846
|
-
if (!row)
|
|
33847
|
-
return null;
|
|
33848
|
-
return {
|
|
33849
|
-
...row,
|
|
33850
|
-
data: JSON.parse(row.data || "{}"),
|
|
33851
|
-
status: row.status,
|
|
33852
|
-
agent_id: row.agent_id || null,
|
|
33853
|
-
error: row.error || null,
|
|
33854
|
-
started_at: row.started_at || null,
|
|
33855
|
-
completed_at: row.completed_at || null
|
|
33856
|
-
};
|
|
33857
|
-
}
|
|
33858
|
-
var init_checkpoints = __esm(() => {
|
|
33859
|
-
init_database();
|
|
33860
|
-
});
|
|
33861
|
-
|
|
33862
|
-
// src/lib/approval-gates.ts
|
|
33863
|
-
var exports_approval_gates = {};
|
|
33864
|
-
__export(exports_approval_gates, {
|
|
33865
|
-
requestApprovalGate: () => requestApprovalGate,
|
|
33866
|
-
rejectApprovalGate: () => rejectApprovalGate,
|
|
33867
|
-
listApprovalGates: () => listApprovalGates,
|
|
33868
|
-
expireApprovalGate: () => expireApprovalGate,
|
|
33869
|
-
checkApprovalGate: () => checkApprovalGate,
|
|
33870
|
-
assertApprovalGate: () => assertApprovalGate,
|
|
33871
|
-
approveApprovalGate: () => approveApprovalGate
|
|
33872
|
-
});
|
|
33873
|
-
function stepForGate(gate) {
|
|
33874
|
-
const trimmed = gate.trim();
|
|
33875
|
-
if (!trimmed)
|
|
33876
|
-
throw new Error("Approval gate name is required");
|
|
33877
|
-
return `approval:${trimmed}`;
|
|
33878
|
-
}
|
|
33879
|
-
function approvalStatusToCheckpointStatus(status) {
|
|
33880
|
-
if (status === "approved")
|
|
33881
|
-
return "completed";
|
|
33882
|
-
if (status === "pending")
|
|
33883
|
-
return "pending";
|
|
33884
|
-
return "failed";
|
|
33885
|
-
}
|
|
33886
|
-
function checkpointStatusToApprovalStatus(checkpoint) {
|
|
33887
|
-
const value = checkpoint.data["approval_status"];
|
|
33888
|
-
if (value === "approved" || value === "rejected" || value === "expired" || value === "pending")
|
|
33889
|
-
return value;
|
|
33890
|
-
if (checkpoint.status === "completed")
|
|
33891
|
-
return "approved";
|
|
33892
|
-
if (checkpoint.status === "failed")
|
|
33893
|
-
return "rejected";
|
|
33894
|
-
return "pending";
|
|
33895
|
-
}
|
|
33896
|
-
function ensureTask(taskId, db) {
|
|
33897
|
-
if (!getTask(taskId, db))
|
|
33898
|
-
throw new TaskNotFoundError(taskId);
|
|
33899
|
-
}
|
|
33900
|
-
function isExpired(expiresAt, at = new Date) {
|
|
33901
|
-
return Boolean(expiresAt && new Date(expiresAt).getTime() <= at.getTime());
|
|
33902
|
-
}
|
|
33903
|
-
function gateFromCheckpoint(checkpoint) {
|
|
33904
|
-
const status = checkpointStatusToApprovalStatus(checkpoint);
|
|
33905
|
-
return {
|
|
33906
|
-
id: checkpoint.id,
|
|
33907
|
-
task_id: checkpoint.task_id,
|
|
33908
|
-
gate: String(checkpoint.data["approval_gate_name"] || checkpoint.step.replace(/^approval:/, "")),
|
|
33909
|
-
status,
|
|
33910
|
-
reviewer: typeof checkpoint.data["reviewer"] === "string" ? checkpoint.data["reviewer"] : null,
|
|
33911
|
-
requester: typeof checkpoint.data["requester"] === "string" ? checkpoint.data["requester"] : null,
|
|
33912
|
-
reason: typeof checkpoint.data["reason"] === "string" ? checkpoint.data["reason"] : null,
|
|
33913
|
-
note: typeof checkpoint.data["note"] === "string" ? checkpoint.data["note"] : null,
|
|
33914
|
-
plan_id: typeof checkpoint.data["plan_id"] === "string" ? checkpoint.data["plan_id"] : null,
|
|
33915
|
-
run_id: typeof checkpoint.data["run_id"] === "string" ? checkpoint.data["run_id"] : null,
|
|
33916
|
-
expires_at: typeof checkpoint.data["expires_at"] === "string" ? checkpoint.data["expires_at"] : null,
|
|
33917
|
-
decided_by: typeof checkpoint.data["decided_by"] === "string" ? checkpoint.data["decided_by"] : null,
|
|
33918
|
-
decided_at: typeof checkpoint.data["decided_at"] === "string" ? checkpoint.data["decided_at"] : null,
|
|
33919
|
-
created_at: checkpoint.created_at,
|
|
33920
|
-
updated_at: checkpoint.updated_at,
|
|
33921
|
-
checkpoint
|
|
33922
|
-
};
|
|
33923
|
-
}
|
|
33924
|
-
function logApprovalEvent(taskId, action, gate, agentId, db) {
|
|
33925
|
-
const payload = JSON.stringify({
|
|
33926
|
-
gate: gate.gate,
|
|
33927
|
-
status: gate.status,
|
|
33928
|
-
reviewer: gate.reviewer,
|
|
33929
|
-
requester: gate.requester,
|
|
33930
|
-
plan_id: gate.plan_id,
|
|
33931
|
-
run_id: gate.run_id,
|
|
33932
|
-
expires_at: gate.expires_at,
|
|
33933
|
-
decided_by: gate.decided_by,
|
|
33934
|
-
decided_at: gate.decided_at
|
|
33935
|
-
});
|
|
33936
|
-
logTaskChange(taskId, `approval_gate.${action}`, "approval_gate", null, payload, agentId, db);
|
|
33937
|
-
if (gate.run_id && getTaskRun(gate.run_id, db)) {
|
|
33938
|
-
addTaskRunEvent({
|
|
33939
|
-
run_id: gate.run_id,
|
|
33940
|
-
event_type: "progress",
|
|
33941
|
-
message: `approval gate ${action}: ${gate.gate}`,
|
|
33942
|
-
data: JSON.parse(payload),
|
|
33943
|
-
agent_id: agentId
|
|
33944
|
-
}, db);
|
|
33945
|
-
}
|
|
33946
|
-
if (action === "approved" || action === "rejected" || action === "expired") {
|
|
33947
|
-
emitLocalEventHooksQuiet({
|
|
33948
|
-
type: "approval.decided",
|
|
33949
|
-
payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
|
|
33950
|
-
databasePath: databasePathFromDatabase(db)
|
|
33951
|
-
});
|
|
33952
|
-
}
|
|
33953
|
-
}
|
|
33954
|
-
function writeGate(input, status, decision, db) {
|
|
33955
|
-
const d = db || getDatabase();
|
|
33956
|
-
ensureTask(input.task_id, d);
|
|
33957
|
-
const step = stepForGate(input.gate);
|
|
33958
|
-
const existing = getCheckpoint(input.task_id, step, d);
|
|
33959
|
-
const existingData = existing?.data || {};
|
|
33960
|
-
const timestamp2 = decision?.decided_at || now();
|
|
33961
|
-
const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
|
|
33962
|
-
const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
|
|
33963
|
-
const data = {
|
|
33964
|
-
...existingData,
|
|
33965
|
-
...input.metadata || {},
|
|
33966
|
-
approval_gate: true,
|
|
33967
|
-
approval_gate_name: input.gate.trim(),
|
|
33968
|
-
approval_status: status,
|
|
33969
|
-
requester: input.requester ?? existingData["requester"] ?? null,
|
|
33970
|
-
reviewer: input.reviewer ?? decision?.decided_by ?? existingData["reviewer"] ?? null,
|
|
33971
|
-
reason: decision?.reason ?? input.reason ?? existingData["reason"] ?? null,
|
|
33972
|
-
note: decision?.note ?? existingData["note"] ?? null,
|
|
33973
|
-
plan_id: input.plan_id ?? existingData["plan_id"] ?? null,
|
|
33974
|
-
run_id: runId ?? null,
|
|
33975
|
-
expires_at: input.expires_at ?? existingData["expires_at"] ?? null,
|
|
33976
|
-
decided_by: decision?.decided_by ?? existingData["decided_by"] ?? null,
|
|
33977
|
-
decided_at: decision?.decided_at ?? existingData["decided_at"] ?? null
|
|
33978
|
-
};
|
|
33979
|
-
const checkpoint = upsertCheckpoint(input.task_id, step, {
|
|
33980
|
-
agent_id: decision?.decided_by || input.requester || input.reviewer,
|
|
33981
|
-
status: approvalStatusToCheckpointStatus(status),
|
|
33982
|
-
data,
|
|
33983
|
-
error: status === "rejected" || status === "expired" ? String(data.reason || status) : null,
|
|
33984
|
-
started_at: existing?.started_at || timestamp2,
|
|
33985
|
-
completed_at: status === "pending" ? null : timestamp2
|
|
33986
|
-
}, d);
|
|
33987
|
-
return gateFromCheckpoint(checkpoint);
|
|
33988
|
-
}
|
|
33989
|
-
function currentGate(taskId, gate, db) {
|
|
33990
|
-
const checkpoint = getCheckpoint(taskId, stepForGate(gate), db);
|
|
33991
|
-
return checkpoint ? gateFromCheckpoint(checkpoint) : null;
|
|
33992
|
-
}
|
|
33993
|
-
function requestApprovalGate(input, db) {
|
|
33994
|
-
const d = db || getDatabase();
|
|
33995
|
-
const existing = currentGate(input.task_id, input.gate, d);
|
|
33996
|
-
if (existing && existing.status !== "pending") {
|
|
33997
|
-
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
33998
|
-
}
|
|
33999
|
-
const gate = writeGate(input, "pending", undefined, d);
|
|
34000
|
-
logApprovalEvent(input.task_id, "requested", gate, input.requester || input.reviewer, d);
|
|
34001
|
-
return gate;
|
|
34002
|
-
}
|
|
34003
|
-
function approveApprovalGate(input, db) {
|
|
34004
|
-
const d = db || getDatabase();
|
|
34005
|
-
const existing = currentGate(input.task_id, input.gate, d);
|
|
34006
|
-
if (!existing)
|
|
34007
|
-
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
34008
|
-
if (existing.status !== "pending")
|
|
34009
|
-
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
34010
|
-
if (isExpired(existing.expires_at))
|
|
34011
|
-
throw new Error(`Approval gate ${input.gate} is expired`);
|
|
34012
|
-
const gate = writeGate({
|
|
34013
|
-
task_id: input.task_id,
|
|
34014
|
-
gate: input.gate,
|
|
34015
|
-
requester: existing.requester || undefined,
|
|
34016
|
-
reviewer: input.reviewer || existing.reviewer || undefined,
|
|
34017
|
-
reason: existing.reason || undefined,
|
|
34018
|
-
plan_id: existing.plan_id || undefined,
|
|
34019
|
-
run_id: existing.run_id || undefined,
|
|
34020
|
-
expires_at: existing.expires_at || undefined
|
|
34021
|
-
}, "approved", { decided_by: input.reviewer, decided_at: now(), note: input.note }, d);
|
|
34022
|
-
logApprovalEvent(input.task_id, "approved", gate, input.reviewer, d);
|
|
34023
|
-
return gate;
|
|
34024
|
-
}
|
|
34025
|
-
function rejectApprovalGate(input, db) {
|
|
34026
|
-
const d = db || getDatabase();
|
|
34027
|
-
const existing = currentGate(input.task_id, input.gate, d);
|
|
34028
|
-
if (!existing)
|
|
34029
|
-
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
34030
|
-
if (existing.status !== "pending")
|
|
34031
|
-
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
34032
|
-
const gate = writeGate({
|
|
34033
|
-
task_id: input.task_id,
|
|
34034
|
-
gate: input.gate,
|
|
34035
|
-
requester: existing.requester || undefined,
|
|
34036
|
-
reviewer: input.reviewer || existing.reviewer || undefined,
|
|
34037
|
-
reason: existing.reason || undefined,
|
|
34038
|
-
plan_id: existing.plan_id || undefined,
|
|
34039
|
-
run_id: existing.run_id || undefined,
|
|
34040
|
-
expires_at: existing.expires_at || undefined
|
|
34041
|
-
}, "rejected", { decided_by: input.reviewer, decided_at: now(), note: input.note, reason: input.reason || input.note }, d);
|
|
34042
|
-
logApprovalEvent(input.task_id, "rejected", gate, input.reviewer, d);
|
|
34043
|
-
return gate;
|
|
34044
|
-
}
|
|
34045
|
-
function expireApprovalGate(input, db) {
|
|
34046
|
-
const d = db || getDatabase();
|
|
34047
|
-
const existing = currentGate(input.task_id, input.gate, d);
|
|
34048
|
-
if (!existing)
|
|
34049
|
-
throw new Error(`Approval gate not found: ${input.gate}`);
|
|
34050
|
-
if (existing.status !== "pending")
|
|
34051
|
-
throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
|
|
34052
|
-
const gate = writeGate({
|
|
34053
|
-
task_id: input.task_id,
|
|
34054
|
-
gate: input.gate,
|
|
34055
|
-
requester: existing.requester || undefined,
|
|
34056
|
-
reviewer: existing.reviewer || undefined,
|
|
34057
|
-
reason: existing.reason || undefined,
|
|
34058
|
-
plan_id: existing.plan_id || undefined,
|
|
34059
|
-
run_id: existing.run_id || undefined,
|
|
34060
|
-
expires_at: existing.expires_at || undefined
|
|
34061
|
-
}, "expired", { decided_by: input.reviewer, decided_at: now(), reason: input.reason || "expired" }, d);
|
|
34062
|
-
logApprovalEvent(input.task_id, "expired", gate, input.reviewer, d);
|
|
34063
|
-
return gate;
|
|
34064
|
-
}
|
|
34065
|
-
function listApprovalGates(taskId, db) {
|
|
34066
|
-
const d = db || getDatabase();
|
|
34067
|
-
ensureTask(taskId, d);
|
|
34068
|
-
return getTaskCheckpoints(taskId, d).filter((checkpoint) => checkpoint.data["approval_gate"] === true || checkpoint.step.startsWith("approval:")).map(gateFromCheckpoint);
|
|
34069
|
-
}
|
|
34070
|
-
function checkApprovalGate(taskId, gateName, db) {
|
|
34071
|
-
const d = db || getDatabase();
|
|
34072
|
-
ensureTask(taskId, d);
|
|
34073
|
-
const gate = currentGate(taskId, gateName, d);
|
|
34074
|
-
const reasons = [];
|
|
34075
|
-
if (!gate)
|
|
34076
|
-
reasons.push(`approval gate is required: ${gateName}`);
|
|
34077
|
-
else if (gate.status !== "approved")
|
|
34078
|
-
reasons.push(`approval gate ${gateName} is ${gate.status}`);
|
|
34079
|
-
if (gate && gate.status === "pending" && isExpired(gate.expires_at))
|
|
34080
|
-
reasons.push(`approval gate ${gateName} is expired`);
|
|
34081
|
-
return { allowed: reasons.length === 0, gate, reasons };
|
|
34082
|
-
}
|
|
34083
|
-
function assertApprovalGate(taskId, gateName, db) {
|
|
34084
|
-
const result = checkApprovalGate(taskId, gateName, db);
|
|
34085
|
-
if (!result.allowed)
|
|
34086
|
-
throw new Error(result.reasons.join("; "));
|
|
34087
|
-
return result.gate;
|
|
34088
|
-
}
|
|
34089
|
-
var init_approval_gates = __esm(() => {
|
|
34090
|
-
init_audit();
|
|
34091
|
-
init_checkpoints();
|
|
34092
|
-
init_database();
|
|
34093
|
-
init_task_runs();
|
|
34094
|
-
init_tasks();
|
|
34095
|
-
init_types();
|
|
34096
|
-
init_event_emission_safety();
|
|
34097
|
-
init_event_hooks();
|
|
34098
|
-
});
|
|
34099
|
-
|
|
34100
36383
|
// src/lib/terminal-notifications.ts
|
|
34101
36384
|
var exports_terminal_notifications = {};
|
|
34102
36385
|
__export(exports_terminal_notifications, {
|
|
@@ -34330,7 +36613,7 @@ var init_terminal_notifications = __esm(() => {
|
|
|
34330
36613
|
});
|
|
34331
36614
|
|
|
34332
36615
|
// src/db/api-keys.ts
|
|
34333
|
-
import { createHash as
|
|
36616
|
+
import { createHash as createHash11, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
34334
36617
|
function rowToRecord(row) {
|
|
34335
36618
|
return {
|
|
34336
36619
|
id: row.id,
|
|
@@ -34344,7 +36627,7 @@ function rowToRecord(row) {
|
|
|
34344
36627
|
};
|
|
34345
36628
|
}
|
|
34346
36629
|
function hashApiKey(key) {
|
|
34347
|
-
return
|
|
36630
|
+
return createHash11("sha256").update(key).digest("hex");
|
|
34348
36631
|
}
|
|
34349
36632
|
function safeEqualHex(a, b) {
|
|
34350
36633
|
if (a.length !== b.length)
|
|
@@ -34352,8 +36635,8 @@ function safeEqualHex(a, b) {
|
|
|
34352
36635
|
return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
34353
36636
|
}
|
|
34354
36637
|
function safeEqualStrings(a, b) {
|
|
34355
|
-
const ah =
|
|
34356
|
-
const bh =
|
|
36638
|
+
const ah = createHash11("sha256").update(a, "utf8").digest();
|
|
36639
|
+
const bh = createHash11("sha256").update(b, "utf8").digest();
|
|
34357
36640
|
return timingSafeEqual3(ah, bh);
|
|
34358
36641
|
}
|
|
34359
36642
|
function generatePlaintextKey() {
|
|
@@ -34895,8 +37178,33 @@ var init_postgres_sync = __esm(() => {
|
|
|
34895
37178
|
};
|
|
34896
37179
|
});
|
|
34897
37180
|
|
|
37181
|
+
// src/task-manifest/canonical.ts
|
|
37182
|
+
import { createHash as createHash12 } from "crypto";
|
|
37183
|
+
function canonicalize2(value) {
|
|
37184
|
+
if (Array.isArray(value))
|
|
37185
|
+
return value.map(canonicalize2);
|
|
37186
|
+
if (value !== null && typeof value === "object") {
|
|
37187
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
|
|
37188
|
+
}
|
|
37189
|
+
return value;
|
|
37190
|
+
}
|
|
37191
|
+
function canonicalJson2(value) {
|
|
37192
|
+
return JSON.stringify(canonicalize2(value));
|
|
37193
|
+
}
|
|
37194
|
+
function canonicalDigest(value) {
|
|
37195
|
+
return createHash12("sha256").update(canonicalJson2(value)).digest("hex");
|
|
37196
|
+
}
|
|
37197
|
+
function deterministicUuid(namespace, ...parts) {
|
|
37198
|
+
const bytes = createHash12("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
37199
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
37200
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
37201
|
+
const hex = bytes.toString("hex");
|
|
37202
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
37203
|
+
}
|
|
37204
|
+
var init_canonical = () => {};
|
|
37205
|
+
|
|
34898
37206
|
// src/storage/postgres-adapter.ts
|
|
34899
|
-
import { randomUUID as
|
|
37207
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
34900
37208
|
function createPostgresTodosStorageAdapter(options) {
|
|
34901
37209
|
const store = new PostgresJsonRecordStore(options);
|
|
34902
37210
|
const adapter = {
|
|
@@ -35120,7 +37428,7 @@ class PostgresJsonRecordStore {
|
|
|
35120
37428
|
AND task_record.deleted_at IS NULL
|
|
35121
37429
|
AND target.payload->>'locked_by' = $3
|
|
35122
37430
|
AND target.payload->>'locked_at' = $4
|
|
35123
|
-
AND
|
|
37431
|
+
AND $4::timestamptz < $5::timestamptz
|
|
35124
37432
|
AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
|
|
35125
37433
|
RETURNING task_record.payload
|
|
35126
37434
|
),
|
|
@@ -36274,7 +38582,7 @@ async function createTask2(input, store, context) {
|
|
|
36274
38582
|
const effectiveProjectId = linkedPlan?.project_id ?? requestedProjectId;
|
|
36275
38583
|
const shortId = effectiveProjectId ? await nextTaskShortId2(effectiveProjectId, store, context) : null;
|
|
36276
38584
|
const task = {
|
|
36277
|
-
id:
|
|
38585
|
+
id: randomUUID5(),
|
|
36278
38586
|
short_id: shortId,
|
|
36279
38587
|
project_id: effectiveProjectId,
|
|
36280
38588
|
parent_id: input.parent_id ?? null,
|
|
@@ -36520,7 +38828,7 @@ async function addVerification(input, store, context) {
|
|
|
36520
38828
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
36521
38829
|
const timestamp2 = new Date().toISOString();
|
|
36522
38830
|
const verification = {
|
|
36523
|
-
id:
|
|
38831
|
+
id: randomUUID5(),
|
|
36524
38832
|
task_id: input.task_id,
|
|
36525
38833
|
command: input.command,
|
|
36526
38834
|
status: input.status ?? "unknown",
|
|
@@ -36541,7 +38849,7 @@ async function addCommit(input, store, context) {
|
|
|
36541
38849
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
36542
38850
|
const timestamp2 = new Date().toISOString();
|
|
36543
38851
|
const commit = {
|
|
36544
|
-
id:
|
|
38852
|
+
id: randomUUID5(),
|
|
36545
38853
|
task_id: input.task_id,
|
|
36546
38854
|
sha: input.sha,
|
|
36547
38855
|
message: input.message ?? null,
|
|
@@ -36562,16 +38870,17 @@ async function findCommit(sha, store) {
|
|
|
36562
38870
|
async function addGitRef(input, store, context) {
|
|
36563
38871
|
if (!await store.get("tasks", input.task_id))
|
|
36564
38872
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
38873
|
+
const existing = (await store.list("refs")).filter((ref) => ref.task_id === input.task_id && ref.ref_type === input.ref_type && ref.name === input.name).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id))[0];
|
|
36565
38874
|
const timestamp2 = new Date().toISOString();
|
|
36566
38875
|
const gitRef = {
|
|
36567
|
-
id:
|
|
38876
|
+
id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
|
|
36568
38877
|
task_id: input.task_id,
|
|
36569
38878
|
ref_type: input.ref_type,
|
|
36570
38879
|
name: input.name,
|
|
36571
|
-
url: input.url ?? null,
|
|
36572
|
-
provider: input.provider ?? null,
|
|
38880
|
+
url: input.url ?? existing?.url ?? null,
|
|
38881
|
+
provider: input.provider ?? existing?.provider ?? null,
|
|
36573
38882
|
metadata: input.metadata ?? {},
|
|
36574
|
-
created_at: timestamp2,
|
|
38883
|
+
created_at: existing?.created_at ?? timestamp2,
|
|
36575
38884
|
updated_at: timestamp2
|
|
36576
38885
|
};
|
|
36577
38886
|
await store.upsert("refs", gitRef, context);
|
|
@@ -36634,7 +38943,7 @@ async function createProject2(input, store, context) {
|
|
|
36634
38943
|
if (!derivedSlug || !taskListId)
|
|
36635
38944
|
throw new Error("Project name and task-list slug must be non-empty");
|
|
36636
38945
|
const project = {
|
|
36637
|
-
id:
|
|
38946
|
+
id: randomUUID5(),
|
|
36638
38947
|
name: input.name,
|
|
36639
38948
|
path: input.path,
|
|
36640
38949
|
description: input.description ?? null,
|
|
@@ -36666,7 +38975,7 @@ async function createPlan2(input, store, context) {
|
|
|
36666
38975
|
store
|
|
36667
38976
|
});
|
|
36668
38977
|
return store.upsert("plans", {
|
|
36669
|
-
id:
|
|
38978
|
+
id: randomUUID5(),
|
|
36670
38979
|
slug,
|
|
36671
38980
|
project_id: projectId,
|
|
36672
38981
|
task_list_id: input.task_list_id ?? context?.taskListId ?? null,
|
|
@@ -36718,7 +39027,7 @@ async function registerAgent2(input, store, context) {
|
|
|
36718
39027
|
}
|
|
36719
39028
|
const timestamp2 = new Date().toISOString();
|
|
36720
39029
|
const agent = {
|
|
36721
|
-
id: existing?.id ??
|
|
39030
|
+
id: existing?.id ?? randomUUID5().slice(0, 8),
|
|
36722
39031
|
name: canonicalName,
|
|
36723
39032
|
description: input.description ?? existing?.description ?? null,
|
|
36724
39033
|
role: input.role ?? existing?.role ?? null,
|
|
@@ -36791,7 +39100,7 @@ async function createTaskList2(input, store, context) {
|
|
|
36791
39100
|
if (!slug)
|
|
36792
39101
|
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
36793
39102
|
return store.upsert("task_lists", {
|
|
36794
|
-
id:
|
|
39103
|
+
id: randomUUID5(),
|
|
36795
39104
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
36796
39105
|
slug,
|
|
36797
39106
|
name: input.name,
|
|
@@ -36826,7 +39135,7 @@ async function updateTaskList2(id, input, store) {
|
|
|
36826
39135
|
async function createTemplate2(input, store, context) {
|
|
36827
39136
|
const timestamp2 = new Date().toISOString();
|
|
36828
39137
|
const template = {
|
|
36829
|
-
id:
|
|
39138
|
+
id: randomUUID5(),
|
|
36830
39139
|
name: input.name,
|
|
36831
39140
|
title_pattern: input.title_pattern,
|
|
36832
39141
|
description: input.description ?? null,
|
|
@@ -36847,7 +39156,7 @@ async function createTemplate2(input, store, context) {
|
|
|
36847
39156
|
}
|
|
36848
39157
|
function buildTemplateTasks(templateId, inputs, timestamp2) {
|
|
36849
39158
|
return inputs.map((input, position) => ({
|
|
36850
|
-
id:
|
|
39159
|
+
id: randomUUID5(),
|
|
36851
39160
|
template_id: templateId,
|
|
36852
39161
|
position,
|
|
36853
39162
|
title_pattern: input.title_pattern,
|
|
@@ -36880,7 +39189,7 @@ async function updateTemplate2(id, input, store) {
|
|
|
36880
39189
|
}
|
|
36881
39190
|
async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context) {
|
|
36882
39191
|
const entry2 = {
|
|
36883
|
-
id:
|
|
39192
|
+
id: randomUUID5(),
|
|
36884
39193
|
task_id: taskId,
|
|
36885
39194
|
action,
|
|
36886
39195
|
field: field ?? null,
|
|
@@ -36894,7 +39203,7 @@ async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId
|
|
|
36894
39203
|
}
|
|
36895
39204
|
async function addComment2(input, store, context) {
|
|
36896
39205
|
const comment = {
|
|
36897
|
-
id:
|
|
39206
|
+
id: randomUUID5(),
|
|
36898
39207
|
task_id: input.task_id,
|
|
36899
39208
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
36900
39209
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
@@ -37133,6 +39442,7 @@ var init_postgres_adapter = __esm(() => {
|
|
|
37133
39442
|
init_integrity();
|
|
37134
39443
|
init_redaction();
|
|
37135
39444
|
init_audit_history_import();
|
|
39445
|
+
init_canonical();
|
|
37136
39446
|
TASK_ORDER_BY = `ORDER BY ${TASK_ORDER_TIEBREAK}`;
|
|
37137
39447
|
});
|
|
37138
39448
|
|
|
@@ -38620,25 +40930,25 @@ var init_sqlite = __esm(() => {
|
|
|
38620
40930
|
});
|
|
38621
40931
|
|
|
38622
40932
|
// src/project-registration/authority.ts
|
|
38623
|
-
import { createHash as
|
|
40933
|
+
import { createHash as createHash13 } from "crypto";
|
|
38624
40934
|
function canonicalProjectRegistrationJson(value) {
|
|
38625
|
-
return JSON.stringify(
|
|
40935
|
+
return JSON.stringify(canonicalize3(value));
|
|
38626
40936
|
}
|
|
38627
|
-
function
|
|
40937
|
+
function canonicalize3(value) {
|
|
38628
40938
|
if (Array.isArray(value))
|
|
38629
|
-
return value.map(
|
|
40939
|
+
return value.map(canonicalize3);
|
|
38630
40940
|
if (!value || typeof value !== "object")
|
|
38631
40941
|
return value;
|
|
38632
40942
|
const out = {};
|
|
38633
40943
|
for (const key of Object.keys(value).sort()) {
|
|
38634
40944
|
const entry2 = value[key];
|
|
38635
40945
|
if (entry2 !== undefined)
|
|
38636
|
-
out[key] =
|
|
40946
|
+
out[key] = canonicalize3(entry2);
|
|
38637
40947
|
}
|
|
38638
40948
|
return out;
|
|
38639
40949
|
}
|
|
38640
40950
|
function digestProjectRegistrationValue(value) {
|
|
38641
|
-
return
|
|
40951
|
+
return createHash13("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
|
|
38642
40952
|
}
|
|
38643
40953
|
function deriveTodosProjectRegistrationIdempotencyKey(input) {
|
|
38644
40954
|
return `prk_${digestProjectRegistrationValue({
|
|
@@ -39633,31 +41943,6 @@ var init_project_registration = __esm(() => {
|
|
|
39633
41943
|
init_types3();
|
|
39634
41944
|
});
|
|
39635
41945
|
|
|
39636
|
-
// src/task-manifest/canonical.ts
|
|
39637
|
-
import { createHash as createHash12 } from "crypto";
|
|
39638
|
-
function canonicalize3(value) {
|
|
39639
|
-
if (Array.isArray(value))
|
|
39640
|
-
return value.map(canonicalize3);
|
|
39641
|
-
if (value !== null && typeof value === "object") {
|
|
39642
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize3(entry2)]));
|
|
39643
|
-
}
|
|
39644
|
-
return value;
|
|
39645
|
-
}
|
|
39646
|
-
function canonicalJson2(value) {
|
|
39647
|
-
return JSON.stringify(canonicalize3(value));
|
|
39648
|
-
}
|
|
39649
|
-
function canonicalDigest(value) {
|
|
39650
|
-
return createHash12("sha256").update(canonicalJson2(value)).digest("hex");
|
|
39651
|
-
}
|
|
39652
|
-
function deterministicUuid(namespace, ...parts) {
|
|
39653
|
-
const bytes = createHash12("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
39654
|
-
bytes[6] = bytes[6] & 15 | 80;
|
|
39655
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
39656
|
-
const hex = bytes.toString("hex");
|
|
39657
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
39658
|
-
}
|
|
39659
|
-
var init_canonical = () => {};
|
|
39660
|
-
|
|
39661
41946
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
39662
41947
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
39663
41948
|
const t = typeof data;
|
|
@@ -44211,7 +46496,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
44211
46496
|
result_digest: input.result_digest
|
|
44212
46497
|
};
|
|
44213
46498
|
const manifestJson = canonicalJson2(manifest);
|
|
44214
|
-
const
|
|
46499
|
+
const resultJson2 = canonicalJson2(result);
|
|
44215
46500
|
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
44216
46501
|
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
44217
46502
|
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
@@ -44223,7 +46508,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
44223
46508
|
input.request_digest,
|
|
44224
46509
|
input.result_digest,
|
|
44225
46510
|
manifestJson,
|
|
44226
|
-
|
|
46511
|
+
resultJson2,
|
|
44227
46512
|
input.now
|
|
44228
46513
|
]);
|
|
44229
46514
|
for (const entry2 of input.outbox) {
|
|
@@ -44250,7 +46535,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
44250
46535
|
input.result_digest,
|
|
44251
46536
|
input.receipt_id,
|
|
44252
46537
|
manifestJson,
|
|
44253
|
-
|
|
46538
|
+
resultJson2,
|
|
44254
46539
|
input.now
|
|
44255
46540
|
]);
|
|
44256
46541
|
fault(faults, "after_receipt_write");
|
|
@@ -47160,6 +49445,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
47160
49445
|
plan_id: { type: "string" },
|
|
47161
49446
|
assigned_to: { type: "string" },
|
|
47162
49447
|
agent_id: { type: "string" },
|
|
49448
|
+
created_by: { type: "string" },
|
|
47163
49449
|
tags: { type: "array", items: { type: "string" } }
|
|
47164
49450
|
}
|
|
47165
49451
|
},
|
|
@@ -49002,6 +51288,7 @@ var init_openapi = __esm(() => {
|
|
|
49002
51288
|
parent_id: { type: "string", nullable: true },
|
|
49003
51289
|
assigned_to: { type: "string", nullable: true },
|
|
49004
51290
|
agent_id: { type: "string", nullable: true },
|
|
51291
|
+
created_by: { type: "string", nullable: true },
|
|
49005
51292
|
reason: { type: "string", nullable: true },
|
|
49006
51293
|
tags: { type: "array", items: { type: "string" } },
|
|
49007
51294
|
version: { type: "number" },
|
|
@@ -51598,7 +53885,7 @@ async function validateMcpAssignee(value, allowSeat) {
|
|
|
51598
53885
|
function registerTaskCrudTools(server, ctx) {
|
|
51599
53886
|
const { shouldRegisterTool, resolveId, formatError, formatTask, applyFocus } = ctx;
|
|
51600
53887
|
function mutationTaskResponse(task2) {
|
|
51601
|
-
const compact =
|
|
53888
|
+
const compact = compactTask2(task2, 240);
|
|
51602
53889
|
compact["version"] = task2.version;
|
|
51603
53890
|
compact["created_at"] = task2.created_at;
|
|
51604
53891
|
compact["task_list_id"] = task2.task_list_id;
|
|
@@ -51820,7 +54107,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
51820
54107
|
throw new TaskNotFoundError(task_id);
|
|
51821
54108
|
const focus = ctx.getAgentFocus(task2.assigned_to || "");
|
|
51822
54109
|
if (detail !== "full") {
|
|
51823
|
-
const compact =
|
|
54110
|
+
const compact = compactTask2(task2, max_description_chars || 240);
|
|
51824
54111
|
compact["version"] = task2.version;
|
|
51825
54112
|
compact["created_at"] = task2.created_at;
|
|
51826
54113
|
compact["focus"] = focus ? { agent_id: focus.agent_id, project_id: focus.project_id || null } : null;
|
|
@@ -52168,7 +54455,7 @@ var exports_mention_resolver = {};
|
|
|
52168
54455
|
__export(exports_mention_resolver, {
|
|
52169
54456
|
resolveMentions: () => resolveMentions
|
|
52170
54457
|
});
|
|
52171
|
-
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as
|
|
54458
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync10 } from "fs";
|
|
52172
54459
|
import { basename as basename8, isAbsolute, join as join19, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
|
|
52173
54460
|
function blankResolution(parsed) {
|
|
52174
54461
|
return {
|
|
@@ -52277,7 +54564,7 @@ function resolveFile(parsed, workspace) {
|
|
|
52277
54564
|
return resolution;
|
|
52278
54565
|
}
|
|
52279
54566
|
if (parsed.line !== undefined) {
|
|
52280
|
-
const lineCount =
|
|
54567
|
+
const lineCount = readFileSync13(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
52281
54568
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
52282
54569
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
52283
54570
|
return resolution;
|
|
@@ -52330,7 +54617,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
52330
54617
|
const pattern = symbolPattern(name);
|
|
52331
54618
|
const matches = [];
|
|
52332
54619
|
for (const file of walkSourceFiles(workspace)) {
|
|
52333
|
-
const lines =
|
|
54620
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/);
|
|
52334
54621
|
for (let index = 0;index < lines.length; index += 1) {
|
|
52335
54622
|
const line = lines[index];
|
|
52336
54623
|
const found = pattern.exec(line);
|
|
@@ -54783,7 +57070,7 @@ __export(exports_audit_ledger, {
|
|
|
54783
57070
|
LOCAL_AUDIT_LEDGER_INITIAL_HASH: () => LOCAL_AUDIT_LEDGER_INITIAL_HASH,
|
|
54784
57071
|
LOCAL_AUDIT_LEDGER_HASH_ALGORITHM: () => LOCAL_AUDIT_LEDGER_HASH_ALGORITHM
|
|
54785
57072
|
});
|
|
54786
|
-
import { createHash as
|
|
57073
|
+
import { createHash as createHash14 } from "crypto";
|
|
54787
57074
|
function canonicalize4(value) {
|
|
54788
57075
|
if (value === null || typeof value !== "object")
|
|
54789
57076
|
return JSON.stringify(value);
|
|
@@ -54793,7 +57080,7 @@ function canonicalize4(value) {
|
|
|
54793
57080
|
return `{${Object.keys(object).sort().map((key2) => `${JSON.stringify(key2)}:${canonicalize4(object[key2])}`).join(",")}}`;
|
|
54794
57081
|
}
|
|
54795
57082
|
function hash(value) {
|
|
54796
|
-
return
|
|
57083
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
54797
57084
|
}
|
|
54798
57085
|
function parsePayload3(value) {
|
|
54799
57086
|
if (!value)
|
|
@@ -55072,7 +57359,7 @@ __export(exports_release_compatibility, {
|
|
|
55072
57359
|
createReleaseCompatibilityReport: () => createReleaseCompatibilityReport,
|
|
55073
57360
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
55074
57361
|
});
|
|
55075
|
-
import { readFileSync as
|
|
57362
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
55076
57363
|
import { join as join20, resolve as resolve19 } from "path";
|
|
55077
57364
|
import { Database as Database3 } from "bun:sqlite";
|
|
55078
57365
|
function pass(id, message, details) {
|
|
@@ -55085,7 +57372,7 @@ function warn(id, message, details) {
|
|
|
55085
57372
|
return { id, status: "warning", message, details };
|
|
55086
57373
|
}
|
|
55087
57374
|
function readPackageJson2(root) {
|
|
55088
|
-
return JSON.parse(
|
|
57375
|
+
return JSON.parse(readFileSync14(join20(root, "package.json"), "utf8"));
|
|
55089
57376
|
}
|
|
55090
57377
|
function sortedKeys(value) {
|
|
55091
57378
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -58193,7 +60480,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
58193
60480
|
type: "text",
|
|
58194
60481
|
text: compactJson({
|
|
58195
60482
|
status: compactStatus(cloudStatus),
|
|
58196
|
-
next_task: next_task2 ?
|
|
60483
|
+
next_task: next_task2 ? compactTask2(next_task2, max_description_chars || 180) : null,
|
|
58197
60484
|
overdue_count: 0,
|
|
58198
60485
|
latest_handoff: null,
|
|
58199
60486
|
as_of: payload2.as_of
|
|
@@ -58232,7 +60519,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
58232
60519
|
type: "text",
|
|
58233
60520
|
text: compactJson({
|
|
58234
60521
|
status: compactStatus(status2),
|
|
58235
|
-
next_task: next_task ?
|
|
60522
|
+
next_task: next_task ? compactTask2(next_task, max_description_chars || 180) : null,
|
|
58236
60523
|
overdue_count: overdue.length,
|
|
58237
60524
|
latest_handoff: compactHandoff(latest_handoff),
|
|
58238
60525
|
as_of: payload.as_of
|
|
@@ -59634,7 +61921,7 @@ function registerTaskAdvTools(server, ctx) {
|
|
|
59634
61921
|
const task3 = await cloudGetTask(cloud, task_id);
|
|
59635
61922
|
if (!task3)
|
|
59636
61923
|
throw new Error(`Task not found: ${task_id}`);
|
|
59637
|
-
return { content: [{ type: "text", text: compactJson({ source: "cloud", task:
|
|
61924
|
+
return { content: [{ type: "text", text: compactJson({ source: "cloud", task: compactTask2(task3, 160) }) }] };
|
|
59638
61925
|
}
|
|
59639
61926
|
const resolvedId = resolveId(task_id);
|
|
59640
61927
|
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
@@ -59655,7 +61942,7 @@ function registerTaskAdvTools(server, ctx) {
|
|
|
59655
61942
|
content: [{
|
|
59656
61943
|
type: "text",
|
|
59657
61944
|
text: compactJson({
|
|
59658
|
-
task:
|
|
61945
|
+
task: compactTask2(task2, 160),
|
|
59659
61946
|
dependencies: {
|
|
59660
61947
|
count: deps.length,
|
|
59661
61948
|
items: deps.slice(0, 10).map((d) => ({ direction: d.direction, task_id: d.task_id, status: d.status })),
|
|
@@ -59737,7 +62024,7 @@ Files (${files.length}):` : null,
|
|
|
59737
62024
|
content: [{
|
|
59738
62025
|
type: "text",
|
|
59739
62026
|
text: compactJson({
|
|
59740
|
-
task:
|
|
62027
|
+
task: compactTask2(task2, max_description_chars || 240),
|
|
59741
62028
|
dependencies: {
|
|
59742
62029
|
count: deps.length,
|
|
59743
62030
|
items: deps.slice(0, 10).map((d) => ({ direction: d.direction, task_id: d.task_id, status: d.status })),
|
|
@@ -60749,7 +63036,7 @@ function updateDispatcherMetadata(runId, dispatcher, db) {
|
|
|
60749
63036
|
d.run("UPDATE task_runs SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), run.id]);
|
|
60750
63037
|
return getTaskRun(run.id, d);
|
|
60751
63038
|
}
|
|
60752
|
-
function
|
|
63039
|
+
function resolveAdapter2(adapter) {
|
|
60753
63040
|
if (!adapter)
|
|
60754
63041
|
return null;
|
|
60755
63042
|
return loadConfig().agent_run_adapters?.[adapter] || null;
|
|
@@ -60800,7 +63087,7 @@ function removeAgentRunAdapter(name) {
|
|
|
60800
63087
|
return true;
|
|
60801
63088
|
}
|
|
60802
63089
|
function queueAgentRun(input, db) {
|
|
60803
|
-
const adapter =
|
|
63090
|
+
const adapter = resolveAdapter2(input.adapter);
|
|
60804
63091
|
const command = input.command || adapter?.command;
|
|
60805
63092
|
if (!command)
|
|
60806
63093
|
throw new Error("agent run requires --command or a configured adapter command");
|
|
@@ -60867,7 +63154,7 @@ async function runNextAgentDispatch(input = {}, db) {
|
|
|
60867
63154
|
const next = listAgentRunQueue(d).find((item) => item.dispatcher.state === "queued" && (!input.adapter || item.dispatcher.adapter === input.adapter));
|
|
60868
63155
|
if (!next)
|
|
60869
63156
|
return null;
|
|
60870
|
-
const adapter =
|
|
63157
|
+
const adapter = resolveAdapter2(next.dispatcher.adapter);
|
|
60871
63158
|
const command = renderCommand(next.dispatcher.command, next.run);
|
|
60872
63159
|
const cwd = next.dispatcher.cwd || adapter?.cwd || process.cwd();
|
|
60873
63160
|
const sandbox = next.dispatcher.sandbox || adapter?.sandbox;
|
|
@@ -60963,7 +63250,7 @@ function queuedRunById(runId, db) {
|
|
|
60963
63250
|
return item.dispatcher ? item : null;
|
|
60964
63251
|
}
|
|
60965
63252
|
function enqueueAgentRun(input, db) {
|
|
60966
|
-
const command = input.command ||
|
|
63253
|
+
const command = input.command || resolveAdapter2(input.adapter)?.command || `agent-run:${input.adapter ?? "custom"}`;
|
|
60967
63254
|
const queued = queueAgentRun({
|
|
60968
63255
|
task_id: input.task_id,
|
|
60969
63256
|
agent_id: input.agent_id,
|
|
@@ -61055,7 +63342,7 @@ __export(exports_verification_providers, {
|
|
|
61055
63342
|
getVerificationRecord: () => getVerificationRecord,
|
|
61056
63343
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
61057
63344
|
});
|
|
61058
|
-
import { existsSync as existsSync20, readFileSync as
|
|
63345
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
61059
63346
|
function normalizeName6(name) {
|
|
61060
63347
|
const normalized = name.trim().toLowerCase();
|
|
61061
63348
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -61207,7 +63494,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
61207
63494
|
};
|
|
61208
63495
|
}
|
|
61209
63496
|
function runCiLogProvider(input) {
|
|
61210
|
-
const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ?
|
|
63497
|
+
const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync15(input.log_path, "utf-8") : "");
|
|
61211
63498
|
return {
|
|
61212
63499
|
status: classifyLog(text),
|
|
61213
63500
|
attempts: 1,
|
|
@@ -63509,8 +65796,8 @@ __export(exports_local_backups, {
|
|
|
63509
65796
|
TODOS_LOCAL_BACKUP_KIND: () => TODOS_LOCAL_BACKUP_KIND,
|
|
63510
65797
|
LOCAL_BACKUP_CHECKSUM_ALGORITHM: () => LOCAL_BACKUP_CHECKSUM_ALGORITHM
|
|
63511
65798
|
});
|
|
63512
|
-
import { createHash as
|
|
63513
|
-
import { readFileSync as
|
|
65799
|
+
import { createHash as createHash15 } from "crypto";
|
|
65800
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
|
|
63514
65801
|
import { dirname as dirname9, resolve as resolve20 } from "path";
|
|
63515
65802
|
import { mkdirSync as mkdirSync10 } from "fs";
|
|
63516
65803
|
function stableJson2(value) {
|
|
@@ -63522,7 +65809,7 @@ function stableJson2(value) {
|
|
|
63522
65809
|
return `{${Object.keys(record).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson2(record[key2])}`).join(",")}}`;
|
|
63523
65810
|
}
|
|
63524
65811
|
function sha2565(value) {
|
|
63525
|
-
return
|
|
65812
|
+
return createHash15("sha256").update(stableJson2(value)).digest("hex");
|
|
63526
65813
|
}
|
|
63527
65814
|
function sqliteIntegrity(db) {
|
|
63528
65815
|
let quick = "unknown";
|
|
@@ -63620,7 +65907,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
63620
65907
|
return path;
|
|
63621
65908
|
}
|
|
63622
65909
|
function readLocalBackupFile(path) {
|
|
63623
|
-
return JSON.parse(
|
|
65910
|
+
return JSON.parse(readFileSync16(resolve20(path), "utf-8"));
|
|
63624
65911
|
}
|
|
63625
65912
|
function verifyLocalBackup(value, options = {}, db) {
|
|
63626
65913
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -64203,7 +66490,7 @@ __export(exports_local_snapshots, {
|
|
|
64203
66490
|
getLocalSnapshot: () => getLocalSnapshot,
|
|
64204
66491
|
TODOS_LOCAL_SNAPSHOT_SCHEMA_VERSION: () => TODOS_LOCAL_SNAPSHOT_SCHEMA_VERSION
|
|
64205
66492
|
});
|
|
64206
|
-
import { createHash as
|
|
66493
|
+
import { createHash as createHash16 } from "crypto";
|
|
64207
66494
|
function source2(version) {
|
|
64208
66495
|
return {
|
|
64209
66496
|
packageName: "@hasna/todos",
|
|
@@ -64227,7 +66514,7 @@ function stable(value) {
|
|
|
64227
66514
|
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => [key2, stable(item)]));
|
|
64228
66515
|
}
|
|
64229
66516
|
function sha2566(value) {
|
|
64230
|
-
return
|
|
66517
|
+
return createHash16("sha256").update(JSON.stringify(stable(value))).digest("hex");
|
|
64231
66518
|
}
|
|
64232
66519
|
function latestTimestamp2(items, fallback) {
|
|
64233
66520
|
const timestamps = [];
|
|
@@ -64799,8 +67086,8 @@ __export(exports_agent_replay_simulator, {
|
|
|
64799
67086
|
simulateAgentReplay: () => simulateAgentReplay,
|
|
64800
67087
|
renderAgentReplaySimulationMarkdown: () => renderAgentReplaySimulationMarkdown
|
|
64801
67088
|
});
|
|
64802
|
-
import { createHash as
|
|
64803
|
-
import { readFileSync as
|
|
67089
|
+
import { createHash as createHash17 } from "crypto";
|
|
67090
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
64804
67091
|
function isObject2(value) {
|
|
64805
67092
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
64806
67093
|
}
|
|
@@ -64821,7 +67108,7 @@ function stable2(value) {
|
|
|
64821
67108
|
return Object.fromEntries(Object.keys(value).sort().map((key2) => [key2, stable2(value[key2])]));
|
|
64822
67109
|
}
|
|
64823
67110
|
function fingerprint2(value) {
|
|
64824
|
-
return
|
|
67111
|
+
return createHash17("sha256").update(JSON.stringify(stable2(value))).digest("hex");
|
|
64825
67112
|
}
|
|
64826
67113
|
function unpackFixture(input) {
|
|
64827
67114
|
if (!isObject2(input))
|
|
@@ -65033,7 +67320,7 @@ function simulateAgentReplay(input, options = {}) {
|
|
|
65033
67320
|
};
|
|
65034
67321
|
}
|
|
65035
67322
|
function simulateAgentReplayFile(path, options = {}) {
|
|
65036
|
-
const parsed = JSON.parse(
|
|
67323
|
+
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
65037
67324
|
return simulateAgentReplay(parsed, options);
|
|
65038
67325
|
}
|
|
65039
67326
|
function renderAgentReplaySimulationMarkdown(simulation) {
|
|
@@ -65139,7 +67426,7 @@ __export(exports_inbox, {
|
|
|
65139
67426
|
deriveInboxTitle: () => deriveInboxTitle,
|
|
65140
67427
|
createInboxItem: () => createInboxItem
|
|
65141
67428
|
});
|
|
65142
|
-
import { createHash as
|
|
67429
|
+
import { createHash as createHash18 } from "crypto";
|
|
65143
67430
|
function parseMetadata3(value) {
|
|
65144
67431
|
if (!value)
|
|
65145
67432
|
return {};
|
|
@@ -65159,7 +67446,7 @@ function compactWhitespace(value) {
|
|
|
65159
67446
|
function fingerprintInboxInput(input) {
|
|
65160
67447
|
const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
|
|
65161
67448
|
const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
|
|
65162
|
-
return
|
|
67449
|
+
return createHash18("sha256").update(`${sourceType}
|
|
65163
67450
|
${input.source_url || ""}
|
|
65164
67451
|
${normalized}`).digest("hex");
|
|
65165
67452
|
}
|
|
@@ -70371,13 +72658,13 @@ __export(exports_environment_snapshots, {
|
|
|
70371
72658
|
compareEnvironmentSnapshotFiles: () => compareEnvironmentSnapshotFiles,
|
|
70372
72659
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
70373
72660
|
});
|
|
70374
|
-
import { createHash as
|
|
70375
|
-
import { existsSync as existsSync21, readFileSync as
|
|
72661
|
+
import { createHash as createHash19 } from "crypto";
|
|
72662
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18, statSync as statSync11 } from "fs";
|
|
70376
72663
|
import { hostname as hostname2, platform, arch } from "os";
|
|
70377
72664
|
import { dirname as dirname10, join as join22, resolve as resolve21 } from "path";
|
|
70378
72665
|
import { tmpdir as tmpdir4 } from "os";
|
|
70379
72666
|
function sha2567(value) {
|
|
70380
|
-
return
|
|
72667
|
+
return createHash19("sha256").update(value).digest("hex");
|
|
70381
72668
|
}
|
|
70382
72669
|
function fileRecord(root, relativePath) {
|
|
70383
72670
|
const path = join22(root, relativePath);
|
|
@@ -70386,7 +72673,7 @@ function fileRecord(root, relativePath) {
|
|
|
70386
72673
|
const stat = statSync11(path);
|
|
70387
72674
|
if (!stat.isFile())
|
|
70388
72675
|
return null;
|
|
70389
|
-
const content =
|
|
72676
|
+
const content = readFileSync18(path);
|
|
70390
72677
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
70391
72678
|
}
|
|
70392
72679
|
function manifestRecord(root, relativePath) {
|
|
@@ -70756,7 +73043,7 @@ Environment:
|
|
|
70756
73043
|
TODOS_TOOL_GROUPS=<list> Comma-separated tool group filter`);
|
|
70757
73044
|
}
|
|
70758
73045
|
function shouldRegisterTool(name) {
|
|
70759
|
-
return
|
|
73046
|
+
return shouldRegisterToolForProfile2(name);
|
|
70760
73047
|
}
|
|
70761
73048
|
function getAgentFocus(agentId) {
|
|
70762
73049
|
const sessionFocus = agentFocusMap.get(agentId);
|
|
@@ -73432,7 +75719,7 @@ __export(exports_config_serve_commands, {
|
|
|
73432
75719
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
73433
75720
|
});
|
|
73434
75721
|
import chalk7 from "chalk";
|
|
73435
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as
|
|
75722
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
|
|
73436
75723
|
import { dirname as dirname12, join as join24 } from "path";
|
|
73437
75724
|
function registerConfigServeCommands(program2) {
|
|
73438
75725
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
@@ -73463,7 +75750,7 @@ function registerConfigServeCommands(program2) {
|
|
|
73463
75750
|
}
|
|
73464
75751
|
let config2 = {};
|
|
73465
75752
|
try {
|
|
73466
|
-
config2 = JSON.parse(
|
|
75753
|
+
config2 = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
73467
75754
|
} catch {}
|
|
73468
75755
|
const keys = key2.split(".");
|
|
73469
75756
|
let obj = config2;
|
|
@@ -73603,7 +75890,7 @@ function registerConfigServeCommands(program2) {
|
|
|
73603
75890
|
redaction.command("scan [text]").description("Scan text or a file for secret-like values without printing values").option("--file <path>", "File to scan").action(async (text2, opts) => {
|
|
73604
75891
|
const globalOpts = program2.opts();
|
|
73605
75892
|
const { listSecretFindings: listSecretFindings2 } = await Promise.resolve().then(() => (init_redaction(), exports_redaction));
|
|
73606
|
-
const value = opts.file ?
|
|
75893
|
+
const value = opts.file ? readFileSync19(opts.file, "utf-8") : text2 || "";
|
|
73607
75894
|
const findings = listSecretFindings2(value);
|
|
73608
75895
|
if (globalOpts.json) {
|
|
73609
75896
|
output({ ok: findings.length === 0, findings }, true);
|
|
@@ -75168,14 +77455,14 @@ __export(exports_task_route_sources, {
|
|
|
75168
77455
|
TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION: () => TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION
|
|
75169
77456
|
});
|
|
75170
77457
|
import { Database as Database4 } from "bun:sqlite";
|
|
75171
|
-
import { createHash as
|
|
77458
|
+
import { createHash as createHash20 } from "crypto";
|
|
75172
77459
|
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync12 } from "fs";
|
|
75173
77460
|
import { basename as basename10, dirname as dirname14, join as join26, resolve as resolve22 } from "path";
|
|
75174
77461
|
function normalizePath6(input) {
|
|
75175
77462
|
return resolve22(input);
|
|
75176
77463
|
}
|
|
75177
77464
|
function sourceStoreId(sourceDbPath) {
|
|
75178
|
-
const digest2 =
|
|
77465
|
+
const digest2 = createHash20("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
|
|
75179
77466
|
return `sqlite:${digest2}`;
|
|
75180
77467
|
}
|
|
75181
77468
|
function inferSourceRepoPath(sourceDbPath) {
|
|
@@ -75507,7 +77794,7 @@ __export(exports_tester_issue_reports, {
|
|
|
75507
77794
|
TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
75508
77795
|
TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION
|
|
75509
77796
|
});
|
|
75510
|
-
import { createHash as
|
|
77797
|
+
import { createHash as createHash21 } from "crypto";
|
|
75511
77798
|
function asObject3(value) {
|
|
75512
77799
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
75513
77800
|
}
|
|
@@ -75679,7 +77966,7 @@ function fingerprintTesterIssueReport(report) {
|
|
|
75679
77966
|
normalizeText4(report.failure?.message || report.summary || report.title).slice(0, 240),
|
|
75680
77967
|
normalizeText4(stackTop).slice(0, 160)
|
|
75681
77968
|
].join("::");
|
|
75682
|
-
return `testers:${
|
|
77969
|
+
return `testers:${createHash21("sha256").update(raw).digest("hex").slice(0, 16)}`;
|
|
75683
77970
|
}
|
|
75684
77971
|
function priorityForSeverity(severity, fallback) {
|
|
75685
77972
|
return PRIORITIES5.includes(severity) ? severity : fallback;
|
|
@@ -75978,7 +78265,7 @@ __export(exports_query_commands, {
|
|
|
75978
78265
|
registerQueryCommands: () => registerQueryCommands
|
|
75979
78266
|
});
|
|
75980
78267
|
import chalk9 from "chalk";
|
|
75981
|
-
import { readFileSync as
|
|
78268
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
|
|
75982
78269
|
function parseJsonObjectOption2(value, label) {
|
|
75983
78270
|
if (!value)
|
|
75984
78271
|
return;
|
|
@@ -77742,7 +80029,7 @@ Findings`));
|
|
|
77742
80029
|
const sessionId = opts.session || globalOpts.session || undefined;
|
|
77743
80030
|
try {
|
|
77744
80031
|
if (opts.import) {
|
|
77745
|
-
const bundle = JSON.parse(
|
|
80032
|
+
const bundle = JSON.parse(readFileSync20(opts.import, "utf-8"));
|
|
77746
80033
|
const result = importHandoffBundle(bundle, { apply: opts.apply }, db);
|
|
77747
80034
|
if (opts.json || globalOpts.json) {
|
|
77748
80035
|
console.log(JSON.stringify(result));
|
|
@@ -78116,7 +80403,7 @@ Findings`));
|
|
|
78116
80403
|
});
|
|
78117
80404
|
calendar.command("import <path>").description("Import VEVENT entries from an ICS file as local imported calendar items").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
78118
80405
|
try {
|
|
78119
|
-
const result = importCalendarIcs(
|
|
80406
|
+
const result = importCalendarIcs(readFileSync20(path, "utf-8"));
|
|
78120
80407
|
if (opts.json || program2.opts().json) {
|
|
78121
80408
|
output(result, true);
|
|
78122
80409
|
return;
|
|
@@ -78267,7 +80554,7 @@ Findings`));
|
|
|
78267
80554
|
});
|
|
78268
80555
|
board.command("import <path>").description("Import local board definitions from a JSON bundle").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
78269
80556
|
try {
|
|
78270
|
-
const bundle = JSON.parse(
|
|
80557
|
+
const bundle = JSON.parse(readFileSync20(path, "utf-8"));
|
|
78271
80558
|
const result = importTaskBoardBundle(bundle);
|
|
78272
80559
|
if (opts.json || program2.opts().json) {
|
|
78273
80560
|
output(result, true);
|
|
@@ -78643,7 +80930,7 @@ Findings`));
|
|
|
78643
80930
|
const { importExternalIssues: importExternalIssues2 } = await Promise.resolve().then(() => (init_external_issue_importers(), exports_external_issue_importers));
|
|
78644
80931
|
let body2 = text2 || "";
|
|
78645
80932
|
if (opts.file)
|
|
78646
|
-
body2 =
|
|
80933
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
78647
80934
|
if (!body2 && !opts.url && !process.stdin.isTTY)
|
|
78648
80935
|
body2 = await Bun.stdin.text();
|
|
78649
80936
|
if (!body2.trim() && !opts.url) {
|
|
@@ -78700,7 +80987,7 @@ Findings`));
|
|
|
78700
80987
|
} = await Promise.resolve().then(() => (init_tester_issue_reports(), exports_tester_issue_reports));
|
|
78701
80988
|
let body2 = jsonText || "";
|
|
78702
80989
|
if (opts.file)
|
|
78703
|
-
body2 =
|
|
80990
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
78704
80991
|
if (!body2 && !process.stdin.isTTY)
|
|
78705
80992
|
body2 = await Bun.stdin.text();
|
|
78706
80993
|
if (!body2.trim()) {
|
|
@@ -78743,11 +81030,11 @@ Findings`));
|
|
|
78743
81030
|
const inbox = program2.command("inbox").description("Capture local inbox items from pasted errors, CI logs, git context, files, or GitHub issue URLs");
|
|
78744
81031
|
inbox.command("add [text]").description("Create a local inbox item and linked task from text, stdin, or a file").option("--file <path>", "Read captured context from a file").option("--source-type <type>", "pasted_error, ci_log, git_context, github_issue, file, or other").option("--source-name <name>", "Human-readable source name").option("--source-url <url>", "Source URL, including GitHub issue URLs").option("--title <title>", "Task/inbox title").option("--priority <priority>", "Task priority").option("--tags <tags>", "Comma-separated extra tags").option("--metadata <json>", "Additional JSON metadata").option("--no-task", "Only store inbox item; do not create a linked task").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
78745
81032
|
const globalOpts = program2.opts();
|
|
78746
|
-
const { readFileSync:
|
|
81033
|
+
const { readFileSync: readFileSync21 } = await import("fs");
|
|
78747
81034
|
const { createInboxItem: createInboxItem2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
78748
81035
|
let body2 = text2 || "";
|
|
78749
81036
|
if (opts.file)
|
|
78750
|
-
body2 =
|
|
81037
|
+
body2 = readFileSync21(opts.file, "utf-8");
|
|
78751
81038
|
if (!body2 && !process.stdin.isTTY)
|
|
78752
81039
|
body2 = await Bun.stdin.text();
|
|
78753
81040
|
if (!body2.trim()) {
|
|
@@ -78807,11 +81094,11 @@ ${diff}` : null].filter(Boolean).join(`
|
|
|
78807
81094
|
});
|
|
78808
81095
|
inbox.command("parse [text]").description("Preview or apply deterministic local natural-language task intake").option("--file <path>", "Read natural-language input from a file").option("--priority <priority>", "Default priority for parsed tasks", "medium").option("--project <id>", "Project ID for applied tasks").option("--list <id>", "Task list ID for applied tasks").option("--reference-date <iso>", "Reference date for due today/tomorrow/next week").option("--apply", "Create parsed tasks; default is dry-run preview").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
78809
81096
|
const globalOpts = program2.opts();
|
|
78810
|
-
const { readFileSync:
|
|
81097
|
+
const { readFileSync: readFileSync21 } = await import("fs");
|
|
78811
81098
|
const { previewNaturalLanguageIntake: previewNaturalLanguageIntake2 } = await Promise.resolve().then(() => (init_natural_language_intake(), exports_natural_language_intake));
|
|
78812
81099
|
let body2 = text2 || "";
|
|
78813
81100
|
if (opts.file)
|
|
78814
|
-
body2 =
|
|
81101
|
+
body2 = readFileSync21(opts.file, "utf-8");
|
|
78815
81102
|
if (!body2 && !process.stdin.isTTY)
|
|
78816
81103
|
body2 = await Bun.stdin.text();
|
|
78817
81104
|
if (!body2.trim()) {
|
|
@@ -79138,7 +81425,7 @@ __export(exports_mcp_hooks_commands, {
|
|
|
79138
81425
|
});
|
|
79139
81426
|
import chalk10 from "chalk";
|
|
79140
81427
|
import { execSync as execSync3 } from "child_process";
|
|
79141
|
-
import { existsSync as existsSync26, readFileSync as
|
|
81428
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
|
|
79142
81429
|
import { dirname as dirname15, join as join27 } from "path";
|
|
79143
81430
|
function getMcpBinaryPath() {
|
|
79144
81431
|
try {
|
|
@@ -79155,7 +81442,7 @@ function readJsonFile2(path) {
|
|
|
79155
81442
|
if (!existsSync26(path))
|
|
79156
81443
|
return {};
|
|
79157
81444
|
try {
|
|
79158
|
-
return JSON.parse(
|
|
81445
|
+
return JSON.parse(readFileSync21(path, "utf-8"));
|
|
79159
81446
|
} catch {
|
|
79160
81447
|
return {};
|
|
79161
81448
|
}
|
|
@@ -79170,7 +81457,7 @@ function writeJsonFile2(path, data) {
|
|
|
79170
81457
|
function readTomlFile(path) {
|
|
79171
81458
|
if (!existsSync26(path))
|
|
79172
81459
|
return "";
|
|
79173
|
-
return
|
|
81460
|
+
return readFileSync21(path, "utf-8");
|
|
79174
81461
|
}
|
|
79175
81462
|
function writeTomlFile(path, content) {
|
|
79176
81463
|
const dir = dirname15(path);
|
|
@@ -80361,7 +82648,7 @@ Artifacts:`));
|
|
|
80361
82648
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
80362
82649
|
const marker = "# todos-auto-link";
|
|
80363
82650
|
if (existsSync26(hookPath)) {
|
|
80364
|
-
const existing =
|
|
82651
|
+
const existing = readFileSync21(hookPath, "utf-8");
|
|
80365
82652
|
if (existing.includes(marker)) {
|
|
80366
82653
|
console.log(chalk10.yellow("Hook already installed."));
|
|
80367
82654
|
return;
|
|
@@ -80391,7 +82678,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
80391
82678
|
console.log(chalk10.dim("No post-commit hook found."));
|
|
80392
82679
|
return;
|
|
80393
82680
|
}
|
|
80394
|
-
const content =
|
|
82681
|
+
const content = readFileSync21(hookPath, "utf-8");
|
|
80395
82682
|
if (!content.includes(marker)) {
|
|
80396
82683
|
console.log(chalk10.dim("Hook not managed by todos."));
|
|
80397
82684
|
return;
|
|
@@ -80574,7 +82861,7 @@ var init_dispatch3 = __esm(() => {
|
|
|
80574
82861
|
});
|
|
80575
82862
|
|
|
80576
82863
|
// src/lib/delegation-brief.ts
|
|
80577
|
-
import { createHash as
|
|
82864
|
+
import { createHash as createHash22 } from "crypto";
|
|
80578
82865
|
function resolveDelegationBrief(input, sources) {
|
|
80579
82866
|
const hasPath = typeof input.briefPath === "string" && input.briefPath.length > 0;
|
|
80580
82867
|
const hasText = typeof input.briefText === "string" && input.briefText.length > 0;
|
|
@@ -80634,7 +82921,7 @@ function resolveDelegationBrief(input, sources) {
|
|
|
80634
82921
|
ok: true,
|
|
80635
82922
|
text: text2,
|
|
80636
82923
|
source: source3,
|
|
80637
|
-
sha256:
|
|
82924
|
+
sha256: createHash22("sha256").update(text2, "utf8").digest("hex"),
|
|
80638
82925
|
bytes: Buffer.byteLength(text2, "utf8")
|
|
80639
82926
|
};
|
|
80640
82927
|
}
|
|
@@ -80642,7 +82929,7 @@ var STDIN_SENTINEL = "-";
|
|
|
80642
82929
|
var init_delegation_brief = () => {};
|
|
80643
82930
|
|
|
80644
82931
|
// src/lib/delegation-policy.ts
|
|
80645
|
-
import { readFileSync as
|
|
82932
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
80646
82933
|
import { homedir as homedir4 } from "os";
|
|
80647
82934
|
import { join as join28 } from "path";
|
|
80648
82935
|
function defaultDelegationEmbargoPath() {
|
|
@@ -80650,7 +82937,7 @@ function defaultDelegationEmbargoPath() {
|
|
|
80650
82937
|
}
|
|
80651
82938
|
function loadDelegationEmbargo(path = defaultDelegationEmbargoPath()) {
|
|
80652
82939
|
try {
|
|
80653
|
-
const parsed = JSON.parse(
|
|
82940
|
+
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
80654
82941
|
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.embargoed) ? parsed.embargoed : [];
|
|
80655
82942
|
const names = new Set;
|
|
80656
82943
|
for (const entry2 of entries) {
|
|
@@ -80733,15 +83020,15 @@ __export(exports_delegate, {
|
|
|
80733
83020
|
registerDelegateCommands: () => registerDelegateCommands
|
|
80734
83021
|
});
|
|
80735
83022
|
import chalk12 from "chalk";
|
|
80736
|
-
import { readFileSync as
|
|
83023
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
80737
83024
|
function registerDelegateCommands(program2) {
|
|
80738
83025
|
program2.command("delegate <task> <worker>").description("Hand a filed task to a worker in one call: brief, depth, lineage, assignment, record and notice").option("--brief <path>", "Path to the self-sufficient brief; `-` reads stdin").option("--brief-text <text>", "Inline brief, as an alternative to --brief").option("--depth-threshold <n>", "Open-task count above which this delegation parks").option("--despite-depth", "Proceed past an armed depth threshold; recorded in the [DISPATCH] comment").option("--owner-directive", "Mark as an owner-directive dispatch: depth warns and never parks").option("--seat <slug>", "Seat whose open count is read (default: the lineage parent)").option("--runtime <name>", "Worker runtime label recorded in the comment (e.g. claude-code-subagent)").option("--reports-to <agent>", "Lineage parent for the worker identity (default: the dispatcher)").option("--reuse-identity", "Skip registration and reuse an existing worker identity").option("--depth <n>", "Explicit delegation_depth (default: the task's current depth + 1)").option("--channel <name>", "Channel for the one-line notice (default: $TODOS_DELEGATE_NOTICE_CHANNEL)").option("--no-post", "Skip the channel notice").option("--claim-window <minutes>", `Minutes before the claim deadline (default: ${DEFAULT_CLAIM_WINDOW_MINUTES})`).option("--assign-seat", "Allow <worker> to name a durable seat (a seat queue has no session watching it)").option("--dry-run", "Report all seven effects and perform none").option("-j, --json", "Output as JSON").action(async (taskRef, workerInput, opts) => {
|
|
80739
83026
|
const globalOpts = program2.opts();
|
|
80740
83027
|
const useJson = Boolean(opts.json || globalOpts.json);
|
|
80741
83028
|
try {
|
|
80742
83029
|
const brief = resolveDelegationBrief({ briefPath: opts.brief, briefText: opts.briefText }, {
|
|
80743
|
-
readFile: (path) =>
|
|
80744
|
-
readStdin: () =>
|
|
83030
|
+
readFile: (path) => readFileSync23(path, "utf8"),
|
|
83031
|
+
readStdin: () => readFileSync23(0, "utf8")
|
|
80745
83032
|
});
|
|
80746
83033
|
if (!brief.ok)
|
|
80747
83034
|
handleError(new Error(brief.message));
|
|
@@ -80989,7 +83276,7 @@ __export(exports_machines, {
|
|
|
80989
83276
|
});
|
|
80990
83277
|
import chalk13 from "chalk";
|
|
80991
83278
|
import { execSync as execSync4 } from "child_process";
|
|
80992
|
-
import { readFileSync as
|
|
83279
|
+
import { readFileSync as readFileSync24, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
80993
83280
|
import { tmpdir as tmpdir5 } from "os";
|
|
80994
83281
|
import { join as join29 } from "path";
|
|
80995
83282
|
function getOrCreateLocalMachineName() {
|
|
@@ -81033,7 +83320,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
81033
83320
|
try {
|
|
81034
83321
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
81035
83322
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
81036
|
-
return JSON.parse(
|
|
83323
|
+
return JSON.parse(readFileSync24(localPath, "utf-8"));
|
|
81037
83324
|
} finally {
|
|
81038
83325
|
try {
|
|
81039
83326
|
runSsh(sshAddress, `rm -f ${shellQuote(remotePath)}`, 1e4);
|
|
@@ -85991,7 +88278,7 @@ var exports_roadmap_commands = {};
|
|
|
85991
88278
|
__export(exports_roadmap_commands, {
|
|
85992
88279
|
registerRoadmapCommands: () => registerRoadmapCommands
|
|
85993
88280
|
});
|
|
85994
|
-
import { readFileSync as
|
|
88281
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
|
|
85995
88282
|
import chalk24 from "chalk";
|
|
85996
88283
|
function splitList3(value) {
|
|
85997
88284
|
return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
@@ -86016,14 +88303,14 @@ function resolveMany(table, values) {
|
|
|
86016
88303
|
return resolved;
|
|
86017
88304
|
});
|
|
86018
88305
|
}
|
|
86019
|
-
function
|
|
88306
|
+
function globalOptions2(program2) {
|
|
86020
88307
|
const command = program2;
|
|
86021
88308
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86022
88309
|
}
|
|
86023
88310
|
function registerRoadmapCommands(program2) {
|
|
86024
88311
|
const roadmaps = program2.command("roadmaps").alias("roadmap").description("Manage local roadmaps, milestones, and release groupings");
|
|
86025
88312
|
roadmaps.command("create <name>").description("Create a local roadmap").option("--description <text>", "Description").option("--project <id>", "Project ID").option("--status <status>", "planned, active, completed, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--release <name>", "Default release label").action(async (name, opts) => {
|
|
86026
|
-
const globalOpts =
|
|
88313
|
+
const globalOpts = globalOptions2(program2);
|
|
86027
88314
|
try {
|
|
86028
88315
|
const { createRoadmap: createRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86029
88316
|
const roadmap = createRoadmap2({
|
|
@@ -86045,7 +88332,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86045
88332
|
}
|
|
86046
88333
|
});
|
|
86047
88334
|
roadmaps.command("list").description("List local roadmaps").option("--project <id>", "Project ID").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
86048
|
-
const globalOpts =
|
|
88335
|
+
const globalOpts = globalOptions2(program2);
|
|
86049
88336
|
try {
|
|
86050
88337
|
const { listRoadmaps: listRoadmaps2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86051
88338
|
const items = listRoadmaps2({ project_id: resolveOptional("projects", opts.project || globalOpts.project), status: opts.status });
|
|
@@ -86064,7 +88351,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86064
88351
|
}
|
|
86065
88352
|
});
|
|
86066
88353
|
roadmaps.command("show <roadmap>").description("Show a roadmap summary").option("--format <format>", "json or markdown", "json").action(async (roadmap, opts) => {
|
|
86067
|
-
const globalOpts =
|
|
88354
|
+
const globalOpts = globalOptions2(program2);
|
|
86068
88355
|
try {
|
|
86069
88356
|
const { renderRoadmapMarkdown: renderRoadmapMarkdown2, summarizeRoadmap: summarizeRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86070
88357
|
if (opts.format === "markdown") {
|
|
@@ -86082,7 +88369,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86082
88369
|
}
|
|
86083
88370
|
});
|
|
86084
88371
|
roadmaps.command("update <roadmap>").description("Update a local roadmap").option("--name <name>", "New name").option("--description <text>", "Description").option("--project <id>", "Project ID").option("--status <status>", "planned, active, completed, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--release <name>", "Release label").action(async (roadmap, opts) => {
|
|
86085
|
-
const globalOpts =
|
|
88372
|
+
const globalOpts = globalOptions2(program2);
|
|
86086
88373
|
try {
|
|
86087
88374
|
const { updateRoadmap: updateRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86088
88375
|
const updated = updateRoadmap2(roadmap, {
|
|
@@ -86104,7 +88391,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86104
88391
|
}
|
|
86105
88392
|
});
|
|
86106
88393
|
roadmaps.command("delete <roadmap>").description("Delete a local roadmap and its local milestone/release config").action(async (roadmap) => {
|
|
86107
|
-
const globalOpts =
|
|
88394
|
+
const globalOpts = globalOptions2(program2);
|
|
86108
88395
|
try {
|
|
86109
88396
|
const { deleteRoadmap: deleteRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86110
88397
|
const deleted = deleteRoadmap2(roadmap);
|
|
@@ -86119,7 +88406,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86119
88406
|
});
|
|
86120
88407
|
const milestones = roadmaps.command("milestones").description("Manage roadmap milestones");
|
|
86121
88408
|
milestones.command("add <roadmap> <title>").description("Add a milestone to a roadmap").option("--description <text>", "Description").option("--due <iso>", "Due date or timestamp").option("--status <status>", "planned, active, completed, blocked, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--release <name>", "Release label").option("--tags <list>", "Comma-separated tags").action(async (roadmap, title, opts) => {
|
|
86122
|
-
const globalOpts =
|
|
88409
|
+
const globalOpts = globalOptions2(program2);
|
|
86123
88410
|
try {
|
|
86124
88411
|
const { createMilestone: createMilestone2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86125
88412
|
const milestone = createMilestone2({
|
|
@@ -86146,7 +88433,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86146
88433
|
}
|
|
86147
88434
|
});
|
|
86148
88435
|
milestones.command("update <milestone>").description("Update a roadmap milestone").option("--title <title>", "Title").option("--description <text>", "Description").option("--due <iso>", "Due date or timestamp").option("--status <status>", "planned, active, completed, blocked, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--release <name>", "Release label").option("--tags <list>", "Comma-separated tags").action(async (milestone, opts) => {
|
|
86149
|
-
const globalOpts =
|
|
88436
|
+
const globalOpts = globalOptions2(program2);
|
|
86150
88437
|
try {
|
|
86151
88438
|
const { updateMilestone: updateMilestone2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86152
88439
|
const updated = updateMilestone2(milestone, {
|
|
@@ -86173,7 +88460,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86173
88460
|
});
|
|
86174
88461
|
const releases = roadmaps.command("releases").description("Manage roadmap release groups");
|
|
86175
88462
|
releases.command("set <roadmap> <name>").description("Create or update a release grouping").option("--release-version <version>", "Version label").option("--status <status>", "planned, active, completed, blocked, archived").option("--milestones <list>", "Comma-separated milestone IDs").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--notes <text>", "Release notes").action(async (roadmap, name, opts) => {
|
|
86176
|
-
const globalOpts =
|
|
88463
|
+
const globalOpts = globalOptions2(program2);
|
|
86177
88464
|
try {
|
|
86178
88465
|
const { upsertReleaseGroup: upsertReleaseGroup2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86179
88466
|
const release = upsertReleaseGroup2({
|
|
@@ -86197,7 +88484,7 @@ function registerRoadmapCommands(program2) {
|
|
|
86197
88484
|
}
|
|
86198
88485
|
});
|
|
86199
88486
|
roadmaps.command("export <roadmap>").description("Export a roadmap as JSON bundle or Markdown").option("--format <format>", "json or markdown", "json").option("--out <path>", "Write output to a file").action(async (roadmap, opts) => {
|
|
86200
|
-
const globalOpts =
|
|
88487
|
+
const globalOpts = globalOptions2(program2);
|
|
86201
88488
|
try {
|
|
86202
88489
|
const { exportRoadmapBundle: exportRoadmapBundle2, renderRoadmapMarkdown: renderRoadmapMarkdown2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86203
88490
|
const content = opts.format === "markdown" ? renderRoadmapMarkdown2(roadmap) : JSON.stringify(exportRoadmapBundle2(roadmap), null, 2);
|
|
@@ -86217,10 +88504,10 @@ function registerRoadmapCommands(program2) {
|
|
|
86217
88504
|
}
|
|
86218
88505
|
});
|
|
86219
88506
|
roadmaps.command("import <path>").description("Preview or apply a roadmap JSON bundle").option("--apply", "Apply the import").action(async (path, opts) => {
|
|
86220
|
-
const globalOpts =
|
|
88507
|
+
const globalOpts = globalOptions2(program2);
|
|
86221
88508
|
try {
|
|
86222
88509
|
const { importRoadmapBundle: importRoadmapBundle2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
86223
|
-
const bundle = JSON.parse(
|
|
88510
|
+
const bundle = JSON.parse(readFileSync25(path, "utf8"));
|
|
86224
88511
|
const result = importRoadmapBundle2(bundle, { apply: Boolean(opts.apply) });
|
|
86225
88512
|
if (globalOpts.json) {
|
|
86226
88513
|
output(result, true);
|
|
@@ -86254,14 +88541,14 @@ function resolveOptional2(table, value) {
|
|
|
86254
88541
|
throw new Error(`Could not resolve ${table} ID: ${value}`);
|
|
86255
88542
|
return resolved;
|
|
86256
88543
|
}
|
|
86257
|
-
function
|
|
88544
|
+
function globalOptions3(program2) {
|
|
86258
88545
|
const command = program2;
|
|
86259
88546
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86260
88547
|
}
|
|
86261
88548
|
function registerCapacityCommands(program2) {
|
|
86262
88549
|
const capacity = program2.command("capacity").description("Manage local capacity profiles and planning forecasts");
|
|
86263
88550
|
capacity.command("set <agent>").description("Create or update a local agent capacity profile").requiredOption("--minutes-per-day <minutes>", "Available minutes per working day").option("--project <id>", "Project ID").option("--days <list>", "Working days as 0-6, where 0 is Sunday", "1,2,3,4,5").option("--from <date>", "Effective date").action(async (agent, opts) => {
|
|
86264
|
-
const globalOpts =
|
|
88551
|
+
const globalOpts = globalOptions3(program2);
|
|
86265
88552
|
try {
|
|
86266
88553
|
const { upsertCapacityProfile: upsertCapacityProfile2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
|
|
86267
88554
|
const profile = upsertCapacityProfile2({
|
|
@@ -86281,7 +88568,7 @@ function registerCapacityCommands(program2) {
|
|
|
86281
88568
|
}
|
|
86282
88569
|
});
|
|
86283
88570
|
capacity.command("list").description("List local capacity profiles").option("--agent <id>", "Filter by agent").option("--project <id>", "Filter by project").action(async (opts) => {
|
|
86284
|
-
const globalOpts =
|
|
88571
|
+
const globalOpts = globalOptions3(program2);
|
|
86285
88572
|
try {
|
|
86286
88573
|
const { listCapacityProfiles: listCapacityProfiles2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
|
|
86287
88574
|
const profiles = listCapacityProfiles2({
|
|
@@ -86304,7 +88591,7 @@ function registerCapacityCommands(program2) {
|
|
|
86304
88591
|
}
|
|
86305
88592
|
});
|
|
86306
88593
|
capacity.command("remove <agent-or-id>").description("Remove a local capacity profile").option("--project <id>", "Project ID for agent-scoped removal").action(async (agentOrId, opts) => {
|
|
86307
|
-
const globalOpts =
|
|
88594
|
+
const globalOpts = globalOptions3(program2);
|
|
86308
88595
|
try {
|
|
86309
88596
|
const { removeCapacityProfile: removeCapacityProfile2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
|
|
86310
88597
|
const removed = removeCapacityProfile2(agentOrId, opts.project ? resolveOptional2("projects", opts.project) : undefined);
|
|
@@ -86318,7 +88605,7 @@ function registerCapacityCommands(program2) {
|
|
|
86318
88605
|
}
|
|
86319
88606
|
});
|
|
86320
88607
|
capacity.command("forecast").description("Forecast local plan or project completion from estimates and capacity").option("--project <id>", "Project ID").option("--plan <id>", "Plan ID").option("--agent <id>", "Agent filter").option("--start-date <date>", "Forecast start date").option("--format <format>", "json or markdown", "json").action(async (opts) => {
|
|
86321
|
-
const globalOpts =
|
|
88608
|
+
const globalOpts = globalOptions3(program2);
|
|
86322
88609
|
try {
|
|
86323
88610
|
const { getPlanningForecast: getPlanningForecast2, renderPlanningForecastMarkdown: renderPlanningForecastMarkdown2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
|
|
86324
88611
|
const forecast = getPlanningForecast2({
|
|
@@ -86352,7 +88639,7 @@ __export(exports_audit_ledger_commands, {
|
|
|
86352
88639
|
registerAuditLedgerCommands: () => registerAuditLedgerCommands
|
|
86353
88640
|
});
|
|
86354
88641
|
import chalk26 from "chalk";
|
|
86355
|
-
function
|
|
88642
|
+
function globalOptions4(program2) {
|
|
86356
88643
|
const command = program2;
|
|
86357
88644
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86358
88645
|
}
|
|
@@ -86369,7 +88656,7 @@ function resolveOptional3(table, value) {
|
|
|
86369
88656
|
function registerAuditLedgerCommands(program2) {
|
|
86370
88657
|
const audit = program2.command("audit-ledger").description("Create and verify tamper-evident local audit ledger checkpoints");
|
|
86371
88658
|
audit.command("show").description("Build a local audit hash chain from current evidence").option("--project <id>", "Project ID").option("--task <id>", "Task ID").option("--run <id>", "Run ID").option("--entries", "Include per-entry hashes and redacted payloads").option("--format <format>", "json or markdown", "json").action(async (opts) => {
|
|
86372
|
-
const globalOpts =
|
|
88659
|
+
const globalOpts = globalOptions4(program2);
|
|
86373
88660
|
try {
|
|
86374
88661
|
const { getLocalAuditLedger: getLocalAuditLedger2, renderLocalAuditLedgerMarkdown: renderLocalAuditLedgerMarkdown2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
|
|
86375
88662
|
const ledger2 = getLocalAuditLedger2({
|
|
@@ -86392,7 +88679,7 @@ function registerAuditLedgerCommands(program2) {
|
|
|
86392
88679
|
}
|
|
86393
88680
|
});
|
|
86394
88681
|
audit.command("seal <name>").description("Store a local audit ledger checkpoint for later verification").option("--project <id>", "Project ID").option("--task <id>", "Task ID").option("--run <id>", "Run ID").option("--note <text>", "Checkpoint note").action(async (name, opts) => {
|
|
86395
|
-
const globalOpts =
|
|
88682
|
+
const globalOpts = globalOptions4(program2);
|
|
86396
88683
|
try {
|
|
86397
88684
|
const { sealLocalAuditLedger: sealLocalAuditLedger2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
|
|
86398
88685
|
const checkpoint = sealLocalAuditLedger2({
|
|
@@ -86413,7 +88700,7 @@ function registerAuditLedgerCommands(program2) {
|
|
|
86413
88700
|
}
|
|
86414
88701
|
});
|
|
86415
88702
|
audit.command("list").description("List local audit ledger checkpoints").action(async () => {
|
|
86416
|
-
const globalOpts =
|
|
88703
|
+
const globalOpts = globalOptions4(program2);
|
|
86417
88704
|
try {
|
|
86418
88705
|
const { listLocalAuditLedgerCheckpoints: listLocalAuditLedgerCheckpoints2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
|
|
86419
88706
|
const checkpoints = listLocalAuditLedgerCheckpoints2();
|
|
@@ -86433,7 +88720,7 @@ function registerAuditLedgerCommands(program2) {
|
|
|
86433
88720
|
}
|
|
86434
88721
|
});
|
|
86435
88722
|
audit.command("verify <checkpoint>").description("Verify current local evidence against a sealed checkpoint").option("--format <format>", "json or markdown", "json").action(async (checkpoint, opts) => {
|
|
86436
|
-
const globalOpts =
|
|
88723
|
+
const globalOpts = globalOptions4(program2);
|
|
86437
88724
|
try {
|
|
86438
88725
|
const { renderLocalAuditLedgerMarkdown: renderLocalAuditLedgerMarkdown2, verifyLocalAuditLedger: verifyLocalAuditLedger2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
|
|
86439
88726
|
const result = verifyLocalAuditLedger2(checkpoint);
|
|
@@ -86465,7 +88752,7 @@ __export(exports_release_compatibility_commands, {
|
|
|
86465
88752
|
registerReleaseCompatibilityCommands: () => registerReleaseCompatibilityCommands
|
|
86466
88753
|
});
|
|
86467
88754
|
import chalk27 from "chalk";
|
|
86468
|
-
function
|
|
88755
|
+
function globalOptions5(program2) {
|
|
86469
88756
|
const command = program2;
|
|
86470
88757
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86471
88758
|
}
|
|
@@ -86477,7 +88764,7 @@ function parseLevels(value) {
|
|
|
86477
88764
|
function registerReleaseCompatibilityCommands(program2) {
|
|
86478
88765
|
const releaseCompat = program2.command("release-compat").description("Check local release compatibility, migrations, exports, and Bun install guidance");
|
|
86479
88766
|
releaseCompat.command("check").description("Build a local release compatibility report").option("--root <path>", "Package root", process.cwd()).option("--levels <csv>", "Comma-separated migration levels to simulate").option("--format <format>", "json or markdown", "json").action(async (opts) => {
|
|
86480
|
-
const globalOpts =
|
|
88767
|
+
const globalOpts = globalOptions5(program2);
|
|
86481
88768
|
try {
|
|
86482
88769
|
const { createReleaseCompatibilityReport: createReleaseCompatibilityReport2, renderReleaseCompatibilityMarkdown: renderReleaseCompatibilityMarkdown2 } = await Promise.resolve().then(() => (init_release_compatibility(), exports_release_compatibility));
|
|
86483
88770
|
const report = createReleaseCompatibilityReport2({
|
|
@@ -86513,7 +88800,7 @@ var exports_usage_ledger_commands = {};
|
|
|
86513
88800
|
__export(exports_usage_ledger_commands, {
|
|
86514
88801
|
registerUsageLedgerCommands: () => registerUsageLedgerCommands
|
|
86515
88802
|
});
|
|
86516
|
-
function
|
|
88803
|
+
function globalOptions6(program2) {
|
|
86517
88804
|
const command = program2;
|
|
86518
88805
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86519
88806
|
}
|
|
@@ -86539,7 +88826,7 @@ function registerUsageLedgerCommands(program2) {
|
|
|
86539
88826
|
const usage = program2.command("usage").description("Report local task, run, command, cost, duration, storage, and quota usage");
|
|
86540
88827
|
usage.command("report").description("Build an aggregate local usage ledger").option("--project <id>", "Filter by project").option("--agent <name>", "Filter by agent").option("--since <iso>", "Only include records created or started at or after this timestamp").option("--until <iso>", "Only include records created or started at or before this timestamp").option("--max-tasks <n>", "Simulate a task quota").option("--max-projects <n>", "Simulate a project quota").option("--max-runs <n>", "Simulate a run quota").option("--max-commands <n>", "Simulate a command quota").option("--max-tokens <n>", "Simulate a token quota").option("--max-cost-usd <n>", "Simulate a USD cost quota").option("--max-storage-bytes <n>", "Simulate an evidence storage quota").option("--format <format>", "json or markdown", "json").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
86541
88828
|
try {
|
|
86542
|
-
const globalOpts =
|
|
88829
|
+
const globalOpts = globalOptions6(program2);
|
|
86543
88830
|
const { createLocalUsageLedger: createLocalUsageLedger2, renderLocalUsageLedgerMarkdown: renderLocalUsageLedgerMarkdown2 } = await Promise.resolve().then(() => (init_usage_ledger(), exports_usage_ledger));
|
|
86544
88831
|
const report = createLocalUsageLedger2({
|
|
86545
88832
|
project_id: resolveProjectInput(opts.project || globalOpts.project),
|
|
@@ -86581,7 +88868,7 @@ __export(exports_local_backup_commands, {
|
|
|
86581
88868
|
});
|
|
86582
88869
|
import chalk28 from "chalk";
|
|
86583
88870
|
import { resolve as resolve25 } from "path";
|
|
86584
|
-
function
|
|
88871
|
+
function globalOptions7(program2) {
|
|
86585
88872
|
const command = program2;
|
|
86586
88873
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
86587
88874
|
}
|
|
@@ -86597,7 +88884,7 @@ function registerLocalBackupCommands(program2) {
|
|
|
86597
88884
|
const backup = program2.command("backup").description("Create, verify, restore, and inspect local backup bundles");
|
|
86598
88885
|
backup.command("create").description("Create a local backup bundle with a manifest and checksums").option("-o, --output <path>", "Write backup JSON to a file").option("--project-id <id>", "Project id to scope the backup. Defaults to auto-detected project when available.").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
86599
88886
|
try {
|
|
86600
|
-
const globalOpts =
|
|
88887
|
+
const globalOpts = globalOptions7(program2);
|
|
86601
88888
|
const { createLocalBackup: createLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
|
|
86602
88889
|
const projectId = opts.projectId ?? autoProject(globalOpts);
|
|
86603
88890
|
const backupBundle = createLocalBackup2({
|
|
@@ -86619,7 +88906,7 @@ function registerLocalBackupCommands(program2) {
|
|
|
86619
88906
|
});
|
|
86620
88907
|
backup.command("verify <file>").description("Verify a local backup bundle checksum, manifest, bridge schema, and current SQLite integrity").option("-j, --json", "Output as JSON").action(async (file, opts) => {
|
|
86621
88908
|
try {
|
|
86622
|
-
const globalOpts =
|
|
88909
|
+
const globalOpts = globalOptions7(program2);
|
|
86623
88910
|
const { readLocalBackupFile: readLocalBackupFile2, verifyLocalBackup: verifyLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
|
|
86624
88911
|
const verification2 = verifyLocalBackup2(readLocalBackupFile2(file));
|
|
86625
88912
|
if (opts.json || globalOpts.json) {
|
|
@@ -86639,7 +88926,7 @@ function registerLocalBackupCommands(program2) {
|
|
|
86639
88926
|
});
|
|
86640
88927
|
backup.command("restore <file>").description("Dry-run or apply a local backup restore. Dry-run is the default.").option("--apply", "Apply the restore. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").option("-j, --json", "Output as JSON").action(async (file, opts) => {
|
|
86641
88928
|
try {
|
|
86642
|
-
const globalOpts =
|
|
88929
|
+
const globalOpts = globalOptions7(program2);
|
|
86643
88930
|
const { readLocalBackupFile: readLocalBackupFile2, restoreLocalBackup: restoreLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
|
|
86644
88931
|
const result = restoreLocalBackup2(readLocalBackupFile2(file), {
|
|
86645
88932
|
apply: Boolean(opts.apply),
|
|
@@ -86667,7 +88954,7 @@ function registerLocalBackupCommands(program2) {
|
|
|
86667
88954
|
});
|
|
86668
88955
|
backup.command("integrity").description("Check local SQLite, bridge, count, and orphan-row integrity").option("--project-id <id>", "Optional project id to scope bridge counts").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
86669
88956
|
try {
|
|
86670
|
-
const globalOpts =
|
|
88957
|
+
const globalOpts = globalOptions7(program2);
|
|
86671
88958
|
const { checkLocalIntegrity: checkLocalIntegrity2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
|
|
86672
88959
|
const report = checkLocalIntegrity2({
|
|
86673
88960
|
project_id: opts.projectId ?? autoProject(globalOpts)
|
|
@@ -87186,7 +89473,7 @@ var init_hybrid = __esm(() => {
|
|
|
87186
89473
|
});
|
|
87187
89474
|
|
|
87188
89475
|
// src/storage/s3-artifacts.ts
|
|
87189
|
-
import { createHash as
|
|
89476
|
+
import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
|
|
87190
89477
|
function createTodosS3ArtifactStore(options) {
|
|
87191
89478
|
const requestFetch = options.fetch ?? fetch;
|
|
87192
89479
|
const now4 = options.now ?? (() => new Date);
|
|
@@ -87358,7 +89645,7 @@ function toAmzDate(date) {
|
|
|
87358
89645
|
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
87359
89646
|
}
|
|
87360
89647
|
function sha256Hex(value) {
|
|
87361
|
-
return
|
|
89648
|
+
return createHash23("sha256").update(value).digest("hex");
|
|
87362
89649
|
}
|
|
87363
89650
|
function hmac(key2, value) {
|
|
87364
89651
|
return createHmac2("sha256", key2).update(value).digest();
|
|
@@ -87966,7 +90253,7 @@ __export(exports_storage_commands, {
|
|
|
87966
90253
|
registerStorageCommands: () => registerStorageCommands
|
|
87967
90254
|
});
|
|
87968
90255
|
import chalk29 from "chalk";
|
|
87969
|
-
function
|
|
90256
|
+
function globalOptions8(program2) {
|
|
87970
90257
|
const command = program2;
|
|
87971
90258
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
87972
90259
|
}
|
|
@@ -88102,7 +90389,7 @@ function registerStorageCommands(program2) {
|
|
|
88102
90389
|
const storage = program2.command("storage").description("Inspect explicit native local and remote storage configuration");
|
|
88103
90390
|
storage.command("status").description("Show redacted native storage configuration without opening network connections").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88104
90391
|
try {
|
|
88105
|
-
const globalOpts =
|
|
90392
|
+
const globalOpts = globalOptions8(program2);
|
|
88106
90393
|
const { getNativeStorageStatus: getNativeStorageStatus2 } = await Promise.resolve().then(() => (init_native_storage_status(), exports_native_storage_status));
|
|
88107
90394
|
const nativeStatus = getNativeStorageStatus2();
|
|
88108
90395
|
const remoteAuthority = getTodosRemoteAuthorityConfigStatus();
|
|
@@ -88160,7 +90447,7 @@ function registerStorageCommands(program2) {
|
|
|
88160
90447
|
});
|
|
88161
90448
|
storage.command("sync-plan").description("Preview native storage sync work without opening network connections").option("--schema-sql", "Include Postgres schema SQL in the dry-run output").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88162
90449
|
try {
|
|
88163
|
-
const globalOpts =
|
|
90450
|
+
const globalOpts = globalOptions8(program2);
|
|
88164
90451
|
const { getNativeStorageSyncPlan: getNativeStorageSyncPlan2 } = await Promise.resolve().then(() => (init_native_storage_status(), exports_native_storage_status));
|
|
88165
90452
|
const plan = getNativeStorageSyncPlan2(process.env, {
|
|
88166
90453
|
includeSchemaSql: Boolean(opts.schemaSql)
|
|
@@ -88177,7 +90464,7 @@ function registerStorageCommands(program2) {
|
|
|
88177
90464
|
storage.command("shadow-status").description("Report dual-write shadow divergence: local vs cloud row counts and last mirror lag (opens a read-only DB connection)").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88178
90465
|
let cloud = null;
|
|
88179
90466
|
try {
|
|
88180
|
-
const globalOpts =
|
|
90467
|
+
const globalOpts = globalOptions8(program2);
|
|
88181
90468
|
const asJson = Boolean(opts.json || globalOpts.json);
|
|
88182
90469
|
const {
|
|
88183
90470
|
createTodosCloudQueryClientFromEnv: createTodosCloudQueryClientFromEnv2,
|
|
@@ -88217,7 +90504,7 @@ function registerStorageCommands(program2) {
|
|
|
88217
90504
|
});
|
|
88218
90505
|
storage.command("shadow-drain").description("Drain the durable dual-write shadow outbox to cloud Postgres (one-way, write-only)").option("-j, --json", "Output as JSON").option("--timeout <ms>", "Max drain time in milliseconds", "30000").action(async (opts) => {
|
|
88219
90506
|
try {
|
|
88220
|
-
const globalOpts =
|
|
90507
|
+
const globalOpts = globalOptions8(program2);
|
|
88221
90508
|
const asJson = Boolean(opts.json || globalOpts.json);
|
|
88222
90509
|
const {
|
|
88223
90510
|
isTodosShadowEnabled: isTodosShadowEnabled2,
|
|
@@ -88259,7 +90546,7 @@ function registerStorageCommands(program2) {
|
|
|
88259
90546
|
const artifacts = storage.command("artifacts").description("Preview or apply native S3 sync for locally stored run artifacts");
|
|
88260
90547
|
artifacts.command("upload").description("Upload locally stored run artifact bytes to configured S3. Dry-run by default.").option("--run-id <id>", "Limit to one run id").option("--task-id <id>", "Limit to one task id").option("--limit <n>", "Maximum artifacts to scan").option("--include-already-synced", "Include artifacts that already have a remote reference").option("--apply", "Perform S3 uploads. Defaults to dry-run.").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88261
90548
|
try {
|
|
88262
|
-
const globalOpts =
|
|
90549
|
+
const globalOpts = globalOptions8(program2);
|
|
88263
90550
|
const { planRunArtifactsS3Sync: planRunArtifactsS3Sync2, uploadRunArtifactsToS3: uploadRunArtifactsToS32 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
|
|
88264
90551
|
const filter = artifactFilter(opts);
|
|
88265
90552
|
if (!opts.apply) {
|
|
@@ -88283,7 +90570,7 @@ function registerStorageCommands(program2) {
|
|
|
88283
90570
|
});
|
|
88284
90571
|
artifacts.command("download").description("Restore locally stored run artifact bytes from configured S3. Dry-run by default.").option("--run-id <id>", "Limit to one run id").option("--task-id <id>", "Limit to one task id").option("--limit <n>", "Maximum artifacts to scan").option("--force", "Download even when local stored content already verifies").option("--apply", "Perform S3 downloads. Defaults to dry-run.").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88285
90572
|
try {
|
|
88286
|
-
const globalOpts =
|
|
90573
|
+
const globalOpts = globalOptions8(program2);
|
|
88287
90574
|
const { downloadRunArtifactsFromS3: downloadRunArtifactsFromS32, planRunArtifactsS3Sync: planRunArtifactsS3Sync2 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
|
|
88288
90575
|
const filter = artifactFilter(opts);
|
|
88289
90576
|
if (!opts.apply) {
|
|
@@ -88550,7 +90837,7 @@ __export(exports_scale_hardening_commands, {
|
|
|
88550
90837
|
registerScaleHardeningCommands: () => registerScaleHardeningCommands
|
|
88551
90838
|
});
|
|
88552
90839
|
import chalk30 from "chalk";
|
|
88553
|
-
function
|
|
90840
|
+
function globalOptions9(program2) {
|
|
88554
90841
|
const command = program2;
|
|
88555
90842
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
88556
90843
|
}
|
|
@@ -88567,7 +90854,7 @@ function registerScaleHardeningCommands(program2) {
|
|
|
88567
90854
|
const scale = program2.command("scale").description("Benchmark local performance, archive readiness, compaction, and SQLite integrity");
|
|
88568
90855
|
scale.command("report").description("Build a local scale hardening report without network access").option("--older-than-days <days>", "Archive-readiness window for terminal tasks", "30").option("--format <format>", "json or markdown", "markdown").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88569
90856
|
try {
|
|
88570
|
-
const globalOpts =
|
|
90857
|
+
const globalOpts = globalOptions9(program2);
|
|
88571
90858
|
const { createScalePerformanceReport: createScalePerformanceReport2, renderScalePerformanceReportMarkdown: renderScalePerformanceReportMarkdown2 } = await Promise.resolve().then(() => (init_scale_hardening(), exports_scale_hardening));
|
|
88572
90859
|
const report = createScalePerformanceReport2({
|
|
88573
90860
|
older_than_days: parsePositiveInteger2(opts.olderThanDays, 30)
|
|
@@ -88586,7 +90873,7 @@ function registerScaleHardeningCommands(program2) {
|
|
|
88586
90873
|
});
|
|
88587
90874
|
scale.command("compact").description("Preview or apply local SQLite optimization and VACUUM compaction").option("--apply", "Run PRAGMA optimize and VACUUM; dry-run by default").option("--format <format>", "json or markdown", "json").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
88588
90875
|
try {
|
|
88589
|
-
const globalOpts =
|
|
90876
|
+
const globalOpts = globalOptions9(program2);
|
|
88590
90877
|
const { compactScaleStorage: compactScaleStorage2 } = await Promise.resolve().then(() => (init_scale_hardening(), exports_scale_hardening));
|
|
88591
90878
|
const result = compactScaleStorage2({ apply: Boolean(opts.apply) });
|
|
88592
90879
|
const format = opts.json || globalOpts.json ? "json" : opts.format || "json";
|
|
@@ -88617,7 +90904,7 @@ __export(exports_pr_group_commands, {
|
|
|
88617
90904
|
registerPrGroupCommands: () => registerPrGroupCommands
|
|
88618
90905
|
});
|
|
88619
90906
|
import chalk31 from "chalk";
|
|
88620
|
-
function
|
|
90907
|
+
function globalOptions10(program2) {
|
|
88621
90908
|
const command = program2;
|
|
88622
90909
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
88623
90910
|
}
|
|
@@ -88639,7 +90926,7 @@ function registerPrGroupCommands(program2) {
|
|
|
88639
90926
|
try {
|
|
88640
90927
|
const remote = getTodosCloudClient();
|
|
88641
90928
|
const view = remote ? await cloudGetPrGroup(remote, groupId) : await createLocalPrGroupLedger(getDatabase()).get(groupId);
|
|
88642
|
-
if (opts.json ||
|
|
90929
|
+
if (opts.json || globalOptions10(program2)["json"]) {
|
|
88643
90930
|
output(view, true);
|
|
88644
90931
|
return;
|
|
88645
90932
|
}
|
|
@@ -88657,7 +90944,7 @@ function registerPrGroupCommands(program2) {
|
|
|
88657
90944
|
};
|
|
88658
90945
|
const remote = getTodosCloudClient();
|
|
88659
90946
|
const history = remote ? await cloudPrGroupEvents(remote, groupId, options) : await createLocalPrGroupLedger(getDatabase()).events(groupId, options);
|
|
88660
|
-
if (opts.json ||
|
|
90947
|
+
if (opts.json || globalOptions10(program2)["json"]) {
|
|
88661
90948
|
output(history, true);
|
|
88662
90949
|
return;
|
|
88663
90950
|
}
|
|
@@ -88940,7 +91227,7 @@ var exports_help_commands = {};
|
|
|
88940
91227
|
__export(exports_help_commands, {
|
|
88941
91228
|
registerHelpCommands: () => registerHelpCommands
|
|
88942
91229
|
});
|
|
88943
|
-
function
|
|
91230
|
+
function globalOptions11(program2) {
|
|
88944
91231
|
const command = program2;
|
|
88945
91232
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
88946
91233
|
}
|
|
@@ -88949,19 +91236,19 @@ function parseShell(value) {
|
|
|
88949
91236
|
return value;
|
|
88950
91237
|
throw new Error(`Unsupported shell: ${value}. Expected one of: ${COMPLETION_SHELLS.join(", ")}`);
|
|
88951
91238
|
}
|
|
88952
|
-
function registerHelpCommands(program2, route = "local") {
|
|
91239
|
+
function registerHelpCommands(program2, route = "local", remoteCapabilities = new Set) {
|
|
88953
91240
|
program2.command("completions").alias("completion").description("Generate shell completions for bash, zsh, or fish").argument("<shell>", "Shell to generate: bash, zsh, or fish").action((shell) => {
|
|
88954
91241
|
try {
|
|
88955
|
-
console.log(generateCompletionScript(program2, parseShell(shell), (command) => isTodosCliCommandVisibleForRoute(command, route)));
|
|
91242
|
+
console.log(generateCompletionScript(program2, parseShell(shell), (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities)));
|
|
88956
91243
|
} catch (error2) {
|
|
88957
91244
|
handleError(error2);
|
|
88958
91245
|
}
|
|
88959
91246
|
});
|
|
88960
91247
|
program2.command("manual").description("Print the complete local CLI manual").option("--format <format>", "markdown or json", "markdown").option("-j, --json", "Output as JSON").action((opts) => {
|
|
88961
91248
|
try {
|
|
88962
|
-
const globalOpts =
|
|
91249
|
+
const globalOpts = globalOptions11(program2);
|
|
88963
91250
|
const manual = createCliManual(program2, {
|
|
88964
|
-
isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route),
|
|
91251
|
+
isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities),
|
|
88965
91252
|
localOnly: route === "local"
|
|
88966
91253
|
});
|
|
88967
91254
|
const format = opts.json || globalOpts.json ? "json" : opts.format || "markdown";
|
|
@@ -88987,6 +91274,7 @@ var init_help_commands = __esm(() => {
|
|
|
88987
91274
|
init_esm();
|
|
88988
91275
|
init_package_version();
|
|
88989
91276
|
init_stage_a();
|
|
91277
|
+
init_cloud_router();
|
|
88990
91278
|
var program2 = new Command;
|
|
88991
91279
|
function fallbackJsonRequested() {
|
|
88992
91280
|
return program2.opts().json === true || process.argv.includes("--json");
|
|
@@ -89049,12 +91337,25 @@ try {
|
|
|
89049
91337
|
console.error(error2 instanceof Error ? error2.message : String(error2));
|
|
89050
91338
|
process.exit(1);
|
|
89051
91339
|
}
|
|
91340
|
+
var remoteCommandCapabilities = new Set;
|
|
91341
|
+
var metadataRequested = authority.route === "remote-diagnostic";
|
|
91342
|
+
if (authority.route !== "local" && metadataRequested) {
|
|
91343
|
+
try {
|
|
91344
|
+
const client = getTodosCloudClient();
|
|
91345
|
+
if (client) {
|
|
91346
|
+
remoteCommandCapabilities = await getTodosRemoteCommandCapabilities(client);
|
|
91347
|
+
}
|
|
91348
|
+
} catch {
|
|
91349
|
+
remoteCommandCapabilities = new Set;
|
|
91350
|
+
}
|
|
91351
|
+
}
|
|
89052
91352
|
var [
|
|
89053
91353
|
{ handleError: handleError2 },
|
|
89054
91354
|
{ registerTaskCommands: registerTaskCommands2 },
|
|
89055
91355
|
{ registerPlanTemplateCommands: registerPlanTemplateCommands2 },
|
|
89056
91356
|
{ registerProjectCommands: registerProjectCommands2 },
|
|
89057
91357
|
{ registerAgentCommands: registerAgentCommands2 },
|
|
91358
|
+
{ registerAiCommands: registerAiCommands2 },
|
|
89058
91359
|
{ registerConfigServeCommands: registerConfigServeCommands2 },
|
|
89059
91360
|
{ registerQueryCommands: registerQueryCommands2 },
|
|
89060
91361
|
{ registerMcpHooksCommands: registerMcpHooksCommands2 },
|
|
@@ -89087,6 +91388,7 @@ var [
|
|
|
89087
91388
|
Promise.resolve().then(() => (init_plan_template_commands(), exports_plan_template_commands)),
|
|
89088
91389
|
Promise.resolve().then(() => (init_project_commands(), exports_project_commands)),
|
|
89089
91390
|
Promise.resolve().then(() => (init_agent_commands(), exports_agent_commands)),
|
|
91391
|
+
Promise.resolve().then(() => (init_ai_commands(), exports_ai_commands)),
|
|
89090
91392
|
Promise.resolve().then(() => (init_config_serve_commands(), exports_config_serve_commands)),
|
|
89091
91393
|
Promise.resolve().then(() => (init_query_commands(), exports_query_commands)),
|
|
89092
91394
|
Promise.resolve().then(() => (init_mcp_hooks_commands(), exports_mcp_hooks_commands)),
|
|
@@ -89118,6 +91420,7 @@ registerTaskCommands2(program2);
|
|
|
89118
91420
|
registerPlanTemplateCommands2(program2);
|
|
89119
91421
|
registerProjectCommands2(program2);
|
|
89120
91422
|
registerAgentCommands2(program2);
|
|
91423
|
+
registerAiCommands2(program2);
|
|
89121
91424
|
registerConfigServeCommands2(program2);
|
|
89122
91425
|
registerQueryCommands2(program2);
|
|
89123
91426
|
registerMcpHooksCommands2(program2);
|
|
@@ -89144,9 +91447,15 @@ registerStorageCommands2(program2);
|
|
|
89144
91447
|
registerScaleHardeningCommands2(program2);
|
|
89145
91448
|
registerPrGroupCommands2(program2);
|
|
89146
91449
|
await registerOptionalEventsCommands(program2);
|
|
89147
|
-
registerHelpCommands2(program2, authority.route);
|
|
89148
|
-
applyTodosCliHelpVisibility(program2, authority.route);
|
|
91450
|
+
registerHelpCommands2(program2, authority.route, remoteCommandCapabilities);
|
|
91451
|
+
applyTodosCliHelpVisibility(program2, authority.route, remoteCommandCapabilities);
|
|
89149
91452
|
try {
|
|
91453
|
+
if (metadataRequested) {
|
|
91454
|
+
const unavailableCommand = getUnavailableTodosCliRemoteMetadataCommand(authority.route, remoteCommandCapabilities, process.argv.slice(2));
|
|
91455
|
+
if (unavailableCommand) {
|
|
91456
|
+
throw new Error(`REMOTE_COMMAND_UNAVAILABLE: configured Todos authority does not advertise ${unavailableCommand}; help is unavailable for this command`);
|
|
91457
|
+
}
|
|
91458
|
+
}
|
|
89150
91459
|
await program2.parseAsync();
|
|
89151
91460
|
} catch (err) {
|
|
89152
91461
|
handleError2(err);
|