@hasna/todos 0.15.20 → 0.15.25
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 +2862 -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 +641 -18
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3148 -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 +130 -72
- package/dist/mcp.js +4 -1
- package/dist/project-registration.js +70 -29
- package/dist/registry.js +606 -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 +136 -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 +4 -1
package/dist/contracts.js
CHANGED
|
@@ -4731,7 +4731,7 @@ var init_database = __esm(() => {
|
|
|
4731
4731
|
});
|
|
4732
4732
|
|
|
4733
4733
|
// src/lib/config.ts
|
|
4734
|
-
import { existsSync as existsSync4 } from "fs";
|
|
4734
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
4735
4735
|
import { dirname as dirname2, join as join3 } from "path";
|
|
4736
4736
|
function getConfigPath() {
|
|
4737
4737
|
return join3(getTodosGlobalDir(), "config.json");
|
|
@@ -4763,6 +4763,22 @@ function saveConfig(config) {
|
|
|
4763
4763
|
function updateConfig(patch) {
|
|
4764
4764
|
return saveConfig({ ...loadConfig(), ...patch });
|
|
4765
4765
|
}
|
|
4766
|
+
function getTodosAiConfig() {
|
|
4767
|
+
const configPath = getConfigPath();
|
|
4768
|
+
if (!existsSync4(configPath))
|
|
4769
|
+
return {};
|
|
4770
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
4771
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4772
|
+
throw new Error("Todos config must be a JSON object");
|
|
4773
|
+
}
|
|
4774
|
+
const ai = parsed["ai"];
|
|
4775
|
+
if (ai === undefined)
|
|
4776
|
+
return {};
|
|
4777
|
+
if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
|
|
4778
|
+
throw new Error("Todos AI configuration must be an object");
|
|
4779
|
+
}
|
|
4780
|
+
return { ...ai };
|
|
4781
|
+
}
|
|
4766
4782
|
function normalizeApiUrl(value) {
|
|
4767
4783
|
const trimmed = value?.trim();
|
|
4768
4784
|
if (!trimmed)
|
|
@@ -7093,7 +7109,7 @@ var init_shared_events = __esm(() => {
|
|
|
7093
7109
|
});
|
|
7094
7110
|
|
|
7095
7111
|
// src/lib/secret-redaction.ts
|
|
7096
|
-
import { readFileSync as
|
|
7112
|
+
import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
|
|
7097
7113
|
function registerCustomRedactor(fn) {
|
|
7098
7114
|
customRedactors.push(fn);
|
|
7099
7115
|
}
|
|
@@ -7160,7 +7176,7 @@ function scanAndRedactText(text, options = {}) {
|
|
|
7160
7176
|
function scanFileForSecrets(path, options = {}) {
|
|
7161
7177
|
if (!existsSync6(path))
|
|
7162
7178
|
throw new Error(`File not found: ${path}`);
|
|
7163
|
-
const content =
|
|
7179
|
+
const content = readFileSync3(path, "utf8");
|
|
7164
7180
|
return scanAndRedactText(content, options);
|
|
7165
7181
|
}
|
|
7166
7182
|
function safeStringify(value, space) {
|
|
@@ -8606,7 +8622,7 @@ function requireCanonicalLockVersion(value) {
|
|
|
8606
8622
|
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" });
|
|
8607
8623
|
}
|
|
8608
8624
|
const parsed = Date.parse(value);
|
|
8609
|
-
if (Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
8625
|
+
if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
8610
8626
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
|
|
8611
8627
|
}
|
|
8612
8628
|
return value;
|
|
@@ -8631,9 +8647,6 @@ function prepareStaleLockHandoff(input, options = {}) {
|
|
|
8631
8647
|
if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
|
|
8632
8648
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
|
|
8633
8649
|
}
|
|
8634
|
-
if (canonicalAgentRef(expectedHolder) === canonicalAgentRef(newHolder)) {
|
|
8635
|
-
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "new_holder must differ from expected_holder", { field: "new_holder" });
|
|
8636
|
-
}
|
|
8637
8650
|
const operationTimestamp = options.now ?? new Date().toISOString();
|
|
8638
8651
|
if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
|
|
8639
8652
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
|
|
@@ -11211,7 +11224,7 @@ var init_boards = __esm(() => {
|
|
|
11211
11224
|
|
|
11212
11225
|
// src/lib/artifact-store.ts
|
|
11213
11226
|
import { createHash as createHash3 } from "crypto";
|
|
11214
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
11227
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
11215
11228
|
import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
|
|
11216
11229
|
import { tmpdir as tmpdir2 } from "os";
|
|
11217
11230
|
function isInMemoryDb2(path) {
|
|
@@ -11278,7 +11291,7 @@ function storeArtifactContent(input) {
|
|
|
11278
11291
|
const sourceStat = statSync2(sourcePath);
|
|
11279
11292
|
if (!sourceStat.isFile())
|
|
11280
11293
|
throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
|
|
11281
|
-
const sourceBuffer =
|
|
11294
|
+
const sourceBuffer = readFileSync4(sourcePath);
|
|
11282
11295
|
const sourceSha = sha256(sourceBuffer);
|
|
11283
11296
|
const textLike = isTextLike(sourceBuffer, input.path);
|
|
11284
11297
|
let storedBuffer = sourceBuffer;
|
|
@@ -11365,7 +11378,7 @@ function verifyStoredArtifact(input) {
|
|
|
11365
11378
|
message: "stored artifact content is missing"
|
|
11366
11379
|
};
|
|
11367
11380
|
}
|
|
11368
|
-
const buffer =
|
|
11381
|
+
const buffer = readFileSync4(storedPath);
|
|
11369
11382
|
const actualSha = sha256(buffer);
|
|
11370
11383
|
const actualSize = buffer.length;
|
|
11371
11384
|
const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
|
|
@@ -11385,7 +11398,7 @@ function exportStoredArtifactContent(input) {
|
|
|
11385
11398
|
const report = verifyStoredArtifact(input);
|
|
11386
11399
|
if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
|
|
11387
11400
|
return null;
|
|
11388
|
-
const content =
|
|
11401
|
+
const content = readFileSync4(artifactStorePath(report.relative_path));
|
|
11389
11402
|
return {
|
|
11390
11403
|
artifact_id: input.id,
|
|
11391
11404
|
sha256: report.actual_sha256,
|
|
@@ -11436,7 +11449,7 @@ function getArtifactStoreRoot(dbPath) {
|
|
|
11436
11449
|
return join5(dirname4(resolve7(path)), "artifacts");
|
|
11437
11450
|
}
|
|
11438
11451
|
function computeContentHash(path) {
|
|
11439
|
-
return sha256(
|
|
11452
|
+
return sha256(readFileSync4(resolve7(path)));
|
|
11440
11453
|
}
|
|
11441
11454
|
function storeArtifactFile(input) {
|
|
11442
11455
|
const sourcePath = resolve7(input.sourcePath);
|
|
@@ -11446,7 +11459,7 @@ function storeArtifactFile(input) {
|
|
|
11446
11459
|
if (!statSync2(sourcePath).isFile()) {
|
|
11447
11460
|
throw new Error(`Source path is not a file: ${input.sourcePath}`);
|
|
11448
11461
|
}
|
|
11449
|
-
const buffer =
|
|
11462
|
+
const buffer = readFileSync4(sourcePath);
|
|
11450
11463
|
const contentHash = sha256(buffer);
|
|
11451
11464
|
const mimeType = mediaTypeFor(sourcePath, isTextLike(buffer, sourcePath));
|
|
11452
11465
|
const storageMode = input.storageMode ?? "copy";
|
|
@@ -12645,7 +12658,7 @@ var init_tasks = __esm(() => {
|
|
|
12645
12658
|
// package.json
|
|
12646
12659
|
var package_default = {
|
|
12647
12660
|
name: "@hasna/todos",
|
|
12648
|
-
version: "0.15.
|
|
12661
|
+
version: "0.15.25",
|
|
12649
12662
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12650
12663
|
type: "module",
|
|
12651
12664
|
main: "dist/index.js",
|
|
@@ -12717,6 +12730,8 @@ var package_default = {
|
|
|
12717
12730
|
"dev:serve": "bun run src/server/index.ts",
|
|
12718
12731
|
"verify:release": "bun run scripts/verify-public-release.ts --mode=review",
|
|
12719
12732
|
"verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
|
|
12733
|
+
"verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
|
|
12734
|
+
"test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
|
|
12720
12735
|
"issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
|
|
12721
12736
|
prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
|
|
12722
12737
|
postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
|
|
@@ -12769,6 +12784,7 @@ var package_default = {
|
|
|
12769
12784
|
"@types/bun": "^1.2.4",
|
|
12770
12785
|
"@types/react": "^18.3.18",
|
|
12771
12786
|
"bun-types": "1.3.9",
|
|
12787
|
+
"hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
|
|
12772
12788
|
typescript: "^5.7.3"
|
|
12773
12789
|
}
|
|
12774
12790
|
};
|
|
@@ -15416,6 +15432,578 @@ function createJsonContractsManifest(options = {}) {
|
|
|
15416
15432
|
var TODOS_JSON_CONTRACTS_MANIFEST = createJsonContractsManifest({
|
|
15417
15433
|
generatedAt: "1970-01-01T00:00:00.000Z"
|
|
15418
15434
|
});
|
|
15435
|
+
|
|
15436
|
+
// src/ai.ts
|
|
15437
|
+
var TODOS_AI_SCHEMA_VERSION = 1;
|
|
15438
|
+
var TODOS_AI_RUNTIME_PROTOCOL_VERSION = 1;
|
|
15439
|
+
var TODOS_AI_RUNTIME_SPECIFIER = "@hasna/todos-ai/runtime";
|
|
15440
|
+
var TODOS_AI_UPDATE_TASK_RESULT_SCHEMA = "todos.ai.update_task.v1";
|
|
15441
|
+
var TODOS_AI_FORMATS = ["text", "json", "stream-json"];
|
|
15442
|
+
var TODOS_AI_WRITE_MODES = ["read-only", "plan", "execute"];
|
|
15443
|
+
var TODOS_AI_APPROVAL_MODES = ["deny", "required", "prompt", "existing"];
|
|
15444
|
+
var TODOS_AI_RUN_STATUSES = [
|
|
15445
|
+
"answered",
|
|
15446
|
+
"needs_input",
|
|
15447
|
+
"needs_approval",
|
|
15448
|
+
"completed",
|
|
15449
|
+
"failed"
|
|
15450
|
+
];
|
|
15451
|
+
var TODOS_AI_RUNTIME_EVENT_TYPES = [
|
|
15452
|
+
"run.started",
|
|
15453
|
+
"run.progress",
|
|
15454
|
+
"text.delta",
|
|
15455
|
+
"tool.started",
|
|
15456
|
+
"tool.completed",
|
|
15457
|
+
"input.required",
|
|
15458
|
+
"approval.required"
|
|
15459
|
+
];
|
|
15460
|
+
var TODOS_AI_TOOL_EFFECTS = ["read", "control", "write"];
|
|
15461
|
+
var TODOS_AI_UPDATE_TASK_FIELDS = [
|
|
15462
|
+
"title",
|
|
15463
|
+
"description",
|
|
15464
|
+
"status",
|
|
15465
|
+
"priority",
|
|
15466
|
+
"assigned_to",
|
|
15467
|
+
"tags",
|
|
15468
|
+
"due_at"
|
|
15469
|
+
];
|
|
15470
|
+
var TODOS_AI_UPDATE_TASK_LIMITS = {
|
|
15471
|
+
max_title_bytes: 1024,
|
|
15472
|
+
max_description_bytes: 8192,
|
|
15473
|
+
max_assignee_bytes: 256,
|
|
15474
|
+
max_tags: 16,
|
|
15475
|
+
max_tag_bytes: 128,
|
|
15476
|
+
max_due_at_bytes: 128,
|
|
15477
|
+
min_idempotency_key_bytes: 8,
|
|
15478
|
+
max_idempotency_key_bytes: 128,
|
|
15479
|
+
max_result_bytes: 65536
|
|
15480
|
+
};
|
|
15481
|
+
var TODOS_AI_DEFAULTS = {
|
|
15482
|
+
format: "text",
|
|
15483
|
+
max_steps: 8,
|
|
15484
|
+
timeout_ms: 60000,
|
|
15485
|
+
write_mode: "read-only"
|
|
15486
|
+
};
|
|
15487
|
+
var TODOS_AI_LIMITS = {
|
|
15488
|
+
max_prompt_bytes: 1048576,
|
|
15489
|
+
max_json_bytes: 1048576,
|
|
15490
|
+
max_result_bytes: 4194304,
|
|
15491
|
+
max_variable_count: 100,
|
|
15492
|
+
max_variable_value_bytes: 65536,
|
|
15493
|
+
max_approval_refs: 32,
|
|
15494
|
+
max_approval_ref_bytes: 1024,
|
|
15495
|
+
max_resume_run_id_bytes: 1024,
|
|
15496
|
+
max_pending_input_prompt_bytes: 1024,
|
|
15497
|
+
max_pending_input_fields: 16,
|
|
15498
|
+
max_pending_input_field_bytes: 128,
|
|
15499
|
+
max_pending_approval_id_bytes: 256,
|
|
15500
|
+
max_pending_approval_summary_bytes: 1024,
|
|
15501
|
+
max_pending_approval_operations: 4,
|
|
15502
|
+
max_pending_approval_bytes: 8192,
|
|
15503
|
+
max_stream_events: 1000,
|
|
15504
|
+
max_stream_record_bytes: 262144,
|
|
15505
|
+
max_stream_bytes: 8388608,
|
|
15506
|
+
min_steps: 1,
|
|
15507
|
+
max_steps: 20,
|
|
15508
|
+
min_timeout_ms: 1000,
|
|
15509
|
+
max_timeout_ms: 600000
|
|
15510
|
+
};
|
|
15511
|
+
var TODOS_AI_EXIT_CODES = {
|
|
15512
|
+
success: 0,
|
|
15513
|
+
usage: 2,
|
|
15514
|
+
needs_input: 3,
|
|
15515
|
+
needs_approval: 4,
|
|
15516
|
+
runtime_unavailable: 5,
|
|
15517
|
+
failed: 6,
|
|
15518
|
+
timeout: 124,
|
|
15519
|
+
interrupted: 130
|
|
15520
|
+
};
|
|
15521
|
+
var TODOS_AI_PROTOCOL = {
|
|
15522
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
15523
|
+
runtime_protocol_version: TODOS_AI_RUNTIME_PROTOCOL_VERSION,
|
|
15524
|
+
runtime_specifier: TODOS_AI_RUNTIME_SPECIFIER,
|
|
15525
|
+
formats: TODOS_AI_FORMATS,
|
|
15526
|
+
write_modes: TODOS_AI_WRITE_MODES,
|
|
15527
|
+
approval_modes: TODOS_AI_APPROVAL_MODES,
|
|
15528
|
+
statuses: TODOS_AI_RUN_STATUSES,
|
|
15529
|
+
event_types: TODOS_AI_RUNTIME_EVENT_TYPES,
|
|
15530
|
+
tool_effects: TODOS_AI_TOOL_EFFECTS,
|
|
15531
|
+
defaults: TODOS_AI_DEFAULTS,
|
|
15532
|
+
limits: TODOS_AI_LIMITS,
|
|
15533
|
+
exit_codes: TODOS_AI_EXIT_CODES
|
|
15534
|
+
};
|
|
15535
|
+
|
|
15536
|
+
class TodosAiContractError extends Error {
|
|
15537
|
+
code;
|
|
15538
|
+
exitCode;
|
|
15539
|
+
constructor(code, message, exitCode = TODOS_AI_EXIT_CODES.usage, options) {
|
|
15540
|
+
super(message, options);
|
|
15541
|
+
this.code = code;
|
|
15542
|
+
this.exitCode = exitCode;
|
|
15543
|
+
this.name = "TodosAiContractError";
|
|
15544
|
+
}
|
|
15545
|
+
}
|
|
15546
|
+
|
|
15547
|
+
class TodosAiNeedsInputSignal extends Error {
|
|
15548
|
+
pending_input;
|
|
15549
|
+
constructor(pendingInput) {
|
|
15550
|
+
if (!isTodosAiJsonValue(pendingInput) || !isPendingInput(pendingInput)) {
|
|
15551
|
+
throw new TodosAiContractError("invalid_input", "Todos AI pending input must be bounded stable control data");
|
|
15552
|
+
}
|
|
15553
|
+
super("Todos AI input required");
|
|
15554
|
+
this.name = "TodosAiNeedsInputSignal";
|
|
15555
|
+
this.pending_input = {
|
|
15556
|
+
prompt: pendingInput.prompt,
|
|
15557
|
+
fields: [...pendingInput.fields]
|
|
15558
|
+
};
|
|
15559
|
+
}
|
|
15560
|
+
}
|
|
15561
|
+
|
|
15562
|
+
class TodosAiNeedsApprovalSignal extends Error {
|
|
15563
|
+
pending_approval;
|
|
15564
|
+
constructor(pendingApproval) {
|
|
15565
|
+
if (!isTodosAiJsonValue(pendingApproval) || !isPendingApproval(pendingApproval)) {
|
|
15566
|
+
throw new TodosAiContractError("invalid_input", "Todos AI pending approval must be bounded stable control data");
|
|
15567
|
+
}
|
|
15568
|
+
super("Todos AI approval required");
|
|
15569
|
+
this.name = "TodosAiNeedsApprovalSignal";
|
|
15570
|
+
this.pending_approval = {
|
|
15571
|
+
id: pendingApproval.id,
|
|
15572
|
+
summary: pendingApproval.summary,
|
|
15573
|
+
operations: pendingApproval.operations.map((operation) => JSON.parse(JSON.stringify(operation)))
|
|
15574
|
+
};
|
|
15575
|
+
}
|
|
15576
|
+
}
|
|
15577
|
+
function isRecord(value) {
|
|
15578
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15579
|
+
}
|
|
15580
|
+
function isJsonObject(value) {
|
|
15581
|
+
if (!isRecord(value))
|
|
15582
|
+
return false;
|
|
15583
|
+
const prototype = Object.getPrototypeOf(value);
|
|
15584
|
+
return prototype === Object.prototype || prototype === null;
|
|
15585
|
+
}
|
|
15586
|
+
function prototypeDefinesToJson(value) {
|
|
15587
|
+
let prototype = Object.getPrototypeOf(value);
|
|
15588
|
+
while (prototype !== null) {
|
|
15589
|
+
if (Object.getOwnPropertyDescriptor(prototype, "toJSON") !== undefined)
|
|
15590
|
+
return true;
|
|
15591
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
15592
|
+
}
|
|
15593
|
+
return false;
|
|
15594
|
+
}
|
|
15595
|
+
function isStableJsonArray(value, ancestors, depth) {
|
|
15596
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || prototypeDefinesToJson(value)) {
|
|
15597
|
+
return false;
|
|
15598
|
+
}
|
|
15599
|
+
const keys = Reflect.ownKeys(value);
|
|
15600
|
+
if (keys.length !== value.length + 1)
|
|
15601
|
+
return false;
|
|
15602
|
+
for (const key of keys) {
|
|
15603
|
+
if (key === "length")
|
|
15604
|
+
continue;
|
|
15605
|
+
if (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key))
|
|
15606
|
+
return false;
|
|
15607
|
+
const index = Number(key);
|
|
15608
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= value.length)
|
|
15609
|
+
return false;
|
|
15610
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
15611
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
15612
|
+
return false;
|
|
15613
|
+
if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
|
|
15614
|
+
return false;
|
|
15615
|
+
}
|
|
15616
|
+
return true;
|
|
15617
|
+
}
|
|
15618
|
+
function isStableJsonObject(value, ancestors, depth) {
|
|
15619
|
+
if (!isJsonObject(value) || prototypeDefinesToJson(value))
|
|
15620
|
+
return false;
|
|
15621
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
15622
|
+
if (typeof key !== "string")
|
|
15623
|
+
return false;
|
|
15624
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
15625
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
15626
|
+
return false;
|
|
15627
|
+
if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
|
|
15628
|
+
return false;
|
|
15629
|
+
}
|
|
15630
|
+
return true;
|
|
15631
|
+
}
|
|
15632
|
+
function isOneOf(value, values) {
|
|
15633
|
+
return typeof value === "string" && values.includes(value);
|
|
15634
|
+
}
|
|
15635
|
+
function selectedString(...values) {
|
|
15636
|
+
for (const value of values) {
|
|
15637
|
+
if (value === undefined || value === null)
|
|
15638
|
+
continue;
|
|
15639
|
+
const trimmed = value.trim();
|
|
15640
|
+
if (trimmed)
|
|
15641
|
+
return trimmed;
|
|
15642
|
+
}
|
|
15643
|
+
return null;
|
|
15644
|
+
}
|
|
15645
|
+
function parseEnum(value, values, field2, fallback) {
|
|
15646
|
+
if (value === null)
|
|
15647
|
+
return fallback;
|
|
15648
|
+
if (isOneOf(value, values))
|
|
15649
|
+
return value;
|
|
15650
|
+
throw new TodosAiContractError("invalid_configuration", `${field2} must be one of: ${values.join(", ")}`);
|
|
15651
|
+
}
|
|
15652
|
+
function parseBoundedInteger(value, field2, fallback, min, max) {
|
|
15653
|
+
if (value === undefined || value === null || value === "")
|
|
15654
|
+
return fallback;
|
|
15655
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
15656
|
+
if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
|
|
15657
|
+
throw new TodosAiContractError("invalid_configuration", `${field2} must be an integer between ${min} and ${max}`);
|
|
15658
|
+
}
|
|
15659
|
+
return parsed;
|
|
15660
|
+
}
|
|
15661
|
+
function normalizeApprovalRefs(values) {
|
|
15662
|
+
const refs = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
|
|
15663
|
+
if (refs.length > TODOS_AI_LIMITS.max_approval_refs) {
|
|
15664
|
+
throw new TodosAiContractError("invalid_configuration", `--approval may be repeated at most ${TODOS_AI_LIMITS.max_approval_refs} times`);
|
|
15665
|
+
}
|
|
15666
|
+
for (const ref of refs) {
|
|
15667
|
+
if (new TextEncoder().encode(ref).byteLength > TODOS_AI_LIMITS.max_approval_ref_bytes) {
|
|
15668
|
+
throw new TodosAiContractError("invalid_configuration", `--approval references may not exceed ${TODOS_AI_LIMITS.max_approval_ref_bytes} bytes`);
|
|
15669
|
+
}
|
|
15670
|
+
}
|
|
15671
|
+
return refs;
|
|
15672
|
+
}
|
|
15673
|
+
function resolveTodosAiCommandOptions(input) {
|
|
15674
|
+
const cli = input.cli ?? {};
|
|
15675
|
+
const config = input.config ?? {};
|
|
15676
|
+
const env = input.env ?? {};
|
|
15677
|
+
const format = parseEnum(selectedString(cli.format, env["TODOS_AI_FORMAT"], config.format), TODOS_AI_FORMATS, "format", TODOS_AI_DEFAULTS.format);
|
|
15678
|
+
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);
|
|
15679
|
+
if (cli.dryRun && cli.writeMode === "execute") {
|
|
15680
|
+
throw new TodosAiContractError("invalid_configuration", "--dry-run cannot be combined with --write-mode execute");
|
|
15681
|
+
}
|
|
15682
|
+
const writeMode = cli.dryRun ? "plan" : configuredWriteMode;
|
|
15683
|
+
const explicitApproval = cli.dryRun ? selectedString(cli.approvalMode) : selectedString(cli.approvalMode, env["TODOS_AI_APPROVAL_MODE"], config.approval_mode);
|
|
15684
|
+
const defaultApproval = writeMode === "execute" ? input.interactive ? "prompt" : "required" : "deny";
|
|
15685
|
+
const approvalMode = cli.dryRun ? parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", "deny") : parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", defaultApproval);
|
|
15686
|
+
const approvalRefs = normalizeApprovalRefs(cli.approvalRefs);
|
|
15687
|
+
if (cli.dryRun && approvalMode !== "deny") {
|
|
15688
|
+
throw new TodosAiContractError("invalid_configuration", "--dry-run requires approval mode deny");
|
|
15689
|
+
}
|
|
15690
|
+
if (writeMode !== "execute" && approvalMode !== "deny") {
|
|
15691
|
+
throw new TodosAiContractError("invalid_configuration", "approval modes other than deny require --write-mode execute");
|
|
15692
|
+
}
|
|
15693
|
+
if (writeMode === "execute" && approvalMode === "deny") {
|
|
15694
|
+
throw new TodosAiContractError("invalid_configuration", "--write-mode execute requires approval mode required, prompt, or existing");
|
|
15695
|
+
}
|
|
15696
|
+
if (!input.interactive && approvalMode === "prompt") {
|
|
15697
|
+
throw new TodosAiContractError("invalid_configuration", "approval mode prompt is unavailable in non-interactive mode");
|
|
15698
|
+
}
|
|
15699
|
+
if (approvalMode === "existing" && approvalRefs.length === 0) {
|
|
15700
|
+
throw new TodosAiContractError("invalid_configuration", "approval mode existing requires at least one --approval reference");
|
|
15701
|
+
}
|
|
15702
|
+
if (approvalMode !== "existing" && approvalRefs.length > 0) {
|
|
15703
|
+
throw new TodosAiContractError("invalid_configuration", "--approval references require --approval-mode existing");
|
|
15704
|
+
}
|
|
15705
|
+
return {
|
|
15706
|
+
provider: selectedString(cli.provider, env["TODOS_AI_PROVIDER"], config.provider),
|
|
15707
|
+
model: selectedString(cli.model, env["TODOS_AI_MODEL"], config.model),
|
|
15708
|
+
profile: selectedString(cli.profile, env["TODOS_AI_PROFILE"], config.profile),
|
|
15709
|
+
format,
|
|
15710
|
+
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),
|
|
15711
|
+
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),
|
|
15712
|
+
write_mode: writeMode,
|
|
15713
|
+
approval_mode: approvalMode,
|
|
15714
|
+
approval_refs: approvalRefs,
|
|
15715
|
+
dry_run: cli.dryRun === true,
|
|
15716
|
+
interactive: input.interactive
|
|
15717
|
+
};
|
|
15718
|
+
}
|
|
15719
|
+
function normalizeTodosAiPrompt(value) {
|
|
15720
|
+
const prompt = value.trim();
|
|
15721
|
+
if (new TextEncoder().encode(prompt).byteLength > TODOS_AI_LIMITS.max_prompt_bytes) {
|
|
15722
|
+
throw new TodosAiContractError("invalid_input", `prompt exceeds ${TODOS_AI_LIMITS.max_prompt_bytes} bytes`);
|
|
15723
|
+
}
|
|
15724
|
+
return prompt;
|
|
15725
|
+
}
|
|
15726
|
+
function parseTodosAiJson(value, field2) {
|
|
15727
|
+
if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_json_bytes) {
|
|
15728
|
+
throw new TodosAiContractError("invalid_input", `${field2} exceeds ${TODOS_AI_LIMITS.max_json_bytes} bytes`);
|
|
15729
|
+
}
|
|
15730
|
+
let parsed;
|
|
15731
|
+
try {
|
|
15732
|
+
parsed = JSON.parse(value);
|
|
15733
|
+
} catch (error) {
|
|
15734
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
15735
|
+
throw new TodosAiContractError("invalid_input", `${field2} must be valid JSON: ${detail}`);
|
|
15736
|
+
}
|
|
15737
|
+
if (!isTodosAiJsonValue(parsed)) {
|
|
15738
|
+
throw new TodosAiContractError("invalid_input", `${field2} must contain only stable JSON values`);
|
|
15739
|
+
}
|
|
15740
|
+
return parsed;
|
|
15741
|
+
}
|
|
15742
|
+
function parseTodosAiOutputSchema(value) {
|
|
15743
|
+
const parsed = parseTodosAiJson(value, "output schema");
|
|
15744
|
+
if (!isRecord(parsed)) {
|
|
15745
|
+
throw new TodosAiContractError("invalid_input", "output schema must be a JSON object");
|
|
15746
|
+
}
|
|
15747
|
+
return parsed;
|
|
15748
|
+
}
|
|
15749
|
+
var SENSITIVE_VARIABLE_KEY = /(?:^|[_.-])(api[_-]?key|credential|password|secret|token)(?:$|[_.-])/i;
|
|
15750
|
+
function parseTodosAiVariables(values) {
|
|
15751
|
+
if (values.length > TODOS_AI_LIMITS.max_variable_count) {
|
|
15752
|
+
throw new TodosAiContractError("invalid_input", `--var may be repeated at most ${TODOS_AI_LIMITS.max_variable_count} times`);
|
|
15753
|
+
}
|
|
15754
|
+
const variables = Object.create(null);
|
|
15755
|
+
for (const entry of values) {
|
|
15756
|
+
const separator = entry.indexOf("=");
|
|
15757
|
+
const key = separator >= 0 ? entry.slice(0, separator).trim() : "";
|
|
15758
|
+
const value = separator >= 0 ? entry.slice(separator + 1) : "";
|
|
15759
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/.test(key)) {
|
|
15760
|
+
throw new TodosAiContractError("invalid_input", `invalid --var entry ${JSON.stringify(entry)}; expected non-secret key=value`);
|
|
15761
|
+
}
|
|
15762
|
+
if (SENSITIVE_VARIABLE_KEY.test(key)) {
|
|
15763
|
+
throw new TodosAiContractError("invalid_input", `--var ${key} is credential-shaped; provide credentials through the runtime's secret configuration`);
|
|
15764
|
+
}
|
|
15765
|
+
if (Object.hasOwn(variables, key)) {
|
|
15766
|
+
throw new TodosAiContractError("invalid_input", `duplicate --var key: ${key}`);
|
|
15767
|
+
}
|
|
15768
|
+
if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_variable_value_bytes) {
|
|
15769
|
+
throw new TodosAiContractError("invalid_input", `--var ${key} exceeds ${TODOS_AI_LIMITS.max_variable_value_bytes} bytes`);
|
|
15770
|
+
}
|
|
15771
|
+
variables[key] = value;
|
|
15772
|
+
}
|
|
15773
|
+
return variables;
|
|
15774
|
+
}
|
|
15775
|
+
function isTodosAiJsonValue(value) {
|
|
15776
|
+
return isTodosAiJsonValueInternal(value, new Set, 0);
|
|
15777
|
+
}
|
|
15778
|
+
function isTodosAiUpdateTaskResult(value) {
|
|
15779
|
+
if (!isTodosAiJsonValue(value) || !isRecord(value) || !hasOnlyKeys(value, [
|
|
15780
|
+
"schema",
|
|
15781
|
+
"operation",
|
|
15782
|
+
"mode",
|
|
15783
|
+
"applied",
|
|
15784
|
+
"readback_verified",
|
|
15785
|
+
"source",
|
|
15786
|
+
"target",
|
|
15787
|
+
"changed_fields",
|
|
15788
|
+
"approval_ref",
|
|
15789
|
+
"payload_digest",
|
|
15790
|
+
"idempotency"
|
|
15791
|
+
]) || value["schema"] !== TODOS_AI_UPDATE_TASK_RESULT_SCHEMA || value["operation"] !== "update_task" || value["mode"] !== "plan" && value["mode"] !== "execute" || value["source"] !== "sqlite" && value["source"] !== "http") {
|
|
15792
|
+
return false;
|
|
15793
|
+
}
|
|
15794
|
+
const changedFields = value["changed_fields"];
|
|
15795
|
+
const target = value["target"];
|
|
15796
|
+
const idempotency = value["idempotency"];
|
|
15797
|
+
const payloadDigest = value["payload_digest"];
|
|
15798
|
+
const approvalRef = value["approval_ref"];
|
|
15799
|
+
if (!Array.isArray(changedFields) || changedFields.length === 0 || changedFields.length > TODOS_AI_UPDATE_TASK_FIELDS.length || !changedFields.every((field2) => typeof field2 === "string" && TODOS_AI_UPDATE_TASK_FIELDS.includes(field2)) || new Set(changedFields).size !== changedFields.length || !TODOS_AI_UPDATE_TASK_FIELDS.filter((field2) => changedFields.includes(field2)).every((field2, index) => changedFields[index] === field2) || !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) {
|
|
15800
|
+
return false;
|
|
15801
|
+
}
|
|
15802
|
+
if (value["mode"] === "plan") {
|
|
15803
|
+
return value["applied"] === false && value["readback_verified"] === false && target["result_version"] === null;
|
|
15804
|
+
}
|
|
15805
|
+
return value["applied"] === true && value["readback_verified"] === true && Number.isSafeInteger(target["result_version"]) && target["result_version"] === target["expected_version"] + 1;
|
|
15806
|
+
}
|
|
15807
|
+
function isTodosAiJsonValueInternal(value, ancestors, depth) {
|
|
15808
|
+
if (depth > 64)
|
|
15809
|
+
return false;
|
|
15810
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
15811
|
+
return true;
|
|
15812
|
+
if (typeof value === "number")
|
|
15813
|
+
return Number.isFinite(value);
|
|
15814
|
+
if (typeof value !== "object")
|
|
15815
|
+
return false;
|
|
15816
|
+
if (ancestors.has(value))
|
|
15817
|
+
return false;
|
|
15818
|
+
ancestors.add(value);
|
|
15819
|
+
try {
|
|
15820
|
+
return Array.isArray(value) ? isStableJsonArray(value, ancestors, depth) : isStableJsonObject(value, ancestors, depth);
|
|
15821
|
+
} catch {
|
|
15822
|
+
return false;
|
|
15823
|
+
} finally {
|
|
15824
|
+
ancestors.delete(value);
|
|
15825
|
+
}
|
|
15826
|
+
}
|
|
15827
|
+
function isUsage(value) {
|
|
15828
|
+
if (!isRecord(value))
|
|
15829
|
+
return false;
|
|
15830
|
+
return ["input_tokens", "output_tokens", "total_tokens"].every((key) => Number.isSafeInteger(value[key]) && value[key] >= 0);
|
|
15831
|
+
}
|
|
15832
|
+
function hasOnlyKeys(value, keys) {
|
|
15833
|
+
const allowed = new Set(keys);
|
|
15834
|
+
return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.has(key));
|
|
15835
|
+
}
|
|
15836
|
+
function boundedUtf8String(value, maximum, allowEmpty = false) {
|
|
15837
|
+
return typeof value === "string" && (allowEmpty || value.length > 0) && new TextEncoder().encode(value).byteLength <= maximum;
|
|
15838
|
+
}
|
|
15839
|
+
function isPendingInput(value) {
|
|
15840
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["prompt", "fields"]))
|
|
15841
|
+
return false;
|
|
15842
|
+
if (!boundedUtf8String(value["prompt"], TODOS_AI_LIMITS.max_pending_input_prompt_bytes)) {
|
|
15843
|
+
return false;
|
|
15844
|
+
}
|
|
15845
|
+
if (!Array.isArray(value["fields"]) || value["fields"].length === 0 || value["fields"].length > TODOS_AI_LIMITS.max_pending_input_fields) {
|
|
15846
|
+
return false;
|
|
15847
|
+
}
|
|
15848
|
+
const fields = value["fields"];
|
|
15849
|
+
const unique = new Set;
|
|
15850
|
+
for (const field2 of fields) {
|
|
15851
|
+
if (!boundedUtf8String(field2, TODOS_AI_LIMITS.max_pending_input_field_bytes) || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(field2) || unique.has(field2)) {
|
|
15852
|
+
return false;
|
|
15853
|
+
}
|
|
15854
|
+
unique.add(field2);
|
|
15855
|
+
}
|
|
15856
|
+
return true;
|
|
15857
|
+
}
|
|
15858
|
+
function isPendingApproval(value) {
|
|
15859
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["id", "summary", "operations"])) {
|
|
15860
|
+
return false;
|
|
15861
|
+
}
|
|
15862
|
+
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)) {
|
|
15863
|
+
return false;
|
|
15864
|
+
}
|
|
15865
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength <= TODOS_AI_LIMITS.max_pending_approval_bytes;
|
|
15866
|
+
}
|
|
15867
|
+
function isAiError(value) {
|
|
15868
|
+
if (!isRecord(value))
|
|
15869
|
+
return false;
|
|
15870
|
+
const code = value["code"];
|
|
15871
|
+
const validCode = [
|
|
15872
|
+
"invalid_input",
|
|
15873
|
+
"invalid_configuration",
|
|
15874
|
+
"runtime_unavailable",
|
|
15875
|
+
"runtime_incompatible",
|
|
15876
|
+
"runtime_invalid_result",
|
|
15877
|
+
"needs_input",
|
|
15878
|
+
"needs_approval",
|
|
15879
|
+
"timeout",
|
|
15880
|
+
"interrupted",
|
|
15881
|
+
"provider_error",
|
|
15882
|
+
"tool_error",
|
|
15883
|
+
"schema_error",
|
|
15884
|
+
"internal_error"
|
|
15885
|
+
];
|
|
15886
|
+
return typeof code === "string" && validCode.includes(code) && typeof value["message"] === "string" && typeof value["retryable"] === "boolean" && (value["details"] === null || isRecord(value["details"]) && isTodosAiJsonValue(value["details"]));
|
|
15887
|
+
}
|
|
15888
|
+
function isTodosAiRunResult(value) {
|
|
15889
|
+
if (!isRecord(value) || !isTodosAiJsonValue(value))
|
|
15890
|
+
return false;
|
|
15891
|
+
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"]));
|
|
15892
|
+
if (!structurallyValid)
|
|
15893
|
+
return false;
|
|
15894
|
+
switch (value["status"]) {
|
|
15895
|
+
case "answered":
|
|
15896
|
+
return typeof value["answer"] === "string" && value["pending_input"] === null && value["pending_approval"] === null && value["error"] === null;
|
|
15897
|
+
case "completed":
|
|
15898
|
+
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;
|
|
15899
|
+
case "needs_input":
|
|
15900
|
+
return value["pending_input"] !== null && value["pending_approval"] === null && value["error"] === null;
|
|
15901
|
+
case "needs_approval":
|
|
15902
|
+
return value["pending_input"] === null && value["pending_approval"] !== null && value["error"] === null;
|
|
15903
|
+
case "failed":
|
|
15904
|
+
return value["pending_input"] === null && value["pending_approval"] === null && value["error"] !== null;
|
|
15905
|
+
}
|
|
15906
|
+
return false;
|
|
15907
|
+
}
|
|
15908
|
+
function assertTodosAiRunResult(value) {
|
|
15909
|
+
if (!isTodosAiRunResult(value)) {
|
|
15910
|
+
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);
|
|
15911
|
+
}
|
|
15912
|
+
return value;
|
|
15913
|
+
}
|
|
15914
|
+
function isTodosAiRuntimeEvent(value) {
|
|
15915
|
+
if (!isRecord(value) || !isTodosAiJsonValue(value))
|
|
15916
|
+
return false;
|
|
15917
|
+
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"]);
|
|
15918
|
+
}
|
|
15919
|
+
function assertTodosAiRuntimeModule(value) {
|
|
15920
|
+
if (!isRecord(value) || value["TODOS_AI_RUNTIME_PROTOCOL_VERSION"] !== TODOS_AI_RUNTIME_PROTOCOL_VERSION || typeof value["createTodosAiRuntime"] !== "function") {
|
|
15921
|
+
throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
|
|
15922
|
+
}
|
|
15923
|
+
return value;
|
|
15924
|
+
}
|
|
15925
|
+
function assertTodosAiRuntime(value) {
|
|
15926
|
+
if (!isRecord(value) || typeof value["run"] !== "function") {
|
|
15927
|
+
throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
|
|
15928
|
+
}
|
|
15929
|
+
return value;
|
|
15930
|
+
}
|
|
15931
|
+
var defaultTodosAiRuntimeImporter = async (specifier) => import(specifier);
|
|
15932
|
+
async function loadTodosAiRuntime(context, importer = defaultTodosAiRuntimeImporter) {
|
|
15933
|
+
let imported;
|
|
15934
|
+
try {
|
|
15935
|
+
imported = await importer(TODOS_AI_RUNTIME_SPECIFIER);
|
|
15936
|
+
} catch (cause) {
|
|
15937
|
+
throw new TodosAiContractError("runtime_unavailable", `optional AI runtime is unavailable; install a compatible ${TODOS_AI_RUNTIME_SPECIFIER}`, TODOS_AI_EXIT_CODES.runtime_unavailable, { cause });
|
|
15938
|
+
}
|
|
15939
|
+
const runtimeModule = assertTodosAiRuntimeModule(imported);
|
|
15940
|
+
return assertTodosAiRuntime(await runtimeModule.createTodosAiRuntime(context));
|
|
15941
|
+
}
|
|
15942
|
+
function createTodosAiFailureResult(runId, code, message, retryable = false, details = null) {
|
|
15943
|
+
return {
|
|
15944
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
15945
|
+
run_id: runId,
|
|
15946
|
+
status: "failed",
|
|
15947
|
+
answer: null,
|
|
15948
|
+
data: null,
|
|
15949
|
+
steps: 0,
|
|
15950
|
+
usage: null,
|
|
15951
|
+
pending_input: null,
|
|
15952
|
+
pending_approval: null,
|
|
15953
|
+
error: { code, message, retryable, details }
|
|
15954
|
+
};
|
|
15955
|
+
}
|
|
15956
|
+
function createTodosAiNeedsInputResult(runId, message) {
|
|
15957
|
+
return {
|
|
15958
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
15959
|
+
run_id: runId,
|
|
15960
|
+
status: "needs_input",
|
|
15961
|
+
answer: null,
|
|
15962
|
+
data: null,
|
|
15963
|
+
steps: 0,
|
|
15964
|
+
usage: null,
|
|
15965
|
+
pending_input: { prompt: message, fields: ["prompt"] },
|
|
15966
|
+
pending_approval: null,
|
|
15967
|
+
error: null
|
|
15968
|
+
};
|
|
15969
|
+
}
|
|
15970
|
+
function createTodosAiNeedsApprovalResult(runId, pendingApproval) {
|
|
15971
|
+
const signal = new TodosAiNeedsApprovalSignal(pendingApproval);
|
|
15972
|
+
return {
|
|
15973
|
+
schema_version: TODOS_AI_SCHEMA_VERSION,
|
|
15974
|
+
run_id: runId,
|
|
15975
|
+
status: "needs_approval",
|
|
15976
|
+
answer: null,
|
|
15977
|
+
data: null,
|
|
15978
|
+
steps: 0,
|
|
15979
|
+
usage: null,
|
|
15980
|
+
pending_input: null,
|
|
15981
|
+
pending_approval: signal.pending_approval,
|
|
15982
|
+
error: null
|
|
15983
|
+
};
|
|
15984
|
+
}
|
|
15985
|
+
function todosAiExitCodeForResult(result) {
|
|
15986
|
+
if (result.status === "answered" || result.status === "completed")
|
|
15987
|
+
return TODOS_AI_EXIT_CODES.success;
|
|
15988
|
+
if (result.status === "needs_input")
|
|
15989
|
+
return TODOS_AI_EXIT_CODES.needs_input;
|
|
15990
|
+
if (result.status === "needs_approval")
|
|
15991
|
+
return TODOS_AI_EXIT_CODES.needs_approval;
|
|
15992
|
+
switch (result.error?.code) {
|
|
15993
|
+
case "invalid_input":
|
|
15994
|
+
case "invalid_configuration":
|
|
15995
|
+
return TODOS_AI_EXIT_CODES.usage;
|
|
15996
|
+
case "runtime_unavailable":
|
|
15997
|
+
case "runtime_incompatible":
|
|
15998
|
+
return TODOS_AI_EXIT_CODES.runtime_unavailable;
|
|
15999
|
+
case "timeout":
|
|
16000
|
+
return TODOS_AI_EXIT_CODES.timeout;
|
|
16001
|
+
case "interrupted":
|
|
16002
|
+
return TODOS_AI_EXIT_CODES.interrupted;
|
|
16003
|
+
default:
|
|
16004
|
+
return TODOS_AI_EXIT_CODES.failed;
|
|
16005
|
+
}
|
|
16006
|
+
}
|
|
15419
16007
|
// src/lib/onboarding-fixtures.ts
|
|
15420
16008
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
15421
16009
|
import { join as join6 } from "path";
|
|
@@ -16371,7 +16959,7 @@ function importOnboardingFixture(options = {}) {
|
|
|
16371
16959
|
// src/lib/local-backups.ts
|
|
16372
16960
|
init_database();
|
|
16373
16961
|
import { createHash as createHash4 } from "crypto";
|
|
16374
|
-
import { readFileSync as
|
|
16962
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
16375
16963
|
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
16376
16964
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
16377
16965
|
var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
|
|
@@ -16486,7 +17074,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
16486
17074
|
return path;
|
|
16487
17075
|
}
|
|
16488
17076
|
function readLocalBackupFile(path) {
|
|
16489
|
-
return JSON.parse(
|
|
17077
|
+
return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
|
|
16490
17078
|
}
|
|
16491
17079
|
function verifyLocalBackup(value, options = {}, db) {
|
|
16492
17080
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -20433,7 +21021,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
|
|
|
20433
21021
|
// src/lib/release-compatibility.ts
|
|
20434
21022
|
init_migrations();
|
|
20435
21023
|
init_schema();
|
|
20436
|
-
import { readFileSync as
|
|
21024
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
20437
21025
|
import { join as join8, resolve as resolve9 } from "path";
|
|
20438
21026
|
import { Database as Database2 } from "bun:sqlite";
|
|
20439
21027
|
var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
@@ -20480,7 +21068,7 @@ function warn(id, message, details) {
|
|
|
20480
21068
|
return { id, status: "warning", message, details };
|
|
20481
21069
|
}
|
|
20482
21070
|
function readPackageJson(root) {
|
|
20483
|
-
return JSON.parse(
|
|
21071
|
+
return JSON.parse(readFileSync6(join8(root, "package.json"), "utf8"));
|
|
20484
21072
|
}
|
|
20485
21073
|
function sortedKeys(value) {
|
|
20486
21074
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -24106,11 +24694,13 @@ export {
|
|
|
24106
24694
|
upsertEncryptionProfile,
|
|
24107
24695
|
updateRoadmap,
|
|
24108
24696
|
updateMilestone,
|
|
24697
|
+
todosAiExitCodeForResult,
|
|
24109
24698
|
summarizeRoadmap,
|
|
24110
24699
|
summarizeMilestone,
|
|
24111
24700
|
sealLocalAuditLedger,
|
|
24112
24701
|
returnReviewItem,
|
|
24113
24702
|
restoreLocalBackup,
|
|
24703
|
+
resolveTodosAiCommandOptions,
|
|
24114
24704
|
requestReviewQueue,
|
|
24115
24705
|
reopenReviewItem,
|
|
24116
24706
|
renderRoadmapMarkdown,
|
|
@@ -24124,7 +24714,12 @@ export {
|
|
|
24124
24714
|
readTesterIssueReportsPayload,
|
|
24125
24715
|
readLocalBackupFile,
|
|
24126
24716
|
pollLocalSnapshots,
|
|
24717
|
+
parseTodosAiVariables,
|
|
24718
|
+
parseTodosAiOutputSchema,
|
|
24719
|
+
parseTodosAiJson,
|
|
24720
|
+
normalizeTodosAiPrompt,
|
|
24127
24721
|
normalizeTesterIssueReport,
|
|
24722
|
+
loadTodosAiRuntime,
|
|
24128
24723
|
listSdkIntegrationExamples,
|
|
24129
24724
|
listRoadmaps,
|
|
24130
24725
|
listReviewRoutingRules,
|
|
@@ -24136,6 +24731,10 @@ export {
|
|
|
24136
24731
|
listLocalReportTypes,
|
|
24137
24732
|
listLocalAuditLedgerCheckpoints,
|
|
24138
24733
|
listEncryptionProfiles,
|
|
24734
|
+
isTodosAiUpdateTaskResult,
|
|
24735
|
+
isTodosAiRuntimeEvent,
|
|
24736
|
+
isTodosAiRunResult,
|
|
24737
|
+
isTodosAiJsonValue,
|
|
24139
24738
|
isEncryptedValue,
|
|
24140
24739
|
isEncryptedBridgeBundle,
|
|
24141
24740
|
importRoadmapBundle,
|
|
@@ -24159,6 +24758,9 @@ export {
|
|
|
24159
24758
|
decryptValue,
|
|
24160
24759
|
decryptString,
|
|
24161
24760
|
decryptBridgeBundle,
|
|
24761
|
+
createTodosAiNeedsInputResult,
|
|
24762
|
+
createTodosAiNeedsApprovalResult,
|
|
24763
|
+
createTodosAiFailureResult,
|
|
24162
24764
|
createSdkIntegrationFixturePack,
|
|
24163
24765
|
createRoadmap,
|
|
24164
24766
|
createReleaseCompatibilityReport,
|
|
@@ -24173,7 +24775,12 @@ export {
|
|
|
24173
24775
|
claimReviewItem,
|
|
24174
24776
|
checkLocalNotifications,
|
|
24175
24777
|
checkLocalIntegrity,
|
|
24778
|
+
assertTodosAiRuntimeModule,
|
|
24779
|
+
assertTodosAiRunResult,
|
|
24176
24780
|
approveReviewItem,
|
|
24781
|
+
TodosAiNeedsInputSignal,
|
|
24782
|
+
TodosAiNeedsApprovalSignal,
|
|
24783
|
+
TodosAiContractError,
|
|
24177
24784
|
TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION,
|
|
24178
24785
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT,
|
|
24179
24786
|
TODOS_ONBOARDING_FIXTURE_SOURCE,
|
|
@@ -24193,6 +24800,22 @@ export {
|
|
|
24193
24800
|
TODOS_ENCRYPTED_BRIDGE_KIND,
|
|
24194
24801
|
TODOS_CONTRACTS,
|
|
24195
24802
|
TODOS_API_ROUTES,
|
|
24803
|
+
TODOS_AI_WRITE_MODES,
|
|
24804
|
+
TODOS_AI_UPDATE_TASK_RESULT_SCHEMA,
|
|
24805
|
+
TODOS_AI_UPDATE_TASK_LIMITS,
|
|
24806
|
+
TODOS_AI_UPDATE_TASK_FIELDS,
|
|
24807
|
+
TODOS_AI_TOOL_EFFECTS,
|
|
24808
|
+
TODOS_AI_SCHEMA_VERSION,
|
|
24809
|
+
TODOS_AI_RUN_STATUSES,
|
|
24810
|
+
TODOS_AI_RUNTIME_SPECIFIER,
|
|
24811
|
+
TODOS_AI_RUNTIME_PROTOCOL_VERSION,
|
|
24812
|
+
TODOS_AI_RUNTIME_EVENT_TYPES,
|
|
24813
|
+
TODOS_AI_PROTOCOL,
|
|
24814
|
+
TODOS_AI_LIMITS,
|
|
24815
|
+
TODOS_AI_FORMATS,
|
|
24816
|
+
TODOS_AI_EXIT_CODES,
|
|
24817
|
+
TODOS_AI_DEFAULTS,
|
|
24818
|
+
TODOS_AI_APPROVAL_MODES,
|
|
24196
24819
|
TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
24197
24820
|
TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
24198
24821
|
TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
|