@hasna/todos 0.11.85 → 0.11.87
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/cli/cloud-router.d.ts +55 -2
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts +2 -0
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1106 -417
- package/dist/contracts.js +1 -1
- package/dist/db/comments.d.ts.map +1 -1
- package/dist/index.js +180 -5
- package/dist/mcp/index.js +483 -39
- package/dist/registry.js +1 -1
- package/dist/release-provenance.json +3 -3
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +21 -0
- package/dist/sdk/v1.generated.d.ts +54 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +13 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +900 -384
- package/dist/server/openapi.d.ts +171 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +7 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/comment-redaction-backfill.d.ts +32 -0
- package/dist/storage/comment-redaction-backfill.d.ts.map +1 -0
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +23 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +6 -0
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage.d.ts +3 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +184 -5
- package/package.json +2 -1
- package/vendor/hasna-contracts-0.5.1.tgz +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1011
1011
|
this._exitCallback = (err) => {
|
|
1012
1012
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1013
1013
|
throw err;
|
|
1014
|
-
}
|
|
1014
|
+
}
|
|
1015
1015
|
};
|
|
1016
1016
|
}
|
|
1017
1017
|
return this;
|
|
@@ -8478,6 +8478,310 @@ var init_storage = __esm(() => {
|
|
|
8478
8478
|
IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
8479
8479
|
});
|
|
8480
8480
|
|
|
8481
|
+
// src/lib/sync-utils.ts
|
|
8482
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
8483
|
+
import { join as join2 } from "path";
|
|
8484
|
+
function getHomeDir() {
|
|
8485
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
8486
|
+
}
|
|
8487
|
+
function getTodosGlobalDir() {
|
|
8488
|
+
return join2(getHomeDir(), ".hasna", "todos");
|
|
8489
|
+
}
|
|
8490
|
+
function ensureDir(dir) {
|
|
8491
|
+
if (!existsSync2(dir))
|
|
8492
|
+
mkdirSync(dir, { recursive: true });
|
|
8493
|
+
}
|
|
8494
|
+
function listJsonFiles(dir) {
|
|
8495
|
+
if (!existsSync2(dir))
|
|
8496
|
+
return [];
|
|
8497
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8498
|
+
}
|
|
8499
|
+
function readJsonFile(path) {
|
|
8500
|
+
try {
|
|
8501
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
8502
|
+
} catch {
|
|
8503
|
+
return null;
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
function writeJsonFile(path, data) {
|
|
8507
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
8508
|
+
`);
|
|
8509
|
+
}
|
|
8510
|
+
function readHighWaterMark(dir) {
|
|
8511
|
+
const path = join2(dir, ".highwatermark");
|
|
8512
|
+
if (!existsSync2(path))
|
|
8513
|
+
return 1;
|
|
8514
|
+
const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
|
|
8515
|
+
return isNaN(val) ? 1 : val;
|
|
8516
|
+
}
|
|
8517
|
+
function writeHighWaterMark(dir, value) {
|
|
8518
|
+
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
8519
|
+
}
|
|
8520
|
+
function getFileMtimeMs(path) {
|
|
8521
|
+
try {
|
|
8522
|
+
return statSync(path).mtimeMs;
|
|
8523
|
+
} catch {
|
|
8524
|
+
return null;
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
function parseTimestamp(value) {
|
|
8528
|
+
if (typeof value !== "string")
|
|
8529
|
+
return null;
|
|
8530
|
+
const parsed = Date.parse(value);
|
|
8531
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
8532
|
+
}
|
|
8533
|
+
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
8534
|
+
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
8535
|
+
const next = [conflict, ...current].slice(0, limit);
|
|
8536
|
+
return { ...metadata, sync_conflicts: next };
|
|
8537
|
+
}
|
|
8538
|
+
var HOME;
|
|
8539
|
+
var init_sync_utils = __esm(() => {
|
|
8540
|
+
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
8541
|
+
});
|
|
8542
|
+
|
|
8543
|
+
// src/lib/config.ts
|
|
8544
|
+
var exports_config = {};
|
|
8545
|
+
__export(exports_config, {
|
|
8546
|
+
updateConfig: () => updateConfig,
|
|
8547
|
+
saveConfig: () => saveConfig,
|
|
8548
|
+
resetConfig: () => resetConfig,
|
|
8549
|
+
normalizeApiUrl: () => normalizeApiUrl,
|
|
8550
|
+
loadConfig: () => loadConfig,
|
|
8551
|
+
getTaskPrefixConfig: () => getTaskPrefixConfig,
|
|
8552
|
+
getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
|
|
8553
|
+
getLocalApiConfig: () => getLocalApiConfig,
|
|
8554
|
+
getConfigPath: () => getConfigPath,
|
|
8555
|
+
getCompletionGuardConfig: () => getCompletionGuardConfig,
|
|
8556
|
+
getAgentTasksDir: () => getAgentTasksDir,
|
|
8557
|
+
getAgentTaskListId: () => getAgentTaskListId,
|
|
8558
|
+
getAgentPoolForProject: () => getAgentPoolForProject
|
|
8559
|
+
});
|
|
8560
|
+
import { existsSync as existsSync3 } from "fs";
|
|
8561
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
8562
|
+
function getConfigPath() {
|
|
8563
|
+
return join3(getTodosGlobalDir(), "config.json");
|
|
8564
|
+
}
|
|
8565
|
+
function resetConfig() {
|
|
8566
|
+
cached = null;
|
|
8567
|
+
}
|
|
8568
|
+
function normalizeAgent(agent) {
|
|
8569
|
+
return agent.trim().toLowerCase();
|
|
8570
|
+
}
|
|
8571
|
+
function loadConfig() {
|
|
8572
|
+
if (cached)
|
|
8573
|
+
return cached;
|
|
8574
|
+
if (!existsSync3(getConfigPath())) {
|
|
8575
|
+
cached = {};
|
|
8576
|
+
return cached;
|
|
8577
|
+
}
|
|
8578
|
+
const config = readJsonFile(getConfigPath()) || {};
|
|
8579
|
+
if (typeof config.sync_agents === "string") {
|
|
8580
|
+
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
8581
|
+
}
|
|
8582
|
+
cached = config;
|
|
8583
|
+
return cached;
|
|
8584
|
+
}
|
|
8585
|
+
function saveConfig(config) {
|
|
8586
|
+
const configPath = getConfigPath();
|
|
8587
|
+
ensureDir(dirname2(configPath));
|
|
8588
|
+
writeJsonFile(configPath, config);
|
|
8589
|
+
cached = config;
|
|
8590
|
+
return config;
|
|
8591
|
+
}
|
|
8592
|
+
function updateConfig(patch) {
|
|
8593
|
+
return saveConfig({ ...loadConfig(), ...patch });
|
|
8594
|
+
}
|
|
8595
|
+
function normalizeApiUrl(value) {
|
|
8596
|
+
const trimmed = value?.trim();
|
|
8597
|
+
if (!trimmed)
|
|
8598
|
+
return null;
|
|
8599
|
+
return trimmed.replace(/\/+$/, "");
|
|
8600
|
+
}
|
|
8601
|
+
function getLocalApiConfig(env = process.env) {
|
|
8602
|
+
const config = loadConfig();
|
|
8603
|
+
const envApiUrl = normalizeApiUrl(env["TODOS_URL"]);
|
|
8604
|
+
const configApiUrl = normalizeApiUrl(config.apiUrl);
|
|
8605
|
+
const apiUrl = envApiUrl ?? configApiUrl;
|
|
8606
|
+
const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
|
|
8607
|
+
return {
|
|
8608
|
+
apiUrl,
|
|
8609
|
+
apiKey,
|
|
8610
|
+
source: {
|
|
8611
|
+
apiUrl: envApiUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
|
|
8612
|
+
apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
|
|
8613
|
+
}
|
|
8614
|
+
};
|
|
8615
|
+
}
|
|
8616
|
+
function getSyncAgentsFromConfig() {
|
|
8617
|
+
const config = loadConfig();
|
|
8618
|
+
const agents = config.sync_agents;
|
|
8619
|
+
if (Array.isArray(agents) && agents.length > 0)
|
|
8620
|
+
return agents.map(normalizeAgent);
|
|
8621
|
+
return null;
|
|
8622
|
+
}
|
|
8623
|
+
function getAgentTaskListId(agent) {
|
|
8624
|
+
const config = loadConfig();
|
|
8625
|
+
const key = normalizeAgent(agent);
|
|
8626
|
+
return config.agents?.[key]?.task_list_id || config.task_list_id || null;
|
|
8627
|
+
}
|
|
8628
|
+
function getAgentTasksDir(agent) {
|
|
8629
|
+
const config = loadConfig();
|
|
8630
|
+
const key = normalizeAgent(agent);
|
|
8631
|
+
return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
|
|
8632
|
+
}
|
|
8633
|
+
function getTaskPrefixConfig() {
|
|
8634
|
+
const config = loadConfig();
|
|
8635
|
+
return config.task_prefix || null;
|
|
8636
|
+
}
|
|
8637
|
+
function getAgentPoolForProject(workingDir) {
|
|
8638
|
+
const config = loadConfig();
|
|
8639
|
+
if (workingDir && config.project_pools) {
|
|
8640
|
+
let bestKey = null;
|
|
8641
|
+
let bestLen = 0;
|
|
8642
|
+
for (const key of Object.keys(config.project_pools)) {
|
|
8643
|
+
if (workingDir.startsWith(key) && key.length > bestLen) {
|
|
8644
|
+
bestKey = key;
|
|
8645
|
+
bestLen = key.length;
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
if (bestKey && config.project_pools[bestKey]) {
|
|
8649
|
+
return config.project_pools[bestKey];
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
8652
|
+
return config.agent_pool || null;
|
|
8653
|
+
}
|
|
8654
|
+
function getCompletionGuardConfig(projectPath) {
|
|
8655
|
+
const config = loadConfig();
|
|
8656
|
+
const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
8657
|
+
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
8658
|
+
return { ...global, ...config.project_overrides[projectPath].completion_guard };
|
|
8659
|
+
}
|
|
8660
|
+
return global;
|
|
8661
|
+
}
|
|
8662
|
+
var cached = null, GUARD_DEFAULTS;
|
|
8663
|
+
var init_config = __esm(() => {
|
|
8664
|
+
init_sync_utils();
|
|
8665
|
+
GUARD_DEFAULTS = {
|
|
8666
|
+
enabled: false,
|
|
8667
|
+
min_work_seconds: 30,
|
|
8668
|
+
max_completions_per_window: 5,
|
|
8669
|
+
window_minutes: 10,
|
|
8670
|
+
cooldown_seconds: 60
|
|
8671
|
+
};
|
|
8672
|
+
});
|
|
8673
|
+
|
|
8674
|
+
// src/lib/redaction.ts
|
|
8675
|
+
var exports_redaction = {};
|
|
8676
|
+
__export(exports_redaction, {
|
|
8677
|
+
upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
|
|
8678
|
+
redactValue: () => redactValue,
|
|
8679
|
+
redactEvidenceText: () => redactEvidenceText,
|
|
8680
|
+
listSecretFindings: () => listSecretFindings,
|
|
8681
|
+
hasSecretFindings: () => hasSecretFindings,
|
|
8682
|
+
getSecretSafetyConfig: () => getSecretSafetyConfig
|
|
8683
|
+
});
|
|
8684
|
+
function unique(values) {
|
|
8685
|
+
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
8686
|
+
}
|
|
8687
|
+
function cloneRegex(regex) {
|
|
8688
|
+
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
8689
|
+
}
|
|
8690
|
+
function customPatterns() {
|
|
8691
|
+
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
8692
|
+
try {
|
|
8693
|
+
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
8694
|
+
} catch {
|
|
8695
|
+
return [];
|
|
8696
|
+
}
|
|
8697
|
+
});
|
|
8698
|
+
}
|
|
8699
|
+
function secretPatterns() {
|
|
8700
|
+
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
8701
|
+
}
|
|
8702
|
+
function isSecretKey(key) {
|
|
8703
|
+
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
8704
|
+
return false;
|
|
8705
|
+
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
8706
|
+
return true;
|
|
8707
|
+
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
8708
|
+
}
|
|
8709
|
+
function redactEvidenceText(value) {
|
|
8710
|
+
let redacted = value;
|
|
8711
|
+
for (const pattern of secretPatterns()) {
|
|
8712
|
+
const regex = cloneRegex(pattern.regex);
|
|
8713
|
+
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
8714
|
+
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
8715
|
+
}
|
|
8716
|
+
return redacted;
|
|
8717
|
+
}
|
|
8718
|
+
function redactValue(value) {
|
|
8719
|
+
if (typeof value === "string")
|
|
8720
|
+
return redactEvidenceText(value);
|
|
8721
|
+
if (Array.isArray(value))
|
|
8722
|
+
return value.map(redactValue);
|
|
8723
|
+
if (value && typeof value === "object") {
|
|
8724
|
+
const redacted = {};
|
|
8725
|
+
for (const [key, child] of Object.entries(value)) {
|
|
8726
|
+
if (isSecretKey(key)) {
|
|
8727
|
+
redacted[key] = "[REDACTED]";
|
|
8728
|
+
} else {
|
|
8729
|
+
redacted[key] = redactValue(child);
|
|
8730
|
+
}
|
|
8731
|
+
}
|
|
8732
|
+
return redacted;
|
|
8733
|
+
}
|
|
8734
|
+
return value;
|
|
8735
|
+
}
|
|
8736
|
+
function listSecretFindings(value) {
|
|
8737
|
+
const findings = [];
|
|
8738
|
+
for (const pattern of secretPatterns()) {
|
|
8739
|
+
const matches = value.match(cloneRegex(pattern.regex));
|
|
8740
|
+
if (matches?.length)
|
|
8741
|
+
findings.push({ pattern: pattern.name, count: matches.length });
|
|
8742
|
+
}
|
|
8743
|
+
return findings;
|
|
8744
|
+
}
|
|
8745
|
+
function hasSecretFindings(value) {
|
|
8746
|
+
return listSecretFindings(value).length > 0;
|
|
8747
|
+
}
|
|
8748
|
+
function getSecretSafetyConfig() {
|
|
8749
|
+
return {
|
|
8750
|
+
redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
|
|
8751
|
+
redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
|
|
8752
|
+
};
|
|
8753
|
+
}
|
|
8754
|
+
function upsertSecretSafetyConfig(input) {
|
|
8755
|
+
const config = loadConfig();
|
|
8756
|
+
const next = {
|
|
8757
|
+
redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
|
|
8758
|
+
redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
|
|
8759
|
+
};
|
|
8760
|
+
saveConfig({ ...config, secret_safety: next });
|
|
8761
|
+
return next;
|
|
8762
|
+
}
|
|
8763
|
+
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
|
|
8764
|
+
var init_redaction = __esm(() => {
|
|
8765
|
+
init_config();
|
|
8766
|
+
DEFAULT_SECRET_PATTERNS = [
|
|
8767
|
+
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
8768
|
+
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
8769
|
+
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
8770
|
+
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
8771
|
+
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
8772
|
+
];
|
|
8773
|
+
DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
8774
|
+
NON_SECRET_USAGE_KEYS = new Set([
|
|
8775
|
+
"tokens",
|
|
8776
|
+
"total_tokens",
|
|
8777
|
+
"token_count",
|
|
8778
|
+
"input_tokens",
|
|
8779
|
+
"output_tokens",
|
|
8780
|
+
"prompt_tokens",
|
|
8781
|
+
"completion_tokens"
|
|
8782
|
+
]);
|
|
8783
|
+
});
|
|
8784
|
+
|
|
8481
8785
|
// src/cli/cloud-router.ts
|
|
8482
8786
|
function getTodosCloudClient(env = process.env) {
|
|
8483
8787
|
if (_cache !== undefined)
|
|
@@ -8503,14 +8807,18 @@ function unwrapTask(raw) {
|
|
|
8503
8807
|
}
|
|
8504
8808
|
function toListQuery(filter = {}) {
|
|
8505
8809
|
const query = {};
|
|
8506
|
-
if (
|
|
8507
|
-
query["status"] = filter.status;
|
|
8508
|
-
if (
|
|
8509
|
-
query["priority"] = filter.priority;
|
|
8810
|
+
if (filter.status)
|
|
8811
|
+
query["status"] = Array.isArray(filter.status) ? filter.status.join(",") : filter.status;
|
|
8812
|
+
if (filter.priority)
|
|
8813
|
+
query["priority"] = Array.isArray(filter.priority) ? filter.priority.join(",") : filter.priority;
|
|
8510
8814
|
if (filter.project_id)
|
|
8511
8815
|
query["project_id"] = filter.project_id;
|
|
8816
|
+
if (filter.parent_id !== undefined)
|
|
8817
|
+
query["parent_id"] = filter.parent_id ?? "";
|
|
8512
8818
|
if (filter.plan_id)
|
|
8513
8819
|
query["plan_id"] = filter.plan_id;
|
|
8820
|
+
if (filter.task_list_id)
|
|
8821
|
+
query["task_list_id"] = filter.task_list_id;
|
|
8514
8822
|
if (filter.assigned_to)
|
|
8515
8823
|
query["assigned_to"] = filter.assigned_to;
|
|
8516
8824
|
if (filter.agent_id)
|
|
@@ -8566,10 +8874,97 @@ async function cloudListPlans(client, projectId) {
|
|
|
8566
8874
|
}
|
|
8567
8875
|
async function cloudAddComment(client, taskId, input) {
|
|
8568
8876
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/comments`, input);
|
|
8569
|
-
|
|
8570
|
-
|
|
8877
|
+
const comment = raw && typeof raw === "object" && "comment" in raw ? raw.comment : raw;
|
|
8878
|
+
if (!isTaskComment(comment))
|
|
8879
|
+
throw new Error("Invalid cloud comment response");
|
|
8880
|
+
return redactComment(comment);
|
|
8881
|
+
}
|
|
8882
|
+
async function cloudListComments(client, taskId, options = {}) {
|
|
8883
|
+
const limit = options.limit ?? 100;
|
|
8884
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
|
|
8885
|
+
throw new Error("Cloud comment limit must be an integer between 1 and 500");
|
|
8571
8886
|
}
|
|
8572
|
-
|
|
8887
|
+
if (options.cursor !== undefined && (typeof options.cursor !== "string" || !options.cursor || options.cursor.length > 1024)) {
|
|
8888
|
+
throw new Error("Cloud comment cursor must be a non-empty string");
|
|
8889
|
+
}
|
|
8890
|
+
let raw;
|
|
8891
|
+
try {
|
|
8892
|
+
raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/comments`, {
|
|
8893
|
+
query: { limit, ...options.cursor ? { cursor: options.cursor } : {} }
|
|
8894
|
+
});
|
|
8895
|
+
} catch (error) {
|
|
8896
|
+
const status = error && typeof error === "object" ? error.status : undefined;
|
|
8897
|
+
if (status === 404 || status === 405) {
|
|
8898
|
+
throw new Error("Cloud task comments require a compatible @hasna/todos server; deploy the server endpoint before this CLI.", { cause: error });
|
|
8899
|
+
}
|
|
8900
|
+
throw error;
|
|
8901
|
+
}
|
|
8902
|
+
const envelope = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
8903
|
+
const candidate = Array.isArray(raw) ? raw : envelope?.comments;
|
|
8904
|
+
if (!Array.isArray(candidate) || !candidate.every(isTaskComment)) {
|
|
8905
|
+
throw new Error("Invalid cloud comments response");
|
|
8906
|
+
}
|
|
8907
|
+
if (envelope?.count !== undefined && (!Number.isSafeInteger(envelope.count) || envelope.count < 0 || envelope.count !== candidate.length)) {
|
|
8908
|
+
throw new Error("Invalid cloud comments response count");
|
|
8909
|
+
}
|
|
8910
|
+
const hasHasMore = envelope ? Object.prototype.hasOwnProperty.call(envelope, "has_more") : false;
|
|
8911
|
+
const hasNextCursor = envelope ? Object.prototype.hasOwnProperty.call(envelope, "next_cursor") : false;
|
|
8912
|
+
if (hasHasMore !== hasNextCursor)
|
|
8913
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8914
|
+
const paginationSupported = hasHasMore && hasNextCursor;
|
|
8915
|
+
if (!paginationSupported) {
|
|
8916
|
+
const comments = candidate.slice(-limit).map(redactComment);
|
|
8917
|
+
return {
|
|
8918
|
+
comments,
|
|
8919
|
+
count: comments.length,
|
|
8920
|
+
has_more: candidate.length > limit,
|
|
8921
|
+
next_cursor: null,
|
|
8922
|
+
limit,
|
|
8923
|
+
pagination_supported: false
|
|
8924
|
+
};
|
|
8925
|
+
}
|
|
8926
|
+
if (candidate.length > limit)
|
|
8927
|
+
throw new Error("Invalid cloud comments response: page exceeds requested limit");
|
|
8928
|
+
const hasMore = envelope.has_more;
|
|
8929
|
+
const nextCursor = envelope.next_cursor;
|
|
8930
|
+
if (typeof hasMore !== "boolean" || nextCursor !== null && (typeof nextCursor !== "string" || !nextCursor)) {
|
|
8931
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8932
|
+
}
|
|
8933
|
+
if (hasMore && nextCursor === null || !hasMore && nextCursor !== null) {
|
|
8934
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8935
|
+
}
|
|
8936
|
+
return {
|
|
8937
|
+
comments: candidate.map(redactComment),
|
|
8938
|
+
count: candidate.length,
|
|
8939
|
+
has_more: hasMore,
|
|
8940
|
+
next_cursor: nextCursor,
|
|
8941
|
+
limit,
|
|
8942
|
+
pagination_supported: true
|
|
8943
|
+
};
|
|
8944
|
+
}
|
|
8945
|
+
function isTaskComment(value) {
|
|
8946
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8947
|
+
return false;
|
|
8948
|
+
const comment = value;
|
|
8949
|
+
return typeof comment["id"] === "string" && typeof comment["task_id"] === "string" && (comment["agent_id"] === null || typeof comment["agent_id"] === "string") && (comment["session_id"] === null || typeof comment["session_id"] === "string") && typeof comment["content"] === "string" && (comment["type"] === "comment" || comment["type"] === "progress" || comment["type"] === "note") && (comment["progress_pct"] === null || typeof comment["progress_pct"] === "number") && typeof comment["created_at"] === "string";
|
|
8950
|
+
}
|
|
8951
|
+
function redactComment(comment) {
|
|
8952
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
8953
|
+
}
|
|
8954
|
+
async function cloudTaskHistory(client, taskId) {
|
|
8955
|
+
const raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/history`);
|
|
8956
|
+
const envelope = raw ?? {};
|
|
8957
|
+
if (Array.isArray(envelope.history))
|
|
8958
|
+
return envelope.history;
|
|
8959
|
+
return Array.isArray(raw) ? raw : [];
|
|
8960
|
+
}
|
|
8961
|
+
async function cloudUpsertTaskByFingerprint(client, input) {
|
|
8962
|
+
const raw = await client.transport.post("/tasks/upsert", input);
|
|
8963
|
+
const envelope = raw ?? {};
|
|
8964
|
+
return {
|
|
8965
|
+
task: unwrapTask(envelope.task ?? raw),
|
|
8966
|
+
created: Boolean(envelope.created)
|
|
8967
|
+
};
|
|
8573
8968
|
}
|
|
8574
8969
|
async function cloudCountTasks(client, filter = {}) {
|
|
8575
8970
|
const { limit: _drop, offset: _o, ...rest } = filter;
|
|
@@ -8642,8 +9037,8 @@ async function cloudLockTask(client, id, agentId) {
|
|
|
8642
9037
|
}
|
|
8643
9038
|
return raw ?? { success: true };
|
|
8644
9039
|
}
|
|
8645
|
-
async function cloudUnlockTask(client, id, agentId) {
|
|
8646
|
-
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, agentId ? { agent_id: agentId } : {});
|
|
9040
|
+
async function cloudUnlockTask(client, id, agentId, force = false) {
|
|
9041
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, { ...agentId ? { agent_id: agentId } : {}, ...force ? { force: true } : {} });
|
|
8647
9042
|
if (raw && typeof raw === "object" && "success" in raw) {
|
|
8648
9043
|
return Boolean(raw.success);
|
|
8649
9044
|
}
|
|
@@ -8772,6 +9167,25 @@ async function cloudListTaskLists(client, projectId) {
|
|
|
8772
9167
|
return envelope.taskLists;
|
|
8773
9168
|
return Array.isArray(raw) ? raw : [];
|
|
8774
9169
|
}
|
|
9170
|
+
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
9171
|
+
const lists = await cloudListTaskLists(client, projectId);
|
|
9172
|
+
const exact = lists.find((list) => list.id === ref || list.slug === ref);
|
|
9173
|
+
if (exact)
|
|
9174
|
+
return exact.id;
|
|
9175
|
+
const prefixes = lists.filter((list) => list.id.startsWith(ref));
|
|
9176
|
+
return prefixes.length === 1 ? prefixes[0].id : null;
|
|
9177
|
+
}
|
|
9178
|
+
async function cloudCreateTaskList(client, input) {
|
|
9179
|
+
const raw = await client.transport.post("/task-lists", input);
|
|
9180
|
+
if (raw && typeof raw === "object" && "task_list" in raw) {
|
|
9181
|
+
return raw.task_list;
|
|
9182
|
+
}
|
|
9183
|
+
return raw;
|
|
9184
|
+
}
|
|
9185
|
+
async function cloudDeleteTaskList(client, id) {
|
|
9186
|
+
await client.delete("task-lists", id);
|
|
9187
|
+
return true;
|
|
9188
|
+
}
|
|
8775
9189
|
async function cloudNextTask(client, agent, filters) {
|
|
8776
9190
|
const query = {};
|
|
8777
9191
|
if (agent)
|
|
@@ -8796,11 +9210,11 @@ async function cloudAllDependencies(client) {
|
|
|
8796
9210
|
return Array.isArray(raw) ? raw : [];
|
|
8797
9211
|
}
|
|
8798
9212
|
async function cloudGetTasksByIds(client, ids) {
|
|
8799
|
-
const
|
|
9213
|
+
const unique2 = Array.from(new Set(ids));
|
|
8800
9214
|
const map = new Map;
|
|
8801
9215
|
const CONCURRENCY = 8;
|
|
8802
|
-
for (let i = 0;i <
|
|
8803
|
-
const batch =
|
|
9216
|
+
for (let i = 0;i < unique2.length; i += CONCURRENCY) {
|
|
9217
|
+
const batch = unique2.slice(i, i + CONCURRENCY);
|
|
8804
9218
|
const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
|
|
8805
9219
|
for (const task of tasks)
|
|
8806
9220
|
if (task && task.id)
|
|
@@ -8898,6 +9312,7 @@ async function cloudTimeline(client, options = {}) {
|
|
|
8898
9312
|
var _cache, PRIORITY_RANK;
|
|
8899
9313
|
var init_cloud_router = __esm(() => {
|
|
8900
9314
|
init_storage();
|
|
9315
|
+
init_redaction();
|
|
8901
9316
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
8902
9317
|
});
|
|
8903
9318
|
|
|
@@ -11200,7 +11615,7 @@ var init_schema = __esm(() => {
|
|
|
11200
11615
|
});
|
|
11201
11616
|
|
|
11202
11617
|
// src/db/machines.ts
|
|
11203
|
-
import { existsSync as
|
|
11618
|
+
import { existsSync as existsSync4 } from "fs";
|
|
11204
11619
|
import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
|
|
11205
11620
|
import { resolve } from "path";
|
|
11206
11621
|
import { spawnSync } from "child_process";
|
|
@@ -11407,7 +11822,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11407
11822
|
message: `${project.name} has ${distinctPaths.length} different machine-local paths`
|
|
11408
11823
|
});
|
|
11409
11824
|
}
|
|
11410
|
-
if (localRow && !
|
|
11825
|
+
if (localRow && !existsSync4(localRow.path)) {
|
|
11411
11826
|
pathIssues.push({
|
|
11412
11827
|
type: "path_missing",
|
|
11413
11828
|
project_id: project.id,
|
|
@@ -11418,7 +11833,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11418
11833
|
message: `Local path does not exist on this machine: ${localRow.path}`
|
|
11419
11834
|
});
|
|
11420
11835
|
}
|
|
11421
|
-
if (!localRow && project.path && machineById.has(localMachine.id) && !
|
|
11836
|
+
if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync4(project.path)) {
|
|
11422
11837
|
pathIssues.push({
|
|
11423
11838
|
type: "path_missing",
|
|
11424
11839
|
project_id: project.id,
|
|
@@ -11526,8 +11941,8 @@ var init_machines = __esm(() => {
|
|
|
11526
11941
|
});
|
|
11527
11942
|
|
|
11528
11943
|
// src/storage/config.ts
|
|
11529
|
-
var
|
|
11530
|
-
__export(
|
|
11944
|
+
var exports_config2 = {};
|
|
11945
|
+
__export(exports_config2, {
|
|
11531
11946
|
parseStorageMode: () => parseStorageMode,
|
|
11532
11947
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
11533
11948
|
loadStorageConfig: () => loadStorageConfig,
|
|
@@ -11690,7 +12105,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
11690
12105
|
return parsed;
|
|
11691
12106
|
}
|
|
11692
12107
|
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER = "hasna-xyz-infra-apps-prod-postgres", CANONICAL_TODOS_RDS_DATABASE = "todos", CANONICAL_TODOS_RDS_RUNTIME_PATH = "hasna/xyz/opensource/todos/prod/rds";
|
|
11693
|
-
var
|
|
12108
|
+
var init_config2 = __esm(() => {
|
|
11694
12109
|
TODOS_STORAGE_TABLES = [
|
|
11695
12110
|
"todos_sync_records",
|
|
11696
12111
|
"todos_sync_cursors"
|
|
@@ -11829,8 +12244,8 @@ __export(exports_database, {
|
|
|
11829
12244
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
11830
12245
|
});
|
|
11831
12246
|
import { Database } from "bun:sqlite";
|
|
11832
|
-
import { existsSync as
|
|
11833
|
-
import { dirname as
|
|
12247
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
12248
|
+
import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
11834
12249
|
function isInMemoryDb(path) {
|
|
11835
12250
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
11836
12251
|
}
|
|
@@ -11839,12 +12254,12 @@ function findNearestProjectDb(startDir) {
|
|
|
11839
12254
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
11840
12255
|
let dir = resolve2(startDir);
|
|
11841
12256
|
while (true) {
|
|
11842
|
-
const candidate =
|
|
11843
|
-
if (
|
|
12257
|
+
const candidate = join4(dir, ".hasna", "todos", "todos.db");
|
|
12258
|
+
if (existsSync5(candidate))
|
|
11844
12259
|
return candidate;
|
|
11845
12260
|
if (dir === stopAt)
|
|
11846
12261
|
break;
|
|
11847
|
-
const parent =
|
|
12262
|
+
const parent = dirname3(dir);
|
|
11848
12263
|
if (parent === dir)
|
|
11849
12264
|
break;
|
|
11850
12265
|
dir = parent;
|
|
@@ -11854,9 +12269,9 @@ function findNearestProjectDb(startDir) {
|
|
|
11854
12269
|
function findGitRoot(startDir) {
|
|
11855
12270
|
let dir = resolve2(startDir);
|
|
11856
12271
|
while (true) {
|
|
11857
|
-
if (
|
|
12272
|
+
if (existsSync5(join4(dir, ".git")))
|
|
11858
12273
|
return dir;
|
|
11859
|
-
const parent =
|
|
12274
|
+
const parent = dirname3(dir);
|
|
11860
12275
|
if (parent === dir)
|
|
11861
12276
|
break;
|
|
11862
12277
|
dir = parent;
|
|
@@ -11877,25 +12292,25 @@ function getDbPath() {
|
|
|
11877
12292
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
11878
12293
|
const gitRoot = findGitRoot(cwd);
|
|
11879
12294
|
if (gitRoot) {
|
|
11880
|
-
return
|
|
12295
|
+
return join4(gitRoot, ".hasna", "todos", "todos.db");
|
|
11881
12296
|
}
|
|
11882
12297
|
}
|
|
11883
12298
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
11884
|
-
return
|
|
12299
|
+
return join4(home, ".hasna", "todos", "todos.db");
|
|
11885
12300
|
}
|
|
11886
12301
|
function getDatabasePath() {
|
|
11887
12302
|
return getDbPath();
|
|
11888
12303
|
}
|
|
11889
|
-
function
|
|
12304
|
+
function ensureDir2(filePath) {
|
|
11890
12305
|
if (isInMemoryDb(filePath))
|
|
11891
12306
|
return;
|
|
11892
|
-
const dir =
|
|
11893
|
-
if (!
|
|
11894
|
-
|
|
12307
|
+
const dir = dirname3(resolve2(filePath));
|
|
12308
|
+
if (!existsSync5(dir)) {
|
|
12309
|
+
mkdirSync2(dir, { recursive: true });
|
|
11895
12310
|
}
|
|
11896
12311
|
}
|
|
11897
12312
|
function openDatabase(path) {
|
|
11898
|
-
|
|
12313
|
+
ensureDir2(path);
|
|
11899
12314
|
const db = new Database(path);
|
|
11900
12315
|
db.run("PRAGMA journal_mode = WAL");
|
|
11901
12316
|
db.run("PRAGMA busy_timeout = 5000");
|
|
@@ -11908,7 +12323,7 @@ function openDatabase(path) {
|
|
|
11908
12323
|
}
|
|
11909
12324
|
function maybeInstallShadowCapture(db) {
|
|
11910
12325
|
try {
|
|
11911
|
-
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (
|
|
12326
|
+
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (init_config2(), __toCommonJS(exports_config2));
|
|
11912
12327
|
if (!isTodosShadowEnabled2())
|
|
11913
12328
|
return;
|
|
11914
12329
|
const { installShadowOutboxSchema: installShadowOutboxSchema2 } = (init_shadow_outbox_schema(), __toCommonJS(exports_shadow_outbox_schema));
|
|
@@ -12689,199 +13104,6 @@ var init_helpers = __esm(() => {
|
|
|
12689
13104
|
};
|
|
12690
13105
|
});
|
|
12691
13106
|
|
|
12692
|
-
// src/lib/sync-utils.ts
|
|
12693
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
12694
|
-
import { join as join3 } from "path";
|
|
12695
|
-
function getHomeDir() {
|
|
12696
|
-
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
12697
|
-
}
|
|
12698
|
-
function getTodosGlobalDir() {
|
|
12699
|
-
return join3(getHomeDir(), ".hasna", "todos");
|
|
12700
|
-
}
|
|
12701
|
-
function ensureDir2(dir) {
|
|
12702
|
-
if (!existsSync4(dir))
|
|
12703
|
-
mkdirSync2(dir, { recursive: true });
|
|
12704
|
-
}
|
|
12705
|
-
function listJsonFiles(dir) {
|
|
12706
|
-
if (!existsSync4(dir))
|
|
12707
|
-
return [];
|
|
12708
|
-
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
12709
|
-
}
|
|
12710
|
-
function readJsonFile(path) {
|
|
12711
|
-
try {
|
|
12712
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
12713
|
-
} catch {
|
|
12714
|
-
return null;
|
|
12715
|
-
}
|
|
12716
|
-
}
|
|
12717
|
-
function writeJsonFile(path, data) {
|
|
12718
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
12719
|
-
`);
|
|
12720
|
-
}
|
|
12721
|
-
function readHighWaterMark(dir) {
|
|
12722
|
-
const path = join3(dir, ".highwatermark");
|
|
12723
|
-
if (!existsSync4(path))
|
|
12724
|
-
return 1;
|
|
12725
|
-
const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
|
|
12726
|
-
return isNaN(val) ? 1 : val;
|
|
12727
|
-
}
|
|
12728
|
-
function writeHighWaterMark(dir, value) {
|
|
12729
|
-
writeFileSync(join3(dir, ".highwatermark"), String(value));
|
|
12730
|
-
}
|
|
12731
|
-
function getFileMtimeMs(path) {
|
|
12732
|
-
try {
|
|
12733
|
-
return statSync(path).mtimeMs;
|
|
12734
|
-
} catch {
|
|
12735
|
-
return null;
|
|
12736
|
-
}
|
|
12737
|
-
}
|
|
12738
|
-
function parseTimestamp(value) {
|
|
12739
|
-
if (typeof value !== "string")
|
|
12740
|
-
return null;
|
|
12741
|
-
const parsed = Date.parse(value);
|
|
12742
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
12743
|
-
}
|
|
12744
|
-
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
12745
|
-
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
12746
|
-
const next = [conflict, ...current].slice(0, limit);
|
|
12747
|
-
return { ...metadata, sync_conflicts: next };
|
|
12748
|
-
}
|
|
12749
|
-
var HOME;
|
|
12750
|
-
var init_sync_utils = __esm(() => {
|
|
12751
|
-
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
12752
|
-
});
|
|
12753
|
-
|
|
12754
|
-
// src/lib/config.ts
|
|
12755
|
-
var exports_config2 = {};
|
|
12756
|
-
__export(exports_config2, {
|
|
12757
|
-
updateConfig: () => updateConfig,
|
|
12758
|
-
saveConfig: () => saveConfig,
|
|
12759
|
-
resetConfig: () => resetConfig,
|
|
12760
|
-
normalizeApiUrl: () => normalizeApiUrl,
|
|
12761
|
-
loadConfig: () => loadConfig,
|
|
12762
|
-
getTaskPrefixConfig: () => getTaskPrefixConfig,
|
|
12763
|
-
getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
|
|
12764
|
-
getLocalApiConfig: () => getLocalApiConfig,
|
|
12765
|
-
getConfigPath: () => getConfigPath,
|
|
12766
|
-
getCompletionGuardConfig: () => getCompletionGuardConfig,
|
|
12767
|
-
getAgentTasksDir: () => getAgentTasksDir,
|
|
12768
|
-
getAgentTaskListId: () => getAgentTaskListId,
|
|
12769
|
-
getAgentPoolForProject: () => getAgentPoolForProject
|
|
12770
|
-
});
|
|
12771
|
-
import { existsSync as existsSync5 } from "fs";
|
|
12772
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
12773
|
-
function getConfigPath() {
|
|
12774
|
-
return join4(getTodosGlobalDir(), "config.json");
|
|
12775
|
-
}
|
|
12776
|
-
function resetConfig() {
|
|
12777
|
-
cached = null;
|
|
12778
|
-
}
|
|
12779
|
-
function normalizeAgent(agent) {
|
|
12780
|
-
return agent.trim().toLowerCase();
|
|
12781
|
-
}
|
|
12782
|
-
function loadConfig() {
|
|
12783
|
-
if (cached)
|
|
12784
|
-
return cached;
|
|
12785
|
-
if (!existsSync5(getConfigPath())) {
|
|
12786
|
-
cached = {};
|
|
12787
|
-
return cached;
|
|
12788
|
-
}
|
|
12789
|
-
const config = readJsonFile(getConfigPath()) || {};
|
|
12790
|
-
if (typeof config.sync_agents === "string") {
|
|
12791
|
-
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
12792
|
-
}
|
|
12793
|
-
cached = config;
|
|
12794
|
-
return cached;
|
|
12795
|
-
}
|
|
12796
|
-
function saveConfig(config) {
|
|
12797
|
-
const configPath = getConfigPath();
|
|
12798
|
-
ensureDir2(dirname3(configPath));
|
|
12799
|
-
writeJsonFile(configPath, config);
|
|
12800
|
-
cached = config;
|
|
12801
|
-
return config;
|
|
12802
|
-
}
|
|
12803
|
-
function updateConfig(patch) {
|
|
12804
|
-
return saveConfig({ ...loadConfig(), ...patch });
|
|
12805
|
-
}
|
|
12806
|
-
function normalizeApiUrl(value) {
|
|
12807
|
-
const trimmed = value?.trim();
|
|
12808
|
-
if (!trimmed)
|
|
12809
|
-
return null;
|
|
12810
|
-
return trimmed.replace(/\/+$/, "");
|
|
12811
|
-
}
|
|
12812
|
-
function getLocalApiConfig(env = process.env) {
|
|
12813
|
-
const config = loadConfig();
|
|
12814
|
-
const envApiUrl = normalizeApiUrl(env["TODOS_URL"]);
|
|
12815
|
-
const configApiUrl = normalizeApiUrl(config.apiUrl);
|
|
12816
|
-
const apiUrl = envApiUrl ?? configApiUrl;
|
|
12817
|
-
const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
|
|
12818
|
-
return {
|
|
12819
|
-
apiUrl,
|
|
12820
|
-
apiKey,
|
|
12821
|
-
source: {
|
|
12822
|
-
apiUrl: envApiUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
|
|
12823
|
-
apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
|
|
12824
|
-
}
|
|
12825
|
-
};
|
|
12826
|
-
}
|
|
12827
|
-
function getSyncAgentsFromConfig() {
|
|
12828
|
-
const config = loadConfig();
|
|
12829
|
-
const agents = config.sync_agents;
|
|
12830
|
-
if (Array.isArray(agents) && agents.length > 0)
|
|
12831
|
-
return agents.map(normalizeAgent);
|
|
12832
|
-
return null;
|
|
12833
|
-
}
|
|
12834
|
-
function getAgentTaskListId(agent) {
|
|
12835
|
-
const config = loadConfig();
|
|
12836
|
-
const key = normalizeAgent(agent);
|
|
12837
|
-
return config.agents?.[key]?.task_list_id || config.task_list_id || null;
|
|
12838
|
-
}
|
|
12839
|
-
function getAgentTasksDir(agent) {
|
|
12840
|
-
const config = loadConfig();
|
|
12841
|
-
const key = normalizeAgent(agent);
|
|
12842
|
-
return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
|
|
12843
|
-
}
|
|
12844
|
-
function getTaskPrefixConfig() {
|
|
12845
|
-
const config = loadConfig();
|
|
12846
|
-
return config.task_prefix || null;
|
|
12847
|
-
}
|
|
12848
|
-
function getAgentPoolForProject(workingDir) {
|
|
12849
|
-
const config = loadConfig();
|
|
12850
|
-
if (workingDir && config.project_pools) {
|
|
12851
|
-
let bestKey = null;
|
|
12852
|
-
let bestLen = 0;
|
|
12853
|
-
for (const key of Object.keys(config.project_pools)) {
|
|
12854
|
-
if (workingDir.startsWith(key) && key.length > bestLen) {
|
|
12855
|
-
bestKey = key;
|
|
12856
|
-
bestLen = key.length;
|
|
12857
|
-
}
|
|
12858
|
-
}
|
|
12859
|
-
if (bestKey && config.project_pools[bestKey]) {
|
|
12860
|
-
return config.project_pools[bestKey];
|
|
12861
|
-
}
|
|
12862
|
-
}
|
|
12863
|
-
return config.agent_pool || null;
|
|
12864
|
-
}
|
|
12865
|
-
function getCompletionGuardConfig(projectPath) {
|
|
12866
|
-
const config = loadConfig();
|
|
12867
|
-
const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
12868
|
-
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
12869
|
-
return { ...global, ...config.project_overrides[projectPath].completion_guard };
|
|
12870
|
-
}
|
|
12871
|
-
return global;
|
|
12872
|
-
}
|
|
12873
|
-
var cached = null, GUARD_DEFAULTS;
|
|
12874
|
-
var init_config2 = __esm(() => {
|
|
12875
|
-
init_sync_utils();
|
|
12876
|
-
GUARD_DEFAULTS = {
|
|
12877
|
-
enabled: false,
|
|
12878
|
-
min_work_seconds: 30,
|
|
12879
|
-
max_completions_per_window: 5,
|
|
12880
|
-
window_minutes: 10,
|
|
12881
|
-
cooldown_seconds: 60
|
|
12882
|
-
};
|
|
12883
|
-
});
|
|
12884
|
-
|
|
12885
13107
|
// src/lib/completion-guard.ts
|
|
12886
13108
|
function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
12887
13109
|
let config;
|
|
@@ -12927,7 +13149,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
12927
13149
|
}
|
|
12928
13150
|
var init_completion_guard = __esm(() => {
|
|
12929
13151
|
init_types();
|
|
12930
|
-
|
|
13152
|
+
init_config();
|
|
12931
13153
|
init_projects();
|
|
12932
13154
|
});
|
|
12933
13155
|
|
|
@@ -12982,117 +13204,6 @@ var init_event_emission_safety = __esm(() => {
|
|
|
12982
13204
|
init_sync_utils();
|
|
12983
13205
|
});
|
|
12984
13206
|
|
|
12985
|
-
// src/lib/redaction.ts
|
|
12986
|
-
var exports_redaction = {};
|
|
12987
|
-
__export(exports_redaction, {
|
|
12988
|
-
upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
|
|
12989
|
-
redactValue: () => redactValue,
|
|
12990
|
-
redactEvidenceText: () => redactEvidenceText,
|
|
12991
|
-
listSecretFindings: () => listSecretFindings,
|
|
12992
|
-
hasSecretFindings: () => hasSecretFindings,
|
|
12993
|
-
getSecretSafetyConfig: () => getSecretSafetyConfig
|
|
12994
|
-
});
|
|
12995
|
-
function unique(values) {
|
|
12996
|
-
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
12997
|
-
}
|
|
12998
|
-
function cloneRegex(regex) {
|
|
12999
|
-
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
13000
|
-
}
|
|
13001
|
-
function customPatterns() {
|
|
13002
|
-
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
13003
|
-
try {
|
|
13004
|
-
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
13005
|
-
} catch {
|
|
13006
|
-
return [];
|
|
13007
|
-
}
|
|
13008
|
-
});
|
|
13009
|
-
}
|
|
13010
|
-
function secretPatterns() {
|
|
13011
|
-
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
13012
|
-
}
|
|
13013
|
-
function isSecretKey(key) {
|
|
13014
|
-
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
13015
|
-
return false;
|
|
13016
|
-
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
13017
|
-
return true;
|
|
13018
|
-
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
13019
|
-
}
|
|
13020
|
-
function redactEvidenceText(value) {
|
|
13021
|
-
let redacted = value;
|
|
13022
|
-
for (const pattern of secretPatterns()) {
|
|
13023
|
-
const regex = cloneRegex(pattern.regex);
|
|
13024
|
-
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
13025
|
-
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
13026
|
-
}
|
|
13027
|
-
return redacted;
|
|
13028
|
-
}
|
|
13029
|
-
function redactValue(value) {
|
|
13030
|
-
if (typeof value === "string")
|
|
13031
|
-
return redactEvidenceText(value);
|
|
13032
|
-
if (Array.isArray(value))
|
|
13033
|
-
return value.map(redactValue);
|
|
13034
|
-
if (value && typeof value === "object") {
|
|
13035
|
-
const redacted = {};
|
|
13036
|
-
for (const [key, child] of Object.entries(value)) {
|
|
13037
|
-
if (isSecretKey(key)) {
|
|
13038
|
-
redacted[key] = "[REDACTED]";
|
|
13039
|
-
} else {
|
|
13040
|
-
redacted[key] = redactValue(child);
|
|
13041
|
-
}
|
|
13042
|
-
}
|
|
13043
|
-
return redacted;
|
|
13044
|
-
}
|
|
13045
|
-
return value;
|
|
13046
|
-
}
|
|
13047
|
-
function listSecretFindings(value) {
|
|
13048
|
-
const findings = [];
|
|
13049
|
-
for (const pattern of secretPatterns()) {
|
|
13050
|
-
const matches = value.match(cloneRegex(pattern.regex));
|
|
13051
|
-
if (matches?.length)
|
|
13052
|
-
findings.push({ pattern: pattern.name, count: matches.length });
|
|
13053
|
-
}
|
|
13054
|
-
return findings;
|
|
13055
|
-
}
|
|
13056
|
-
function hasSecretFindings(value) {
|
|
13057
|
-
return listSecretFindings(value).length > 0;
|
|
13058
|
-
}
|
|
13059
|
-
function getSecretSafetyConfig() {
|
|
13060
|
-
return {
|
|
13061
|
-
redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
|
|
13062
|
-
redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
|
|
13063
|
-
};
|
|
13064
|
-
}
|
|
13065
|
-
function upsertSecretSafetyConfig(input) {
|
|
13066
|
-
const config = loadConfig();
|
|
13067
|
-
const next = {
|
|
13068
|
-
redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
|
|
13069
|
-
redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
|
|
13070
|
-
};
|
|
13071
|
-
saveConfig({ ...config, secret_safety: next });
|
|
13072
|
-
return next;
|
|
13073
|
-
}
|
|
13074
|
-
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
|
|
13075
|
-
var init_redaction = __esm(() => {
|
|
13076
|
-
init_config2();
|
|
13077
|
-
DEFAULT_SECRET_PATTERNS = [
|
|
13078
|
-
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
13079
|
-
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
13080
|
-
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
13081
|
-
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
13082
|
-
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
13083
|
-
];
|
|
13084
|
-
DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
13085
|
-
NON_SECRET_USAGE_KEYS = new Set([
|
|
13086
|
-
"tokens",
|
|
13087
|
-
"total_tokens",
|
|
13088
|
-
"token_count",
|
|
13089
|
-
"input_tokens",
|
|
13090
|
-
"output_tokens",
|
|
13091
|
-
"prompt_tokens",
|
|
13092
|
-
"completion_tokens"
|
|
13093
|
-
]);
|
|
13094
|
-
});
|
|
13095
|
-
|
|
13096
13207
|
// src/lib/workspace-trust.ts
|
|
13097
13208
|
var exports_workspace_trust = {};
|
|
13098
13209
|
__export(exports_workspace_trust, {
|
|
@@ -13238,7 +13349,7 @@ function checkWorkspacePermission(input = {}) {
|
|
|
13238
13349
|
}
|
|
13239
13350
|
var DEFAULT_DENYLIST, DEFAULT_ENV_REDACTIONS, PRESET_DEFAULTS;
|
|
13240
13351
|
var init_workspace_trust = __esm(() => {
|
|
13241
|
-
|
|
13352
|
+
init_config();
|
|
13242
13353
|
DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13243
13354
|
DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
13244
13355
|
PRESET_DEFAULTS = {
|
|
@@ -13481,7 +13592,7 @@ function explainRunnerSandbox(input = {}) {
|
|
|
13481
13592
|
}
|
|
13482
13593
|
var DEFAULT_COMMAND_DENYLIST, DEFAULT_ENV_REDACTIONS2;
|
|
13483
13594
|
var init_runner_sandbox = __esm(() => {
|
|
13484
|
-
|
|
13595
|
+
init_config();
|
|
13485
13596
|
init_workspace_trust();
|
|
13486
13597
|
DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13487
13598
|
DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
@@ -13728,7 +13839,7 @@ var LOCAL_EVENT_TYPES, VALID_TARGETS;
|
|
|
13728
13839
|
var init_event_hooks = __esm(() => {
|
|
13729
13840
|
init_redaction();
|
|
13730
13841
|
init_runner_sandbox();
|
|
13731
|
-
|
|
13842
|
+
init_config();
|
|
13732
13843
|
init_event_emission_safety();
|
|
13733
13844
|
LOCAL_EVENT_TYPES = [
|
|
13734
13845
|
"task.created",
|
|
@@ -18613,7 +18724,7 @@ function getComment(id, db) {
|
|
|
18613
18724
|
}
|
|
18614
18725
|
function listComments(taskId, db) {
|
|
18615
18726
|
const d = db || getDatabase();
|
|
18616
|
-
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
18727
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
|
|
18617
18728
|
}
|
|
18618
18729
|
function updateComment(id, input, db) {
|
|
18619
18730
|
const d = db || getDatabase();
|
|
@@ -20241,10 +20352,27 @@ var init_task_routing = __esm(() => {
|
|
|
20241
20352
|
// src/cli/commands/task-commands.ts
|
|
20242
20353
|
var exports_task_commands = {};
|
|
20243
20354
|
__export(exports_task_commands, {
|
|
20244
|
-
registerTaskCommands: () => registerTaskCommands
|
|
20355
|
+
registerTaskCommands: () => registerTaskCommands,
|
|
20356
|
+
escapeTerminalControls: () => escapeTerminalControls
|
|
20245
20357
|
});
|
|
20246
20358
|
import chalk2 from "chalk";
|
|
20247
20359
|
import { basename as basename3, resolve as resolve9 } from "path";
|
|
20360
|
+
function escapeTerminalControls(value) {
|
|
20361
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
|
|
20362
|
+
const code = character.charCodeAt(0);
|
|
20363
|
+
if (code === 10)
|
|
20364
|
+
return "\\n";
|
|
20365
|
+
if (code === 13)
|
|
20366
|
+
return "\\r";
|
|
20367
|
+
if (code === 9)
|
|
20368
|
+
return "\\t";
|
|
20369
|
+
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
20370
|
+
});
|
|
20371
|
+
}
|
|
20372
|
+
function formatHumanComment(comment) {
|
|
20373
|
+
const agent = comment.agent_id ? chalk2.cyan(`[${escapeTerminalControls(comment.agent_id)}] `) : "";
|
|
20374
|
+
return ` ${agent}${chalk2.dim(escapeTerminalControls(comment.created_at))}: ${escapeTerminalControls(comment.content)}`;
|
|
20375
|
+
}
|
|
20248
20376
|
function resolveProjectIdOrSlug(input) {
|
|
20249
20377
|
const db = getDatabase();
|
|
20250
20378
|
if (isPathLike(input)) {
|
|
@@ -20401,18 +20529,24 @@ function registerTaskCommands(program2) {
|
|
|
20401
20529
|
if (cloud) {
|
|
20402
20530
|
let task3;
|
|
20403
20531
|
try {
|
|
20532
|
+
const cloudProjectId = opts.project || globalOpts.project;
|
|
20533
|
+
const cloudTaskListId = opts.list ? await cloudResolveTaskListRef(cloud, opts.list, cloudProjectId) : undefined;
|
|
20534
|
+
if (opts.list && !cloudTaskListId) {
|
|
20535
|
+
throw new Error(`Could not resolve task list ID or slug: ${opts.list}`);
|
|
20536
|
+
}
|
|
20404
20537
|
task3 = await cloudCreateTask(cloud, {
|
|
20405
20538
|
title,
|
|
20406
20539
|
description: opts.description,
|
|
20407
20540
|
priority: parsePriority(opts.priority),
|
|
20541
|
+
parent_id: opts.parent,
|
|
20408
20542
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
20409
20543
|
plan_id: opts.plan,
|
|
20410
20544
|
assigned_to: opts.assign,
|
|
20411
20545
|
status: parseStatus(opts.status),
|
|
20412
|
-
task_list_id:
|
|
20546
|
+
task_list_id: cloudTaskListId,
|
|
20413
20547
|
agent_id: globalOpts.agent,
|
|
20414
20548
|
session_id: globalOpts.session,
|
|
20415
|
-
project_id:
|
|
20549
|
+
project_id: cloudProjectId,
|
|
20416
20550
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
20417
20551
|
sla_minutes: opts.slaMinutes !== undefined || opts.sla !== undefined ? parseIntOption(opts.slaMinutes ?? opts.sla, "--sla-minutes") : undefined,
|
|
20418
20552
|
requires_approval: opts.approval || undefined,
|
|
@@ -20478,7 +20612,7 @@ function registerTaskCommands(program2) {
|
|
|
20478
20612
|
}
|
|
20479
20613
|
});
|
|
20480
20614
|
const task = program2.command("task").description("Task subcommands for deterministic automation");
|
|
20481
|
-
task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action((opts) => {
|
|
20615
|
+
task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action(async (opts) => {
|
|
20482
20616
|
const globalOpts = program2.opts();
|
|
20483
20617
|
opts.tags = opts.tags || opts.tag;
|
|
20484
20618
|
opts.list = opts.list || opts.taskList;
|
|
@@ -20493,6 +20627,34 @@ function registerTaskCommands(program2) {
|
|
|
20493
20627
|
}
|
|
20494
20628
|
return id;
|
|
20495
20629
|
})() : undefined;
|
|
20630
|
+
const cloud = getTodosCloudClient();
|
|
20631
|
+
if (cloud) {
|
|
20632
|
+
let cloudResult;
|
|
20633
|
+
try {
|
|
20634
|
+
cloudResult = await cloudUpsertTaskByFingerprint(cloud, {
|
|
20635
|
+
fingerprint: opts.fingerprint,
|
|
20636
|
+
title: opts.title,
|
|
20637
|
+
description: opts.description,
|
|
20638
|
+
priority: parsePriority(opts.priority),
|
|
20639
|
+
status: parseStatus(opts.status),
|
|
20640
|
+
task_list_id: taskListId,
|
|
20641
|
+
tags: parseTags(opts.tags),
|
|
20642
|
+
metadata: buildExpectationMetadata(opts),
|
|
20643
|
+
working_dir: opts.workingDir ? resolve9(opts.workingDir) : process.cwd(),
|
|
20644
|
+
project_id: projectId,
|
|
20645
|
+
assigned_to: opts.assign
|
|
20646
|
+
});
|
|
20647
|
+
} catch (e) {
|
|
20648
|
+
handleError(e);
|
|
20649
|
+
}
|
|
20650
|
+
if (globalOpts.json) {
|
|
20651
|
+
output(cloudResult, true);
|
|
20652
|
+
} else {
|
|
20653
|
+
console.log(chalk2.green(cloudResult.created ? "Task created:" : "Task updated:"));
|
|
20654
|
+
console.log(formatTaskLine(cloudResult.task));
|
|
20655
|
+
}
|
|
20656
|
+
return;
|
|
20657
|
+
}
|
|
20496
20658
|
let result;
|
|
20497
20659
|
try {
|
|
20498
20660
|
result = upsertTaskByFingerprint({
|
|
@@ -20743,7 +20905,22 @@ function registerTaskCommands(program2) {
|
|
|
20743
20905
|
let task2;
|
|
20744
20906
|
if (cloud) {
|
|
20745
20907
|
const remote = await cloudGetTask(cloud, resolveTaskId(id));
|
|
20746
|
-
|
|
20908
|
+
const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
|
|
20909
|
+
task2 = remote ? {
|
|
20910
|
+
subtasks: [],
|
|
20911
|
+
dependencies: [],
|
|
20912
|
+
blocked_by: [],
|
|
20913
|
+
...remote,
|
|
20914
|
+
tags: remote.tags ?? [],
|
|
20915
|
+
comments: commentPage.comments,
|
|
20916
|
+
comments_page: {
|
|
20917
|
+
count: commentPage.count,
|
|
20918
|
+
limit: commentPage.limit,
|
|
20919
|
+
has_more: commentPage.has_more,
|
|
20920
|
+
next_cursor: commentPage.next_cursor,
|
|
20921
|
+
pagination_supported: commentPage.pagination_supported
|
|
20922
|
+
}
|
|
20923
|
+
} : null;
|
|
20747
20924
|
} else {
|
|
20748
20925
|
const resolvedId = resolveTaskId(id);
|
|
20749
20926
|
task2 = getTaskWithRelations(resolvedId);
|
|
@@ -20827,11 +21004,11 @@ function registerTaskCommands(program2) {
|
|
|
20827
21004
|
}
|
|
20828
21005
|
}
|
|
20829
21006
|
if (task2.comments.length > 0) {
|
|
21007
|
+
const suffix = task2.comments_page?.has_more ? task2.comments_page.pagination_supported ? ", newer page shown; older comments available" : ", newer comments shown; older comments omitted until the server is upgraded" : "";
|
|
20830
21008
|
console.log(chalk2.bold(`
|
|
20831
|
-
Comments (${task2.comments.length}):`));
|
|
21009
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
20832
21010
|
for (const c of task2.comments) {
|
|
20833
|
-
|
|
20834
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21011
|
+
console.log(formatHumanComment(c));
|
|
20835
21012
|
}
|
|
20836
21013
|
}
|
|
20837
21014
|
});
|
|
@@ -20857,7 +21034,23 @@ function registerTaskCommands(program2) {
|
|
|
20857
21034
|
let task2;
|
|
20858
21035
|
if (cloud) {
|
|
20859
21036
|
const remote = await cloudGetTask(cloud, resolvedId);
|
|
20860
|
-
|
|
21037
|
+
const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
|
|
21038
|
+
task2 = remote ? {
|
|
21039
|
+
subtasks: [],
|
|
21040
|
+
dependencies: [],
|
|
21041
|
+
blocked_by: [],
|
|
21042
|
+
checklist: [],
|
|
21043
|
+
...remote,
|
|
21044
|
+
tags: remote.tags ?? [],
|
|
21045
|
+
comments: commentPage.comments,
|
|
21046
|
+
comments_page: {
|
|
21047
|
+
count: commentPage.count,
|
|
21048
|
+
limit: commentPage.limit,
|
|
21049
|
+
has_more: commentPage.has_more,
|
|
21050
|
+
next_cursor: commentPage.next_cursor,
|
|
21051
|
+
pagination_supported: commentPage.pagination_supported
|
|
21052
|
+
}
|
|
21053
|
+
} : null;
|
|
20861
21054
|
} else {
|
|
20862
21055
|
task2 = getTaskWithRelations(resolvedId);
|
|
20863
21056
|
}
|
|
@@ -20964,11 +21157,11 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20964
21157
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
20965
21158
|
}
|
|
20966
21159
|
if (task2.comments.length > 0) {
|
|
21160
|
+
const suffix = task2.comments_page?.has_more ? task2.comments_page.pagination_supported ? ", newer page shown; older comments available" : ", newer comments shown; older comments omitted until the server is upgraded" : "";
|
|
20967
21161
|
console.log(chalk2.bold(`
|
|
20968
|
-
Comments (${task2.comments.length}):`));
|
|
21162
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
20969
21163
|
for (const c of task2.comments) {
|
|
20970
|
-
|
|
20971
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21164
|
+
console.log(formatHumanComment(c));
|
|
20972
21165
|
}
|
|
20973
21166
|
}
|
|
20974
21167
|
if (task2.checklist && task2.checklist.length > 0) {
|
|
@@ -20985,8 +21178,18 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20985
21178
|
program2.command("history <id>").description("Show change history for a task (audit log)").action(async (id) => {
|
|
20986
21179
|
const globalOpts = program2.opts();
|
|
20987
21180
|
const resolvedId = resolveTaskId(id);
|
|
20988
|
-
const
|
|
20989
|
-
|
|
21181
|
+
const cloud = getTodosCloudClient();
|
|
21182
|
+
let history;
|
|
21183
|
+
if (cloud) {
|
|
21184
|
+
try {
|
|
21185
|
+
history = await cloudTaskHistory(cloud, resolvedId);
|
|
21186
|
+
} catch (e) {
|
|
21187
|
+
handleError(e);
|
|
21188
|
+
}
|
|
21189
|
+
} else {
|
|
21190
|
+
const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
|
|
21191
|
+
history = getTaskHistory2(resolvedId);
|
|
21192
|
+
}
|
|
20990
21193
|
if (globalOpts.json) {
|
|
20991
21194
|
output(history, true);
|
|
20992
21195
|
return;
|
|
@@ -21254,7 +21457,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21254
21457
|
const resolvedId = resolveTaskId(id);
|
|
21255
21458
|
try {
|
|
21256
21459
|
if (cloud)
|
|
21257
|
-
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent);
|
|
21460
|
+
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent, !globalOpts.agent);
|
|
21258
21461
|
else
|
|
21259
21462
|
unlockTask(resolvedId, globalOpts.agent);
|
|
21260
21463
|
} catch (e) {
|
|
@@ -21292,7 +21495,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21292
21495
|
process.exit(1);
|
|
21293
21496
|
}
|
|
21294
21497
|
});
|
|
21295
|
-
program2.command("bulk <action> <ids...>").description("Bulk operation on multiple tasks (done, start, delete, plan)").option("--plan <id>", "Plan ID for the plan/move-plan action").option("--clear-plan", "Remove plan assignment for the plan/move-plan action").action((action, ids, opts) => {
|
|
21498
|
+
program2.command("bulk <action> <ids...>").description("Bulk operation on multiple tasks (done, start, delete, plan)").option("--plan <id>", "Plan ID for the plan/move-plan action").option("--clear-plan", "Remove plan assignment for the plan/move-plan action").action(async (action, ids, opts) => {
|
|
21296
21499
|
const globalOpts = program2.opts();
|
|
21297
21500
|
const results = [];
|
|
21298
21501
|
const isPlanAction = action === "plan" || action === "move-plan";
|
|
@@ -21301,6 +21504,45 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21301
21504
|
process.exit(1);
|
|
21302
21505
|
}
|
|
21303
21506
|
const planId = isPlanAction ? opts.plan ? resolvePlanId(opts.plan) : null : undefined;
|
|
21507
|
+
const knownActions = new Set(["done", "complete", "start", "delete", "plan", "move-plan"]);
|
|
21508
|
+
if (!knownActions.has(action)) {
|
|
21509
|
+
console.error(chalk2.red(`Unknown action: ${action}. Use: done, start, delete, plan`));
|
|
21510
|
+
process.exit(1);
|
|
21511
|
+
}
|
|
21512
|
+
const cloud = getTodosCloudClient();
|
|
21513
|
+
if (cloud) {
|
|
21514
|
+
for (const rawId of ids) {
|
|
21515
|
+
try {
|
|
21516
|
+
const resolvedId = resolveTaskId(rawId);
|
|
21517
|
+
if (action === "done" || action === "complete") {
|
|
21518
|
+
await cloudTaskAction(cloud, resolvedId, "complete", { agent_id: globalOpts.agent });
|
|
21519
|
+
} else if (action === "start") {
|
|
21520
|
+
await cloudTaskAction(cloud, resolvedId, "start", { agent_id: globalOpts.agent || "cli" });
|
|
21521
|
+
} else if (action === "delete") {
|
|
21522
|
+
await cloudDeleteTask(cloud, resolvedId);
|
|
21523
|
+
} else {
|
|
21524
|
+
const current = await cloudGetTask(cloud, resolvedId);
|
|
21525
|
+
if (!current)
|
|
21526
|
+
throw new Error(`Task not found: ${rawId}`);
|
|
21527
|
+
await cloudUpdateTask(cloud, resolvedId, { version: current.version, plan_id: planId });
|
|
21528
|
+
}
|
|
21529
|
+
results.push({ id: resolvedId, success: true });
|
|
21530
|
+
} catch (e) {
|
|
21531
|
+
results.push({ id: rawId, success: false, error: e instanceof Error ? e.message : String(e) });
|
|
21532
|
+
}
|
|
21533
|
+
}
|
|
21534
|
+
const succeededCloud = results.filter((r) => r.success).length;
|
|
21535
|
+
const failedCloud = results.filter((r) => !r.success).length;
|
|
21536
|
+
if (globalOpts.json) {
|
|
21537
|
+
output({ results, succeeded: succeededCloud, failed: failedCloud }, true);
|
|
21538
|
+
} else {
|
|
21539
|
+
console.log(chalk2.green(`${action}: ${succeededCloud} succeeded, ${failedCloud} failed`));
|
|
21540
|
+
for (const r of results.filter((r2) => !r2.success)) {
|
|
21541
|
+
console.log(chalk2.red(` ${r.id}: ${r.error}`));
|
|
21542
|
+
}
|
|
21543
|
+
}
|
|
21544
|
+
return;
|
|
21545
|
+
}
|
|
21304
21546
|
for (const rawId of ids) {
|
|
21305
21547
|
try {
|
|
21306
21548
|
const resolvedId = resolveTaskId(rawId);
|
|
@@ -23170,7 +23412,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
23170
23412
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
23171
23413
|
const dir = getTaskListDir(taskListId);
|
|
23172
23414
|
if (!existsSync10(dir))
|
|
23173
|
-
|
|
23415
|
+
ensureDir(dir);
|
|
23174
23416
|
const filter = {};
|
|
23175
23417
|
if (projectId)
|
|
23176
23418
|
filter["project_id"] = projectId;
|
|
@@ -23353,7 +23595,7 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
23353
23595
|
}
|
|
23354
23596
|
var init_claude_tasks = __esm(() => {
|
|
23355
23597
|
init_tasks();
|
|
23356
|
-
|
|
23598
|
+
init_config();
|
|
23357
23599
|
init_sync_utils();
|
|
23358
23600
|
});
|
|
23359
23601
|
|
|
@@ -23397,7 +23639,7 @@ function metadataKey(agent) {
|
|
|
23397
23639
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
23398
23640
|
const dir = getTaskListDir2(agent, taskListId);
|
|
23399
23641
|
if (!existsSync11(dir))
|
|
23400
|
-
|
|
23642
|
+
ensureDir(dir);
|
|
23401
23643
|
const filter = {};
|
|
23402
23644
|
if (projectId)
|
|
23403
23645
|
filter["project_id"] = projectId;
|
|
@@ -23570,7 +23812,7 @@ function syncAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
23570
23812
|
var init_agent_tasks = __esm(() => {
|
|
23571
23813
|
init_tasks();
|
|
23572
23814
|
init_sync_utils();
|
|
23573
|
-
|
|
23815
|
+
init_config();
|
|
23574
23816
|
});
|
|
23575
23817
|
|
|
23576
23818
|
// src/lib/sync.ts
|
|
@@ -23642,7 +23884,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
23642
23884
|
var init_sync = __esm(() => {
|
|
23643
23885
|
init_claude_tasks();
|
|
23644
23886
|
init_agent_tasks();
|
|
23645
|
-
|
|
23887
|
+
init_config();
|
|
23646
23888
|
});
|
|
23647
23889
|
|
|
23648
23890
|
// src/lib/project-bootstrap.ts
|
|
@@ -31355,7 +31597,7 @@ function applyExportProfile(data, options = {}) {
|
|
|
31355
31597
|
}
|
|
31356
31598
|
var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value", TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge", TODOS_ENCRYPTION_SCHEMA_VERSION = 1, DEFAULT_ENCRYPTION_PROFILE = "default", DEFAULT_ENCRYPTION_KEY_ENV = "TODOS_ENCRYPTION_KEY", EncryptionKeyUnavailableError, EncryptedPayloadError;
|
|
31357
31599
|
var init_local_encryption = __esm(() => {
|
|
31358
|
-
|
|
31600
|
+
init_config();
|
|
31359
31601
|
init_redaction();
|
|
31360
31602
|
EncryptionKeyUnavailableError = class EncryptionKeyUnavailableError extends Error {
|
|
31361
31603
|
keyEnv;
|
|
@@ -32534,7 +32776,7 @@ var init_project_commands = __esm(() => {
|
|
|
32534
32776
|
init_cloud_router();
|
|
32535
32777
|
init_saved_search_views();
|
|
32536
32778
|
init_sync();
|
|
32537
|
-
|
|
32779
|
+
init_config();
|
|
32538
32780
|
init_helpers();
|
|
32539
32781
|
});
|
|
32540
32782
|
|
|
@@ -33534,9 +33776,11 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33534
33776
|
program2.command("lists").aliases(["task-lists", "tl"]).description("List and manage task lists").option("--add <name>", "Create a task list").option("--slug <slug>", "Custom slug (with --add)").option("-d, --description <text>", "Description (with --add)").option("--delete <id>", "Delete a task list").action(async (opts) => {
|
|
33535
33777
|
try {
|
|
33536
33778
|
const globalOpts = program2.opts();
|
|
33537
|
-
const
|
|
33779
|
+
const cloud = getTodosCloudClient();
|
|
33780
|
+
const projectId = cloud ? globalOpts.project : autoProject(globalOpts);
|
|
33538
33781
|
if (opts.add) {
|
|
33539
|
-
const
|
|
33782
|
+
const input = { name: opts.add, slug: opts.slug, description: opts.description, project_id: projectId };
|
|
33783
|
+
const list = cloud ? await cloudCreateTaskList(cloud, input) : createTaskList(input);
|
|
33540
33784
|
if (globalOpts.json) {
|
|
33541
33785
|
output(list, true);
|
|
33542
33786
|
return;
|
|
@@ -33548,6 +33792,14 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33548
33792
|
return;
|
|
33549
33793
|
}
|
|
33550
33794
|
if (opts.delete) {
|
|
33795
|
+
if (cloud) {
|
|
33796
|
+
const resolved2 = await cloudResolveTaskListRef(cloud, opts.delete, projectId ?? undefined);
|
|
33797
|
+
if (!resolved2)
|
|
33798
|
+
throw new Error(`Task list not found or ambiguous: ${opts.delete}`);
|
|
33799
|
+
await cloudDeleteTaskList(cloud, resolved2);
|
|
33800
|
+
console.log(chalk5.green("Task list deleted."));
|
|
33801
|
+
return;
|
|
33802
|
+
}
|
|
33551
33803
|
const db = getDatabase();
|
|
33552
33804
|
const resolved = resolvePartialId(db, "task_lists", opts.delete);
|
|
33553
33805
|
if (!resolved) {
|
|
@@ -33558,7 +33810,6 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33558
33810
|
console.log(chalk5.green("Task list deleted."));
|
|
33559
33811
|
return;
|
|
33560
33812
|
}
|
|
33561
|
-
const cloud = getTodosCloudClient();
|
|
33562
33813
|
const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
|
|
33563
33814
|
if (globalOpts.json) {
|
|
33564
33815
|
output(lists, true);
|
|
@@ -34973,7 +35224,7 @@ function renderExtensionSummary(record) {
|
|
|
34973
35224
|
}
|
|
34974
35225
|
var BUILTIN_CLI_COMMANDS;
|
|
34975
35226
|
var init_local_extensions = __esm(() => {
|
|
34976
|
-
|
|
35227
|
+
init_config();
|
|
34977
35228
|
init_mcp();
|
|
34978
35229
|
init_package_version();
|
|
34979
35230
|
init_redaction();
|
|
@@ -35405,7 +35656,7 @@ var init_policy_packs = __esm(() => {
|
|
|
35405
35656
|
init_database();
|
|
35406
35657
|
init_tasks();
|
|
35407
35658
|
init_task_runs();
|
|
35408
|
-
|
|
35659
|
+
init_config();
|
|
35409
35660
|
});
|
|
35410
35661
|
|
|
35411
35662
|
// src/db/checkpoints.ts
|
|
@@ -35928,7 +36179,7 @@ function describeTerminalNotificationRule(rule) {
|
|
|
35928
36179
|
}
|
|
35929
36180
|
var SEVERITY_ORDER, EVENT_SEVERITY, VALID_SEVERITIES, VALID_FORMATS;
|
|
35930
36181
|
var init_terminal_notifications = __esm(() => {
|
|
35931
|
-
|
|
36182
|
+
init_config();
|
|
35932
36183
|
init_event_hooks();
|
|
35933
36184
|
init_redaction();
|
|
35934
36185
|
SEVERITY_ORDER = {
|
|
@@ -37431,7 +37682,7 @@ function createTodosCloudQueryClientFromEnv(env = process.env, options = {}) {
|
|
|
37431
37682
|
return createTodosCloudQueryClient(url, options);
|
|
37432
37683
|
}
|
|
37433
37684
|
var init_cloud_client = __esm(() => {
|
|
37434
|
-
|
|
37685
|
+
init_config2();
|
|
37435
37686
|
});
|
|
37436
37687
|
|
|
37437
37688
|
// src/storage/sqlite-snapshot.ts
|
|
@@ -37813,6 +38064,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37813
38064
|
list: (filter = {}) => listTasks(filter, database()),
|
|
37814
38065
|
count: (filter = {}) => countTasks(filter, database()),
|
|
37815
38066
|
update: (id, input) => updateTask(id, input, database()),
|
|
38067
|
+
unlock: (id, agentId) => {
|
|
38068
|
+
unlockTask(id, agentId, database());
|
|
38069
|
+
return true;
|
|
38070
|
+
},
|
|
37816
38071
|
delete: (id) => deleteTask(id, database()),
|
|
37817
38072
|
start: (id, agentId) => startTask(id, agentId, database()),
|
|
37818
38073
|
complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
|
|
@@ -37864,6 +38119,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37864
38119
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId) => logTaskChange(taskId, action, field, oldValue, newValue, agentId, database()),
|
|
37865
38120
|
addComment: (input) => addComment(input, database()),
|
|
37866
38121
|
getComments: (taskId) => listComments(taskId, database()),
|
|
38122
|
+
getCommentsPage: (taskId, options2) => {
|
|
38123
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
38124
|
+
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
38125
|
+
}
|
|
38126
|
+
let comments = listComments(taskId, database());
|
|
38127
|
+
comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
|
|
38128
|
+
if (options2?.before) {
|
|
38129
|
+
const before = options2.before;
|
|
38130
|
+
comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
|
|
38131
|
+
}
|
|
38132
|
+
if (options2?.limit !== undefined)
|
|
38133
|
+
comments = comments.slice(-options2.limit);
|
|
38134
|
+
return comments;
|
|
38135
|
+
},
|
|
37867
38136
|
getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
|
|
37868
38137
|
getRecentActivity: (limit) => getRecentActivity(limit, database())
|
|
37869
38138
|
},
|
|
@@ -37921,6 +38190,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
|
|
|
37921
38190
|
)`
|
|
37922
38191
|
];
|
|
37923
38192
|
}
|
|
38193
|
+
function postgresTodosCommentCursorIndexSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABLE) {
|
|
38194
|
+
assertSafeIdentifier(tableName);
|
|
38195
|
+
return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${tableName}_comment_task_created_idx
|
|
38196
|
+
ON ${tableName} (service, (payload->>'task_id'), (payload->>'created_at'), object_id)
|
|
38197
|
+
WHERE object_type = 'comments' AND deleted_at IS NULL`;
|
|
38198
|
+
}
|
|
37924
38199
|
|
|
37925
38200
|
class PostgresTodosSyncStore {
|
|
37926
38201
|
client;
|
|
@@ -38446,7 +38721,7 @@ function __resetRuntimeShadowForTests() {
|
|
|
38446
38721
|
}
|
|
38447
38722
|
var _capturedDb = null, _outbox = null, _cloud = null, _exitRegistered = false;
|
|
38448
38723
|
var init_shadow_runtime = __esm(() => {
|
|
38449
|
-
|
|
38724
|
+
init_config2();
|
|
38450
38725
|
init_cloud_client();
|
|
38451
38726
|
init_shadow_outbox();
|
|
38452
38727
|
});
|
|
@@ -38833,7 +39108,8 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
38833
39108
|
getActiveWork: (filters) => getActiveWork2(filters, store),
|
|
38834
39109
|
getChangedSince: (since, filters) => getChangedSince(since, filters, store),
|
|
38835
39110
|
lock: (id, agentId) => lockTask2(id, agentId, store),
|
|
38836
|
-
unlock: (id, agentId) => unlockTask2(id, agentId, store)
|
|
39111
|
+
unlock: (id, agentId) => unlockTask2(id, agentId, store),
|
|
39112
|
+
getByFingerprint: (fingerprint) => store.getTaskByFingerprint(fingerprint)
|
|
38837
39113
|
},
|
|
38838
39114
|
dependencies: {
|
|
38839
39115
|
add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
|
|
@@ -38901,7 +39177,24 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
38901
39177
|
audit: {
|
|
38902
39178
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context),
|
|
38903
39179
|
addComment: (input, context) => addComment2(input, store, context),
|
|
38904
|
-
getComments: async (taskId) =>
|
|
39180
|
+
getComments: async (taskId) => {
|
|
39181
|
+
const pages = [];
|
|
39182
|
+
let before;
|
|
39183
|
+
while (true) {
|
|
39184
|
+
const page = await store.listComments(taskId, { limit: 1000, ...before ? { before } : {} });
|
|
39185
|
+
if (page.length === 0)
|
|
39186
|
+
break;
|
|
39187
|
+
pages.unshift(page);
|
|
39188
|
+
if (page.length < 1000)
|
|
39189
|
+
break;
|
|
39190
|
+
const oldest = page[0];
|
|
39191
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
39192
|
+
}
|
|
39193
|
+
return pages.flat().map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
39194
|
+
},
|
|
39195
|
+
getCommentsPage: async (taskId, options2) => {
|
|
39196
|
+
return (await store.listComments(taskId, options2)).map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
39197
|
+
},
|
|
38905
39198
|
getTaskHistory: async (taskId) => (await store.list("audit_history")).filter((entry2) => entry2.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
|
|
38906
39199
|
getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
|
|
38907
39200
|
},
|
|
@@ -38951,6 +39244,27 @@ class PostgresJsonRecordStore {
|
|
|
38951
39244
|
async list(type) {
|
|
38952
39245
|
return (await this.listRecords(type)).map((record) => record.payload);
|
|
38953
39246
|
}
|
|
39247
|
+
async listComments(taskId, options = {}) {
|
|
39248
|
+
await this.ensureSchema();
|
|
39249
|
+
const limit = options.limit ?? 100;
|
|
39250
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
39251
|
+
throw new Error("Postgres comment limit must be an integer between 1 and 1001");
|
|
39252
|
+
}
|
|
39253
|
+
const params = [this.service, taskId];
|
|
39254
|
+
let cursorPredicate = "";
|
|
39255
|
+
if (options.before) {
|
|
39256
|
+
params.push(options.before.created_at, options.before.id);
|
|
39257
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
39258
|
+
}
|
|
39259
|
+
params.push(limit);
|
|
39260
|
+
const result = await this.options.client.query(`/* todos:list-comments */ SELECT payload FROM ${this.tableName}
|
|
39261
|
+
WHERE service = $1 AND object_type = 'comments' AND deleted_at IS NULL
|
|
39262
|
+
AND payload->>'task_id' = $2
|
|
39263
|
+
${cursorPredicate}
|
|
39264
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
39265
|
+
LIMIT $${params.length}`, params);
|
|
39266
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
39267
|
+
}
|
|
38954
39268
|
async listRecords(type) {
|
|
38955
39269
|
await this.ensureSchema();
|
|
38956
39270
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -39022,6 +39336,17 @@ class PostgresJsonRecordStore {
|
|
|
39022
39336
|
const result = await this.options.client.query(sql, params);
|
|
39023
39337
|
return result.rows.map((row) => payloadRecord2(row.payload));
|
|
39024
39338
|
}
|
|
39339
|
+
async getTaskByFingerprint(fingerprint) {
|
|
39340
|
+
await this.ensureSchema();
|
|
39341
|
+
const sql = `/* todos:task-by-fingerprint */ SELECT payload FROM ${this.tableName}
|
|
39342
|
+
WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
|
|
39343
|
+
AND payload->'metadata'->>'fingerprint' = $3
|
|
39344
|
+
ORDER BY payload->>'created_at' ASC
|
|
39345
|
+
LIMIT 1`;
|
|
39346
|
+
const result = await this.options.client.query(sql, [this.service, "tasks", fingerprint]);
|
|
39347
|
+
const row = result.rows[0];
|
|
39348
|
+
return row ? payloadRecord2(row.payload) : null;
|
|
39349
|
+
}
|
|
39025
39350
|
async countTasks(filter) {
|
|
39026
39351
|
await this.ensureSchema();
|
|
39027
39352
|
const { where, params } = this.buildTaskFilterSql(filter);
|
|
@@ -39333,7 +39658,7 @@ async function lockTask2(id, agentId, store) {
|
|
|
39333
39658
|
async function unlockTask2(id, agentId, store) {
|
|
39334
39659
|
const task = await requireRecord("tasks", id, store);
|
|
39335
39660
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
39336
|
-
throw new
|
|
39661
|
+
throw new LockError(id, task.locked_by);
|
|
39337
39662
|
}
|
|
39338
39663
|
await patchTask(task, { locked_by: null, locked_at: null }, store);
|
|
39339
39664
|
return true;
|
|
@@ -39698,13 +40023,16 @@ async function addComment2(input, store, context) {
|
|
|
39698
40023
|
task_id: input.task_id,
|
|
39699
40024
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
39700
40025
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
39701
|
-
content: input.content,
|
|
40026
|
+
content: redactEvidenceText(input.content),
|
|
39702
40027
|
type: input.type ?? "comment",
|
|
39703
40028
|
progress_pct: input.progress_pct ?? null,
|
|
39704
40029
|
created_at: new Date().toISOString()
|
|
39705
40030
|
};
|
|
39706
40031
|
return store.upsert("comments", comment, context);
|
|
39707
40032
|
}
|
|
40033
|
+
function redactComment2(comment) {
|
|
40034
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
40035
|
+
}
|
|
39708
40036
|
async function exportSnapshot(store) {
|
|
39709
40037
|
return {
|
|
39710
40038
|
exportedAt: new Date().toISOString(),
|
|
@@ -39850,7 +40178,109 @@ function numberValue2(value) {
|
|
|
39850
40178
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
39851
40179
|
}
|
|
39852
40180
|
var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_BY = "ORDER BY CASE payload->>'priority' WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END ASC, payload->>'created_at' ASC, payload->>'id' ASC";
|
|
39853
|
-
var init_postgres_adapter = () => {
|
|
40181
|
+
var init_postgres_adapter = __esm(() => {
|
|
40182
|
+
init_types();
|
|
40183
|
+
init_redaction();
|
|
40184
|
+
});
|
|
40185
|
+
|
|
40186
|
+
// src/storage/comment-redaction-backfill.ts
|
|
40187
|
+
function assertSafeIdentifier2(value) {
|
|
40188
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
40189
|
+
throw new Error(`Unsafe Postgres identifier: ${value}`);
|
|
40190
|
+
}
|
|
40191
|
+
}
|
|
40192
|
+
function payloadObject(value) {
|
|
40193
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
40194
|
+
return value;
|
|
40195
|
+
if (typeof value !== "string")
|
|
40196
|
+
return null;
|
|
40197
|
+
try {
|
|
40198
|
+
const parsed = JSON.parse(value);
|
|
40199
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
40200
|
+
} catch {
|
|
40201
|
+
return null;
|
|
40202
|
+
}
|
|
40203
|
+
}
|
|
40204
|
+
async function backfillPostgresCommentRedaction(client, options = {}) {
|
|
40205
|
+
const apply = options.apply === true;
|
|
40206
|
+
if (apply && options.confirmation !== COMMENT_REDACTION_BACKFILL_CONFIRMATION) {
|
|
40207
|
+
throw new Error(`Applying the comment redaction backfill requires confirmation ${COMMENT_REDACTION_BACKFILL_CONFIRMATION}`);
|
|
40208
|
+
}
|
|
40209
|
+
const tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
40210
|
+
assertSafeIdentifier2(tableName);
|
|
40211
|
+
const service = options.service ?? "todos";
|
|
40212
|
+
const batchSize = options.batchSize ?? 100;
|
|
40213
|
+
if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 500) {
|
|
40214
|
+
throw new Error("Comment redaction backfill batchSize must be an integer between 1 and 500");
|
|
40215
|
+
}
|
|
40216
|
+
const result = {
|
|
40217
|
+
dry_run: !apply,
|
|
40218
|
+
scanned: 0,
|
|
40219
|
+
candidates: 0,
|
|
40220
|
+
updated: 0,
|
|
40221
|
+
conflicts: 0,
|
|
40222
|
+
batches: 0,
|
|
40223
|
+
remaining_candidates: 0
|
|
40224
|
+
};
|
|
40225
|
+
let afterId = "";
|
|
40226
|
+
while (true) {
|
|
40227
|
+
const page = await client.query(`/* todos:comment-redaction-backfill-scan */
|
|
40228
|
+
SELECT object_id, payload
|
|
40229
|
+
FROM ${tableName}
|
|
40230
|
+
WHERE service = $1 AND object_type = 'comments'
|
|
40231
|
+
AND object_id > $2
|
|
40232
|
+
ORDER BY object_id ASC
|
|
40233
|
+
LIMIT $3`, [service, afterId, batchSize]);
|
|
40234
|
+
if (page.rows.length === 0)
|
|
40235
|
+
break;
|
|
40236
|
+
result.batches += 1;
|
|
40237
|
+
for (const row of page.rows) {
|
|
40238
|
+
afterId = row.object_id;
|
|
40239
|
+
result.scanned += 1;
|
|
40240
|
+
const payload = payloadObject(row.payload);
|
|
40241
|
+
const original = payload?.["content"];
|
|
40242
|
+
if (typeof original !== "string")
|
|
40243
|
+
continue;
|
|
40244
|
+
const redacted = redactEvidenceText(original);
|
|
40245
|
+
if (redacted === original)
|
|
40246
|
+
continue;
|
|
40247
|
+
result.candidates += 1;
|
|
40248
|
+
if (!apply)
|
|
40249
|
+
continue;
|
|
40250
|
+
const nextPayload = { ...payload, content: redacted };
|
|
40251
|
+
const update = await client.query(`/* todos:comment-redaction-backfill-apply */
|
|
40252
|
+
UPDATE ${tableName}
|
|
40253
|
+
SET payload = $3::jsonb
|
|
40254
|
+
WHERE service = $1 AND object_type = 'comments' AND object_id = $2
|
|
40255
|
+
AND payload = $4::jsonb
|
|
40256
|
+
RETURNING object_id`, [service, row.object_id, nextPayload, row.payload]);
|
|
40257
|
+
if (update.rows.length === 1)
|
|
40258
|
+
result.updated += 1;
|
|
40259
|
+
else
|
|
40260
|
+
result.conflicts += 1;
|
|
40261
|
+
}
|
|
40262
|
+
if (page.rows.length < batchSize)
|
|
40263
|
+
break;
|
|
40264
|
+
}
|
|
40265
|
+
if (!apply) {
|
|
40266
|
+
result.remaining_candidates = result.candidates;
|
|
40267
|
+
return result;
|
|
40268
|
+
}
|
|
40269
|
+
const verification = await backfillPostgresCommentRedaction(client, {
|
|
40270
|
+
...options,
|
|
40271
|
+
apply: false,
|
|
40272
|
+
confirmation: undefined
|
|
40273
|
+
});
|
|
40274
|
+
result.remaining_candidates = verification.candidates;
|
|
40275
|
+
return result;
|
|
40276
|
+
}
|
|
40277
|
+
function isCommentRedactionBackfillComplete(result) {
|
|
40278
|
+
return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
|
|
40279
|
+
}
|
|
40280
|
+
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
40281
|
+
var init_comment_redaction_backfill = __esm(() => {
|
|
40282
|
+
init_redaction();
|
|
40283
|
+
});
|
|
39854
40284
|
|
|
39855
40285
|
// src/server/cloud.ts
|
|
39856
40286
|
var exports_cloud = {};
|
|
@@ -39864,7 +40294,9 @@ __export(exports_cloud, {
|
|
|
39864
40294
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
39865
40295
|
getApiKeyStore: () => getApiKeyStore,
|
|
39866
40296
|
ensureCloudSchema: () => ensureCloudSchema,
|
|
40297
|
+
ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
|
|
39867
40298
|
closeCloud: () => closeCloud,
|
|
40299
|
+
backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
|
|
39868
40300
|
TODOS_APP_SLUG: () => TODOS_APP_SLUG
|
|
39869
40301
|
});
|
|
39870
40302
|
function resolveCloudDatabaseUrl(env = process.env) {
|
|
@@ -39942,6 +40374,9 @@ async function ensureCloudSchema() {
|
|
|
39942
40374
|
})();
|
|
39943
40375
|
return schemaEnsured;
|
|
39944
40376
|
}
|
|
40377
|
+
async function ensureCloudCommentCursorIndex() {
|
|
40378
|
+
await getClient().query(postgresTodosCommentCursorIndexSql());
|
|
40379
|
+
}
|
|
39945
40380
|
async function normalizeCloudPayloads() {
|
|
39946
40381
|
const client = getClient();
|
|
39947
40382
|
const res = await client.query(`UPDATE todos_sync_records
|
|
@@ -39950,6 +40385,9 @@ async function normalizeCloudPayloads() {
|
|
|
39950
40385
|
RETURNING object_id AS id`);
|
|
39951
40386
|
return res.rows.length;
|
|
39952
40387
|
}
|
|
40388
|
+
function backfillCloudCommentRedaction(options = {}) {
|
|
40389
|
+
return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
40390
|
+
}
|
|
39953
40391
|
async function pingCloud() {
|
|
39954
40392
|
const client = getClient();
|
|
39955
40393
|
const res = await client.query("select 1 as ok");
|
|
@@ -39970,6 +40408,7 @@ var init_cloud = __esm(() => {
|
|
|
39970
40408
|
init_auth();
|
|
39971
40409
|
init_cloud_client();
|
|
39972
40410
|
init_postgres_adapter();
|
|
40411
|
+
init_comment_redaction_backfill();
|
|
39973
40412
|
});
|
|
39974
40413
|
|
|
39975
40414
|
// src/server/openapi.ts
|
|
@@ -39993,6 +40432,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
39993
40432
|
schemas: {
|
|
39994
40433
|
Task: taskSchema,
|
|
39995
40434
|
Project: projectSchema,
|
|
40435
|
+
TaskComment: taskCommentSchema,
|
|
39996
40436
|
CreateTaskInput: {
|
|
39997
40437
|
type: "object",
|
|
39998
40438
|
required: ["title"],
|
|
@@ -40027,6 +40467,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40027
40467
|
description: { type: "string" },
|
|
40028
40468
|
task_prefix: { type: "string" }
|
|
40029
40469
|
}
|
|
40470
|
+
},
|
|
40471
|
+
CreateTaskCommentInput: {
|
|
40472
|
+
type: "object",
|
|
40473
|
+
required: ["content"],
|
|
40474
|
+
properties: {
|
|
40475
|
+
content: { type: "string", minLength: 1 },
|
|
40476
|
+
agent_id: { type: "string" },
|
|
40477
|
+
session_id: { type: "string" },
|
|
40478
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
40479
|
+
progress_pct: { type: "number" }
|
|
40480
|
+
}
|
|
40030
40481
|
}
|
|
40031
40482
|
}
|
|
40032
40483
|
},
|
|
@@ -40125,6 +40576,61 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40125
40576
|
}
|
|
40126
40577
|
}
|
|
40127
40578
|
},
|
|
40579
|
+
"/v1/tasks/{id}/comments": {
|
|
40580
|
+
get: {
|
|
40581
|
+
operationId: "listTaskComments",
|
|
40582
|
+
summary: "List a bounded page of task comments",
|
|
40583
|
+
description: "Returns the newest page in oldest-to-newest display order. Use next_cursor to request older pages; count is the page size, not a total. Pagination-aware clients must send limit during the mixed-version rollout.",
|
|
40584
|
+
parameters: [
|
|
40585
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
40586
|
+
{ name: "limit", in: "query", required: true, schema: { type: "integer", minimum: 1, maximum: 500, default: 100 } },
|
|
40587
|
+
{ name: "cursor", in: "query", schema: { type: "string" } }
|
|
40588
|
+
],
|
|
40589
|
+
responses: {
|
|
40590
|
+
"200": {
|
|
40591
|
+
content: {
|
|
40592
|
+
"application/json": {
|
|
40593
|
+
schema: {
|
|
40594
|
+
type: "object",
|
|
40595
|
+
required: ["comments", "count", "has_more", "next_cursor"],
|
|
40596
|
+
properties: {
|
|
40597
|
+
comments: { type: "array", maxItems: 500, items: { $ref: "#/components/schemas/TaskComment" } },
|
|
40598
|
+
count: { type: "integer", minimum: 0, maximum: 500 },
|
|
40599
|
+
has_more: { type: "boolean" },
|
|
40600
|
+
next_cursor: { type: "string", nullable: true }
|
|
40601
|
+
}
|
|
40602
|
+
}
|
|
40603
|
+
}
|
|
40604
|
+
}
|
|
40605
|
+
},
|
|
40606
|
+
"426": {
|
|
40607
|
+
description: "Upgrade required: a predecessor client omitted limit and the complete legacy history exceeds 500 comments, or the configured storage adapter lacks cursor pagination support."
|
|
40608
|
+
}
|
|
40609
|
+
}
|
|
40610
|
+
},
|
|
40611
|
+
post: {
|
|
40612
|
+
operationId: "createTaskComment",
|
|
40613
|
+
summary: "Create a task comment",
|
|
40614
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
40615
|
+
requestBody: {
|
|
40616
|
+
required: true,
|
|
40617
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTaskCommentInput" } } }
|
|
40618
|
+
},
|
|
40619
|
+
responses: {
|
|
40620
|
+
"201": {
|
|
40621
|
+
content: {
|
|
40622
|
+
"application/json": {
|
|
40623
|
+
schema: {
|
|
40624
|
+
type: "object",
|
|
40625
|
+
required: ["comment"],
|
|
40626
|
+
properties: { comment: { $ref: "#/components/schemas/TaskComment" } }
|
|
40627
|
+
}
|
|
40628
|
+
}
|
|
40629
|
+
}
|
|
40630
|
+
}
|
|
40631
|
+
}
|
|
40632
|
+
}
|
|
40633
|
+
},
|
|
40128
40634
|
"/v1/tasks/{id}/start": {
|
|
40129
40635
|
post: {
|
|
40130
40636
|
operationId: "startTask",
|
|
@@ -40243,7 +40749,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40243
40749
|
}
|
|
40244
40750
|
};
|
|
40245
40751
|
}
|
|
40246
|
-
var taskSchema, projectSchema;
|
|
40752
|
+
var taskSchema, projectSchema, taskCommentSchema;
|
|
40247
40753
|
var init_openapi = __esm(() => {
|
|
40248
40754
|
init_package_version();
|
|
40249
40755
|
taskSchema = {
|
|
@@ -40274,6 +40780,20 @@ var init_openapi = __esm(() => {
|
|
|
40274
40780
|
updated_at: { type: "string" }
|
|
40275
40781
|
}
|
|
40276
40782
|
};
|
|
40783
|
+
taskCommentSchema = {
|
|
40784
|
+
type: "object",
|
|
40785
|
+
required: ["id", "task_id", "agent_id", "session_id", "content", "type", "progress_pct", "created_at"],
|
|
40786
|
+
properties: {
|
|
40787
|
+
id: { type: "string" },
|
|
40788
|
+
task_id: { type: "string" },
|
|
40789
|
+
agent_id: { type: "string", nullable: true },
|
|
40790
|
+
session_id: { type: "string", nullable: true },
|
|
40791
|
+
content: { type: "string" },
|
|
40792
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
40793
|
+
progress_pct: { type: "number", nullable: true },
|
|
40794
|
+
created_at: { type: "string", format: "date-time" }
|
|
40795
|
+
}
|
|
40796
|
+
};
|
|
40277
40797
|
});
|
|
40278
40798
|
|
|
40279
40799
|
// src/server/v1.ts
|
|
@@ -40303,6 +40823,29 @@ function contextFromPrincipal(principal, body) {
|
|
|
40303
40823
|
const agentId = body?.agent_id || principal.agent || undefined;
|
|
40304
40824
|
return agentId ? { agentId } : {};
|
|
40305
40825
|
}
|
|
40826
|
+
function redactComment3(comment) {
|
|
40827
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
40828
|
+
}
|
|
40829
|
+
function encodeCommentCursor(comment) {
|
|
40830
|
+
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
40831
|
+
}
|
|
40832
|
+
function decodeCommentCursor(value) {
|
|
40833
|
+
if (value.length > 1024)
|
|
40834
|
+
throw new Error("invalid comment cursor");
|
|
40835
|
+
let parsed;
|
|
40836
|
+
try {
|
|
40837
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
40838
|
+
} catch {
|
|
40839
|
+
throw new Error("invalid comment cursor");
|
|
40840
|
+
}
|
|
40841
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
40842
|
+
throw new Error("invalid comment cursor");
|
|
40843
|
+
const cursor = parsed;
|
|
40844
|
+
if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
|
|
40845
|
+
throw new Error("invalid comment cursor");
|
|
40846
|
+
}
|
|
40847
|
+
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
40848
|
+
}
|
|
40306
40849
|
function normalizeImportSnapshot(raw) {
|
|
40307
40850
|
const body = raw && typeof raw === "object" ? raw : {};
|
|
40308
40851
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -40323,7 +40866,7 @@ function normalizeImportSnapshot(raw) {
|
|
|
40323
40866
|
function countSnapshotRecords(s) {
|
|
40324
40867
|
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
40325
40868
|
}
|
|
40326
|
-
async function handleV1Request(req, url) {
|
|
40869
|
+
async function handleV1Request(req, url, dependencies = {}) {
|
|
40327
40870
|
const path = url.pathname;
|
|
40328
40871
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
40329
40872
|
return null;
|
|
@@ -40332,7 +40875,7 @@ async function handleV1Request(req, url) {
|
|
|
40332
40875
|
const requiredScopes = [isWrite ? "todos:write" : "todos:read"];
|
|
40333
40876
|
let verifier;
|
|
40334
40877
|
try {
|
|
40335
|
-
verifier = getCloudVerifier();
|
|
40878
|
+
verifier = (dependencies.getVerifier ?? getCloudVerifier)();
|
|
40336
40879
|
} catch (e) {
|
|
40337
40880
|
return error(503, e.message);
|
|
40338
40881
|
}
|
|
@@ -40341,8 +40884,8 @@ async function handleV1Request(req, url) {
|
|
|
40341
40884
|
return error(decision.status, decision.message, { reason: decision.reason });
|
|
40342
40885
|
}
|
|
40343
40886
|
const principal = decision.principal;
|
|
40344
|
-
await ensureCloudSchema();
|
|
40345
|
-
const store = getCloudStorageAdapter();
|
|
40887
|
+
await (dependencies.ensureSchema ?? ensureCloudSchema)();
|
|
40888
|
+
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
40346
40889
|
const segments = path.split("/").filter(Boolean);
|
|
40347
40890
|
const resource = segments[1];
|
|
40348
40891
|
const id = segments[2];
|
|
@@ -40370,13 +40913,74 @@ async function handleV1Request(req, url) {
|
|
|
40370
40913
|
missing
|
|
40371
40914
|
});
|
|
40372
40915
|
}
|
|
40916
|
+
if (id === "upsert" && !action) {
|
|
40917
|
+
if (method !== "POST")
|
|
40918
|
+
return error(405, `method ${method} not allowed on /v1/tasks/upsert`);
|
|
40919
|
+
if (typeof store.tasks.getByFingerprint !== "function") {
|
|
40920
|
+
return error(501, "fingerprint upsert is not supported by this storage backend");
|
|
40921
|
+
}
|
|
40922
|
+
const body = await readJson(req) ?? {};
|
|
40923
|
+
const fingerprint = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
|
|
40924
|
+
if (!fingerprint)
|
|
40925
|
+
return error(400, "fingerprint is required");
|
|
40926
|
+
if (typeof body.title !== "string" || !body.title.trim())
|
|
40927
|
+
return error(400, "title is required");
|
|
40928
|
+
const existing = await store.tasks.getByFingerprint(fingerprint);
|
|
40929
|
+
const metadata = {
|
|
40930
|
+
...existing?.metadata ?? {},
|
|
40931
|
+
...body.metadata ?? {},
|
|
40932
|
+
fingerprint
|
|
40933
|
+
};
|
|
40934
|
+
const fields = { metadata };
|
|
40935
|
+
for (const key of [
|
|
40936
|
+
"title",
|
|
40937
|
+
"description",
|
|
40938
|
+
"priority",
|
|
40939
|
+
"status",
|
|
40940
|
+
"project_id",
|
|
40941
|
+
"assigned_to",
|
|
40942
|
+
"working_dir",
|
|
40943
|
+
"plan_id",
|
|
40944
|
+
"task_list_id",
|
|
40945
|
+
"tags",
|
|
40946
|
+
"due_at",
|
|
40947
|
+
"estimated_minutes",
|
|
40948
|
+
"sla_minutes",
|
|
40949
|
+
"requires_approval",
|
|
40950
|
+
"recurrence_rule",
|
|
40951
|
+
"task_type"
|
|
40952
|
+
]) {
|
|
40953
|
+
const bag = body;
|
|
40954
|
+
if (bag[key] !== undefined)
|
|
40955
|
+
fields[key] = bag[key];
|
|
40956
|
+
}
|
|
40957
|
+
if (!existing) {
|
|
40958
|
+
const task = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
|
|
40959
|
+
return json2({ task, created: true }, 201);
|
|
40960
|
+
}
|
|
40961
|
+
try {
|
|
40962
|
+
const task = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
|
|
40963
|
+
return json2({ task, created: false });
|
|
40964
|
+
} catch (e) {
|
|
40965
|
+
const msg = e.message || "";
|
|
40966
|
+
if (msg.includes("version conflict"))
|
|
40967
|
+
return error(409, msg);
|
|
40968
|
+
throw e;
|
|
40969
|
+
}
|
|
40970
|
+
}
|
|
40373
40971
|
if (!id) {
|
|
40374
40972
|
if (method === "GET") {
|
|
40375
40973
|
const filter = {
|
|
40376
|
-
...url.searchParams.get("status") ? {
|
|
40377
|
-
|
|
40974
|
+
...url.searchParams.get("status") ? {
|
|
40975
|
+
status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
|
|
40976
|
+
} : {},
|
|
40977
|
+
...url.searchParams.get("priority") ? {
|
|
40978
|
+
priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
|
|
40979
|
+
} : {},
|
|
40378
40980
|
...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
|
|
40981
|
+
...url.searchParams.has("parent_id") ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : {},
|
|
40379
40982
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
|
|
40983
|
+
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
40380
40984
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
40381
40985
|
...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
|
|
40382
40986
|
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
@@ -40400,8 +41004,47 @@ async function handleV1Request(req, url) {
|
|
|
40400
41004
|
if (action) {
|
|
40401
41005
|
if (action === "comments") {
|
|
40402
41006
|
if (method === "GET") {
|
|
40403
|
-
|
|
40404
|
-
|
|
41007
|
+
if (!await store.tasks.get(id))
|
|
41008
|
+
return error(404, "task not found");
|
|
41009
|
+
const rawLimit = url.searchParams.get("limit");
|
|
41010
|
+
const cursor = url.searchParams.get("cursor");
|
|
41011
|
+
if (rawLimit === null && cursor === null) {
|
|
41012
|
+
const storageContext = contextFromPrincipal(principal);
|
|
41013
|
+
const legacyPage = (await (store.audit.getCommentsPage ? store.audit.getCommentsPage(id, { limit: LEGACY_COMMENT_RESPONSE_LIMIT + 1 }, storageContext) : store.audit.getComments(id, storageContext))).map(redactComment3);
|
|
41014
|
+
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
41015
|
+
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
41016
|
+
}
|
|
41017
|
+
return json2({
|
|
41018
|
+
comments: legacyPage,
|
|
41019
|
+
count: legacyPage.length,
|
|
41020
|
+
has_more: false,
|
|
41021
|
+
next_cursor: null
|
|
41022
|
+
});
|
|
41023
|
+
}
|
|
41024
|
+
const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
|
|
41025
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
|
|
41026
|
+
return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
|
|
41027
|
+
}
|
|
41028
|
+
let before;
|
|
41029
|
+
if (cursor) {
|
|
41030
|
+
try {
|
|
41031
|
+
before = decodeCommentCursor(cursor);
|
|
41032
|
+
} catch {
|
|
41033
|
+
return error(400, "invalid comment cursor");
|
|
41034
|
+
}
|
|
41035
|
+
}
|
|
41036
|
+
if (!store.audit.getCommentsPage) {
|
|
41037
|
+
return error(426, "storage adapter must be upgraded to support cursor-paginated comments");
|
|
41038
|
+
}
|
|
41039
|
+
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
41040
|
+
const hasMore = page.length > limit;
|
|
41041
|
+
const comments = hasMore ? page.slice(1) : page;
|
|
41042
|
+
return json2({
|
|
41043
|
+
comments,
|
|
41044
|
+
count: comments.length,
|
|
41045
|
+
has_more: hasMore,
|
|
41046
|
+
next_cursor: hasMore && comments[0] ? encodeCommentCursor(comments[0]) : null
|
|
41047
|
+
});
|
|
40405
41048
|
}
|
|
40406
41049
|
if (method === "POST") {
|
|
40407
41050
|
const body2 = await readJson(req) ?? {};
|
|
@@ -40419,24 +41062,45 @@ async function handleV1Request(req, url) {
|
|
|
40419
41062
|
type: body2.type,
|
|
40420
41063
|
progress_pct: body2.progress_pct
|
|
40421
41064
|
}, contextFromPrincipal(principal, body2));
|
|
40422
|
-
return json2({ comment }, 201);
|
|
41065
|
+
return json2({ comment: redactComment3(comment) }, 201);
|
|
40423
41066
|
}
|
|
40424
41067
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
40425
41068
|
}
|
|
41069
|
+
if (action === "history") {
|
|
41070
|
+
if (method !== "GET")
|
|
41071
|
+
return error(405, `method ${method} not allowed on /v1/tasks/:id/history`);
|
|
41072
|
+
if (!await store.tasks.get(id))
|
|
41073
|
+
return error(404, "task not found");
|
|
41074
|
+
const history = await store.audit.getTaskHistory(id);
|
|
41075
|
+
return json2({ history, count: history.length });
|
|
41076
|
+
}
|
|
40426
41077
|
if (action === "lock" || action === "unlock") {
|
|
40427
41078
|
if (method !== "POST")
|
|
40428
41079
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
40429
|
-
if (typeof store.tasks.lock !== "function" || typeof store.tasks.unlock !== "function") {
|
|
40430
|
-
return error(501, "task locking is not supported by this storage backend");
|
|
40431
|
-
}
|
|
40432
41080
|
const body2 = await readJson(req) ?? {};
|
|
40433
41081
|
if (!await store.tasks.get(id))
|
|
40434
41082
|
return error(404, "task not found");
|
|
40435
41083
|
if (action === "lock") {
|
|
40436
|
-
|
|
40437
|
-
|
|
41084
|
+
if (typeof store.tasks.lock !== "function")
|
|
41085
|
+
return error(501, "task locking is not supported by this storage backend");
|
|
41086
|
+
const agentId3 = body2.agent_id || principal.agent || "todos-serve";
|
|
41087
|
+
return json2({ result: await store.tasks.lock(id, agentId3) });
|
|
41088
|
+
}
|
|
41089
|
+
if (typeof store.tasks.unlock !== "function")
|
|
41090
|
+
return error(501, "task unlocking is not supported by this storage backend");
|
|
41091
|
+
if (body2.force === true) {
|
|
41092
|
+
if (!principal.scopes.includes("todos:*"))
|
|
41093
|
+
return error(403, "force unlock requires todos:* scope");
|
|
41094
|
+
const released2 = await store.tasks.unlock(id);
|
|
41095
|
+
return json2({ success: released2 });
|
|
40438
41096
|
}
|
|
40439
|
-
|
|
41097
|
+
if (body2.agent_id && principal.agent && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
41098
|
+
return error(403, "unlock agent_id must match the authenticated agent");
|
|
41099
|
+
}
|
|
41100
|
+
const agentId2 = principal.agent || body2.agent_id;
|
|
41101
|
+
if (!agentId2)
|
|
41102
|
+
return error(403, "unlock requires an agent-bound key or force=true");
|
|
41103
|
+
const released = await store.tasks.unlock(id, agentId2);
|
|
40440
41104
|
return json2({ success: released });
|
|
40441
41105
|
}
|
|
40442
41106
|
if (action === "dependencies") {
|
|
@@ -40719,12 +41383,28 @@ async function handleV1Request(req, url) {
|
|
|
40719
41383
|
const activity = await store.audit.getRecentActivity(limit);
|
|
40720
41384
|
return json2({ activity, count: activity.length });
|
|
40721
41385
|
}
|
|
40722
|
-
if (resource === "task-lists"
|
|
40723
|
-
if (method
|
|
40724
|
-
|
|
40725
|
-
|
|
40726
|
-
|
|
40727
|
-
|
|
41386
|
+
if (resource === "task-lists") {
|
|
41387
|
+
if (!id && method === "GET") {
|
|
41388
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
41389
|
+
const taskLists = await store.taskLists.list(projectId);
|
|
41390
|
+
return json2({ task_lists: taskLists, count: taskLists.length });
|
|
41391
|
+
}
|
|
41392
|
+
if (!id && method === "POST") {
|
|
41393
|
+
const body = await readJson(req);
|
|
41394
|
+
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
41395
|
+
return error(400, "name is required");
|
|
41396
|
+
const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
|
|
41397
|
+
return json2({ task_list: taskList }, 201);
|
|
41398
|
+
}
|
|
41399
|
+
if (id && method === "GET") {
|
|
41400
|
+
const taskList = await store.taskLists.get(id);
|
|
41401
|
+
return taskList ? json2({ task_list: taskList }) : error(404, "task list not found");
|
|
41402
|
+
}
|
|
41403
|
+
if (id && method === "DELETE") {
|
|
41404
|
+
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
41405
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "task list not found");
|
|
41406
|
+
}
|
|
41407
|
+
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
40728
41408
|
}
|
|
40729
41409
|
if (resource === "dependencies" && !id) {
|
|
40730
41410
|
if (method !== "GET")
|
|
@@ -40732,8 +41412,8 @@ async function handleV1Request(req, url) {
|
|
|
40732
41412
|
if (typeof store.dependencies?.listAll !== "function") {
|
|
40733
41413
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
40734
41414
|
}
|
|
40735
|
-
const
|
|
40736
|
-
return json2({ dependencies, count:
|
|
41415
|
+
const dependencies2 = await store.dependencies.listAll();
|
|
41416
|
+
return json2({ dependencies: dependencies2, count: dependencies2.length });
|
|
40737
41417
|
}
|
|
40738
41418
|
if (resource === "commits" && id) {
|
|
40739
41419
|
if (method !== "GET")
|
|
@@ -40790,12 +41470,16 @@ async function handleV1Request(req, url) {
|
|
|
40790
41470
|
}
|
|
40791
41471
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
40792
41472
|
} catch (e) {
|
|
41473
|
+
if (e instanceof LockError)
|
|
41474
|
+
return error(409, e.message, { code: LockError.code });
|
|
40793
41475
|
return error(500, e.message || "internal error");
|
|
40794
41476
|
}
|
|
40795
41477
|
}
|
|
40796
|
-
var JSON_HEADERS;
|
|
41478
|
+
var JSON_HEADERS, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
40797
41479
|
var init_v1 = __esm(() => {
|
|
41480
|
+
init_types();
|
|
40798
41481
|
init_cloud();
|
|
41482
|
+
init_redaction();
|
|
40799
41483
|
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
40800
41484
|
});
|
|
40801
41485
|
|
|
@@ -47030,7 +47714,7 @@ var init_workflow_states = __esm(() => {
|
|
|
47030
47714
|
init_tasks();
|
|
47031
47715
|
init_types();
|
|
47032
47716
|
init_database();
|
|
47033
|
-
|
|
47717
|
+
init_config();
|
|
47034
47718
|
init_local_fields();
|
|
47035
47719
|
DEFAULT_WORKFLOW_STATES = [
|
|
47036
47720
|
{ name: "pending", canonical_status: "pending", aliases: ["todo", "backlog"], transitions: null, terminal: false },
|
|
@@ -48195,7 +48879,7 @@ var init_roadmaps = __esm(() => {
|
|
|
48195
48879
|
init_tasks();
|
|
48196
48880
|
init_plans();
|
|
48197
48881
|
init_task_runs();
|
|
48198
|
-
|
|
48882
|
+
init_config();
|
|
48199
48883
|
});
|
|
48200
48884
|
|
|
48201
48885
|
// src/lib/capacity-forecasts.ts
|
|
@@ -48420,7 +49104,7 @@ var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
|
|
|
48420
49104
|
var init_capacity_forecasts = __esm(() => {
|
|
48421
49105
|
init_tasks();
|
|
48422
49106
|
init_task_relations();
|
|
48423
|
-
|
|
49107
|
+
init_config();
|
|
48424
49108
|
});
|
|
48425
49109
|
|
|
48426
49110
|
// src/lib/audit-ledger.ts
|
|
@@ -48712,7 +49396,7 @@ var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1, LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "
|
|
|
48712
49396
|
var init_audit_ledger = __esm(() => {
|
|
48713
49397
|
init_database();
|
|
48714
49398
|
init_task_runs();
|
|
48715
|
-
|
|
49399
|
+
init_config();
|
|
48716
49400
|
init_redaction();
|
|
48717
49401
|
LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
|
|
48718
49402
|
});
|
|
@@ -54593,7 +55277,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
54593
55277
|
init_database();
|
|
54594
55278
|
init_redaction();
|
|
54595
55279
|
init_runner_sandbox();
|
|
54596
|
-
|
|
55280
|
+
init_config();
|
|
54597
55281
|
});
|
|
54598
55282
|
|
|
54599
55283
|
// src/lib/verification-providers.ts
|
|
@@ -54938,7 +55622,7 @@ var init_verification_providers = __esm(() => {
|
|
|
54938
55622
|
init_task_commits();
|
|
54939
55623
|
init_database();
|
|
54940
55624
|
init_tasks();
|
|
54941
|
-
|
|
55625
|
+
init_config();
|
|
54942
55626
|
init_redaction();
|
|
54943
55627
|
DEFAULT_RETRY = {
|
|
54944
55628
|
attempts: 1,
|
|
@@ -61764,7 +62448,7 @@ var init_review_queues = __esm(() => {
|
|
|
61764
62448
|
init_audit();
|
|
61765
62449
|
init_database();
|
|
61766
62450
|
init_tasks();
|
|
61767
|
-
|
|
62451
|
+
init_config();
|
|
61768
62452
|
init_event_emission_safety();
|
|
61769
62453
|
init_event_hooks();
|
|
61770
62454
|
init_task_contracts();
|
|
@@ -63651,7 +64335,7 @@ ID: ${updated.id}${taskNote}`
|
|
|
63651
64335
|
var init_agents2 = __esm(() => {
|
|
63652
64336
|
init_zod();
|
|
63653
64337
|
init_agents();
|
|
63654
|
-
|
|
64338
|
+
init_config();
|
|
63655
64339
|
init_database();
|
|
63656
64340
|
init_cloud_router();
|
|
63657
64341
|
});
|
|
@@ -64085,7 +64769,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
64085
64769
|
}
|
|
64086
64770
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
64087
64771
|
const path = outputPath ? resolve20(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
64088
|
-
|
|
64772
|
+
ensureDir(dirname9(path));
|
|
64089
64773
|
writeJsonFile(path, snapshot);
|
|
64090
64774
|
return path;
|
|
64091
64775
|
}
|
|
@@ -67728,7 +68412,7 @@ Commit Links (${commitRows.length}):`));
|
|
|
67728
68412
|
var init_config_serve_commands = __esm(() => {
|
|
67729
68413
|
init_database();
|
|
67730
68414
|
init_tasks();
|
|
67731
|
-
|
|
68415
|
+
init_config();
|
|
67732
68416
|
init_sync_utils();
|
|
67733
68417
|
init_helpers();
|
|
67734
68418
|
});
|
|
@@ -69934,7 +70618,7 @@ Findings`));
|
|
|
69934
70618
|
checks.push({ name: "Migrations", ok: false, message: "Could not read migration version" });
|
|
69935
70619
|
}
|
|
69936
70620
|
try {
|
|
69937
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (
|
|
70621
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
69938
70622
|
loadConfig2();
|
|
69939
70623
|
checks.push({ name: "Config", ok: true, message: "Loaded successfully" });
|
|
69940
70624
|
} catch (e) {
|
|
@@ -79365,7 +80049,7 @@ var init_factory = __esm(() => {
|
|
|
79365
80049
|
init_postgres_adapter();
|
|
79366
80050
|
init_shadow();
|
|
79367
80051
|
init_cloud_client();
|
|
79368
|
-
|
|
80052
|
+
init_config2();
|
|
79369
80053
|
});
|
|
79370
80054
|
|
|
79371
80055
|
// src/storage/s3-artifacts.ts
|
|
@@ -79807,6 +80491,7 @@ __export(exports_storage, {
|
|
|
79807
80491
|
signAwsV4Request: () => signAwsV4Request,
|
|
79808
80492
|
registerShadowExitFlush: () => registerShadowExitFlush,
|
|
79809
80493
|
postgresTodosSyncSchemaSql: () => postgresTodosSyncSchemaSql,
|
|
80494
|
+
postgresTodosCommentCursorIndexSql: () => postgresTodosCommentCursorIndexSql,
|
|
79810
80495
|
planRunArtifactsS3Sync: () => planRunArtifactsS3Sync,
|
|
79811
80496
|
parseStorageMode: () => parseStorageMode,
|
|
79812
80497
|
maybeInstallShadowCapture: () => maybeInstallShadowCapture2,
|
|
@@ -79814,6 +80499,7 @@ __export(exports_storage, {
|
|
|
79814
80499
|
loadStorageConfig: () => loadStorageConfig,
|
|
79815
80500
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
79816
80501
|
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
80502
|
+
isCommentRedactionBackfillComplete: () => isCommentRedactionBackfillComplete,
|
|
79817
80503
|
installShadowOutboxSchema: () => installShadowOutboxSchema,
|
|
79818
80504
|
importSqliteTodosStorageSnapshot: () => importSqliteTodosStorageSnapshot,
|
|
79819
80505
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
@@ -79841,6 +80527,7 @@ __export(exports_storage, {
|
|
|
79841
80527
|
closeRuntimeShadowCloud: () => closeRuntimeShadowCloud,
|
|
79842
80528
|
buildS3ObjectUrl: () => buildS3ObjectUrl,
|
|
79843
80529
|
buildS3ObjectKey: () => buildS3ObjectKey,
|
|
80530
|
+
backfillPostgresCommentRedaction: () => backfillPostgresCommentRedaction,
|
|
79844
80531
|
assertTodosShadowConfig: () => assertTodosShadowConfig,
|
|
79845
80532
|
assertTodosRemoteStorageConfig: () => assertTodosRemoteStorageConfig,
|
|
79846
80533
|
TodosShadowOutbox: () => TodosShadowOutbox,
|
|
@@ -79853,12 +80540,13 @@ __export(exports_storage, {
|
|
|
79853
80540
|
PostgresTodosSyncStore: () => PostgresTodosSyncStore,
|
|
79854
80541
|
DEFAULT_TODOS_POSTGRES_SYNC_TABLE: () => DEFAULT_TODOS_POSTGRES_SYNC_TABLE,
|
|
79855
80542
|
DEFAULT_TODOS_POSTGRES_CURSOR_TABLE: () => DEFAULT_TODOS_POSTGRES_CURSOR_TABLE,
|
|
80543
|
+
COMMENT_REDACTION_BACKFILL_CONFIRMATION: () => COMMENT_REDACTION_BACKFILL_CONFIRMATION,
|
|
79856
80544
|
CANONICAL_TODOS_RDS_RUNTIME_PATH: () => CANONICAL_TODOS_RDS_RUNTIME_PATH,
|
|
79857
80545
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
79858
80546
|
CANONICAL_TODOS_RDS_CLUSTER: () => CANONICAL_TODOS_RDS_CLUSTER
|
|
79859
80547
|
});
|
|
79860
80548
|
var init_storage2 = __esm(() => {
|
|
79861
|
-
|
|
80549
|
+
init_config2();
|
|
79862
80550
|
init_factory();
|
|
79863
80551
|
init_shadow();
|
|
79864
80552
|
init_shadow_outbox();
|
|
@@ -79867,6 +80555,7 @@ var init_storage2 = __esm(() => {
|
|
|
79867
80555
|
init_hybrid();
|
|
79868
80556
|
init_local_sqlite();
|
|
79869
80557
|
init_sqlite_snapshot();
|
|
80558
|
+
init_comment_redaction_backfill();
|
|
79870
80559
|
init_postgres_adapter();
|
|
79871
80560
|
init_s3_artifacts();
|
|
79872
80561
|
init_s3_artifact_sync();
|