@hasna/todos 0.11.86 → 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 +33 -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 +934 -412
- package/dist/contracts.js +1 -1
- package/dist/db/comments.d.ts.map +1 -1
- package/dist/index.js +167 -4
- package/dist/mcp/index.js +407 -38
- 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 +824 -383
- 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 +16 -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 +171 -4
- 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,82 @@ 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) };
|
|
8573
8953
|
}
|
|
8574
8954
|
async function cloudTaskHistory(client, taskId) {
|
|
8575
8955
|
const raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/history`);
|
|
@@ -8657,8 +9037,8 @@ async function cloudLockTask(client, id, agentId) {
|
|
|
8657
9037
|
}
|
|
8658
9038
|
return raw ?? { success: true };
|
|
8659
9039
|
}
|
|
8660
|
-
async function cloudUnlockTask(client, id, agentId) {
|
|
8661
|
-
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 } : {} });
|
|
8662
9042
|
if (raw && typeof raw === "object" && "success" in raw) {
|
|
8663
9043
|
return Boolean(raw.success);
|
|
8664
9044
|
}
|
|
@@ -8787,6 +9167,25 @@ async function cloudListTaskLists(client, projectId) {
|
|
|
8787
9167
|
return envelope.taskLists;
|
|
8788
9168
|
return Array.isArray(raw) ? raw : [];
|
|
8789
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
|
+
}
|
|
8790
9189
|
async function cloudNextTask(client, agent, filters) {
|
|
8791
9190
|
const query = {};
|
|
8792
9191
|
if (agent)
|
|
@@ -8811,11 +9210,11 @@ async function cloudAllDependencies(client) {
|
|
|
8811
9210
|
return Array.isArray(raw) ? raw : [];
|
|
8812
9211
|
}
|
|
8813
9212
|
async function cloudGetTasksByIds(client, ids) {
|
|
8814
|
-
const
|
|
9213
|
+
const unique2 = Array.from(new Set(ids));
|
|
8815
9214
|
const map = new Map;
|
|
8816
9215
|
const CONCURRENCY = 8;
|
|
8817
|
-
for (let i = 0;i <
|
|
8818
|
-
const batch =
|
|
9216
|
+
for (let i = 0;i < unique2.length; i += CONCURRENCY) {
|
|
9217
|
+
const batch = unique2.slice(i, i + CONCURRENCY);
|
|
8819
9218
|
const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
|
|
8820
9219
|
for (const task of tasks)
|
|
8821
9220
|
if (task && task.id)
|
|
@@ -8913,6 +9312,7 @@ async function cloudTimeline(client, options = {}) {
|
|
|
8913
9312
|
var _cache, PRIORITY_RANK;
|
|
8914
9313
|
var init_cloud_router = __esm(() => {
|
|
8915
9314
|
init_storage();
|
|
9315
|
+
init_redaction();
|
|
8916
9316
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
8917
9317
|
});
|
|
8918
9318
|
|
|
@@ -11215,7 +11615,7 @@ var init_schema = __esm(() => {
|
|
|
11215
11615
|
});
|
|
11216
11616
|
|
|
11217
11617
|
// src/db/machines.ts
|
|
11218
|
-
import { existsSync as
|
|
11618
|
+
import { existsSync as existsSync4 } from "fs";
|
|
11219
11619
|
import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
|
|
11220
11620
|
import { resolve } from "path";
|
|
11221
11621
|
import { spawnSync } from "child_process";
|
|
@@ -11422,7 +11822,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11422
11822
|
message: `${project.name} has ${distinctPaths.length} different machine-local paths`
|
|
11423
11823
|
});
|
|
11424
11824
|
}
|
|
11425
|
-
if (localRow && !
|
|
11825
|
+
if (localRow && !existsSync4(localRow.path)) {
|
|
11426
11826
|
pathIssues.push({
|
|
11427
11827
|
type: "path_missing",
|
|
11428
11828
|
project_id: project.id,
|
|
@@ -11433,7 +11833,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11433
11833
|
message: `Local path does not exist on this machine: ${localRow.path}`
|
|
11434
11834
|
});
|
|
11435
11835
|
}
|
|
11436
|
-
if (!localRow && project.path && machineById.has(localMachine.id) && !
|
|
11836
|
+
if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync4(project.path)) {
|
|
11437
11837
|
pathIssues.push({
|
|
11438
11838
|
type: "path_missing",
|
|
11439
11839
|
project_id: project.id,
|
|
@@ -11541,8 +11941,8 @@ var init_machines = __esm(() => {
|
|
|
11541
11941
|
});
|
|
11542
11942
|
|
|
11543
11943
|
// src/storage/config.ts
|
|
11544
|
-
var
|
|
11545
|
-
__export(
|
|
11944
|
+
var exports_config2 = {};
|
|
11945
|
+
__export(exports_config2, {
|
|
11546
11946
|
parseStorageMode: () => parseStorageMode,
|
|
11547
11947
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
11548
11948
|
loadStorageConfig: () => loadStorageConfig,
|
|
@@ -11705,7 +12105,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
11705
12105
|
return parsed;
|
|
11706
12106
|
}
|
|
11707
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";
|
|
11708
|
-
var
|
|
12108
|
+
var init_config2 = __esm(() => {
|
|
11709
12109
|
TODOS_STORAGE_TABLES = [
|
|
11710
12110
|
"todos_sync_records",
|
|
11711
12111
|
"todos_sync_cursors"
|
|
@@ -11844,8 +12244,8 @@ __export(exports_database, {
|
|
|
11844
12244
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
11845
12245
|
});
|
|
11846
12246
|
import { Database } from "bun:sqlite";
|
|
11847
|
-
import { existsSync as
|
|
11848
|
-
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";
|
|
11849
12249
|
function isInMemoryDb(path) {
|
|
11850
12250
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
11851
12251
|
}
|
|
@@ -11854,12 +12254,12 @@ function findNearestProjectDb(startDir) {
|
|
|
11854
12254
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
11855
12255
|
let dir = resolve2(startDir);
|
|
11856
12256
|
while (true) {
|
|
11857
|
-
const candidate =
|
|
11858
|
-
if (
|
|
12257
|
+
const candidate = join4(dir, ".hasna", "todos", "todos.db");
|
|
12258
|
+
if (existsSync5(candidate))
|
|
11859
12259
|
return candidate;
|
|
11860
12260
|
if (dir === stopAt)
|
|
11861
12261
|
break;
|
|
11862
|
-
const parent =
|
|
12262
|
+
const parent = dirname3(dir);
|
|
11863
12263
|
if (parent === dir)
|
|
11864
12264
|
break;
|
|
11865
12265
|
dir = parent;
|
|
@@ -11869,9 +12269,9 @@ function findNearestProjectDb(startDir) {
|
|
|
11869
12269
|
function findGitRoot(startDir) {
|
|
11870
12270
|
let dir = resolve2(startDir);
|
|
11871
12271
|
while (true) {
|
|
11872
|
-
if (
|
|
12272
|
+
if (existsSync5(join4(dir, ".git")))
|
|
11873
12273
|
return dir;
|
|
11874
|
-
const parent =
|
|
12274
|
+
const parent = dirname3(dir);
|
|
11875
12275
|
if (parent === dir)
|
|
11876
12276
|
break;
|
|
11877
12277
|
dir = parent;
|
|
@@ -11892,25 +12292,25 @@ function getDbPath() {
|
|
|
11892
12292
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
11893
12293
|
const gitRoot = findGitRoot(cwd);
|
|
11894
12294
|
if (gitRoot) {
|
|
11895
|
-
return
|
|
12295
|
+
return join4(gitRoot, ".hasna", "todos", "todos.db");
|
|
11896
12296
|
}
|
|
11897
12297
|
}
|
|
11898
12298
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
11899
|
-
return
|
|
12299
|
+
return join4(home, ".hasna", "todos", "todos.db");
|
|
11900
12300
|
}
|
|
11901
12301
|
function getDatabasePath() {
|
|
11902
12302
|
return getDbPath();
|
|
11903
12303
|
}
|
|
11904
|
-
function
|
|
12304
|
+
function ensureDir2(filePath) {
|
|
11905
12305
|
if (isInMemoryDb(filePath))
|
|
11906
12306
|
return;
|
|
11907
|
-
const dir =
|
|
11908
|
-
if (!
|
|
11909
|
-
|
|
12307
|
+
const dir = dirname3(resolve2(filePath));
|
|
12308
|
+
if (!existsSync5(dir)) {
|
|
12309
|
+
mkdirSync2(dir, { recursive: true });
|
|
11910
12310
|
}
|
|
11911
12311
|
}
|
|
11912
12312
|
function openDatabase(path) {
|
|
11913
|
-
|
|
12313
|
+
ensureDir2(path);
|
|
11914
12314
|
const db = new Database(path);
|
|
11915
12315
|
db.run("PRAGMA journal_mode = WAL");
|
|
11916
12316
|
db.run("PRAGMA busy_timeout = 5000");
|
|
@@ -11923,7 +12323,7 @@ function openDatabase(path) {
|
|
|
11923
12323
|
}
|
|
11924
12324
|
function maybeInstallShadowCapture(db) {
|
|
11925
12325
|
try {
|
|
11926
|
-
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (
|
|
12326
|
+
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (init_config2(), __toCommonJS(exports_config2));
|
|
11927
12327
|
if (!isTodosShadowEnabled2())
|
|
11928
12328
|
return;
|
|
11929
12329
|
const { installShadowOutboxSchema: installShadowOutboxSchema2 } = (init_shadow_outbox_schema(), __toCommonJS(exports_shadow_outbox_schema));
|
|
@@ -12704,199 +13104,6 @@ var init_helpers = __esm(() => {
|
|
|
12704
13104
|
};
|
|
12705
13105
|
});
|
|
12706
13106
|
|
|
12707
|
-
// src/lib/sync-utils.ts
|
|
12708
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
12709
|
-
import { join as join3 } from "path";
|
|
12710
|
-
function getHomeDir() {
|
|
12711
|
-
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
12712
|
-
}
|
|
12713
|
-
function getTodosGlobalDir() {
|
|
12714
|
-
return join3(getHomeDir(), ".hasna", "todos");
|
|
12715
|
-
}
|
|
12716
|
-
function ensureDir2(dir) {
|
|
12717
|
-
if (!existsSync4(dir))
|
|
12718
|
-
mkdirSync2(dir, { recursive: true });
|
|
12719
|
-
}
|
|
12720
|
-
function listJsonFiles(dir) {
|
|
12721
|
-
if (!existsSync4(dir))
|
|
12722
|
-
return [];
|
|
12723
|
-
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
12724
|
-
}
|
|
12725
|
-
function readJsonFile(path) {
|
|
12726
|
-
try {
|
|
12727
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
12728
|
-
} catch {
|
|
12729
|
-
return null;
|
|
12730
|
-
}
|
|
12731
|
-
}
|
|
12732
|
-
function writeJsonFile(path, data) {
|
|
12733
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
12734
|
-
`);
|
|
12735
|
-
}
|
|
12736
|
-
function readHighWaterMark(dir) {
|
|
12737
|
-
const path = join3(dir, ".highwatermark");
|
|
12738
|
-
if (!existsSync4(path))
|
|
12739
|
-
return 1;
|
|
12740
|
-
const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
|
|
12741
|
-
return isNaN(val) ? 1 : val;
|
|
12742
|
-
}
|
|
12743
|
-
function writeHighWaterMark(dir, value) {
|
|
12744
|
-
writeFileSync(join3(dir, ".highwatermark"), String(value));
|
|
12745
|
-
}
|
|
12746
|
-
function getFileMtimeMs(path) {
|
|
12747
|
-
try {
|
|
12748
|
-
return statSync(path).mtimeMs;
|
|
12749
|
-
} catch {
|
|
12750
|
-
return null;
|
|
12751
|
-
}
|
|
12752
|
-
}
|
|
12753
|
-
function parseTimestamp(value) {
|
|
12754
|
-
if (typeof value !== "string")
|
|
12755
|
-
return null;
|
|
12756
|
-
const parsed = Date.parse(value);
|
|
12757
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
12758
|
-
}
|
|
12759
|
-
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
12760
|
-
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
12761
|
-
const next = [conflict, ...current].slice(0, limit);
|
|
12762
|
-
return { ...metadata, sync_conflicts: next };
|
|
12763
|
-
}
|
|
12764
|
-
var HOME;
|
|
12765
|
-
var init_sync_utils = __esm(() => {
|
|
12766
|
-
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
12767
|
-
});
|
|
12768
|
-
|
|
12769
|
-
// src/lib/config.ts
|
|
12770
|
-
var exports_config2 = {};
|
|
12771
|
-
__export(exports_config2, {
|
|
12772
|
-
updateConfig: () => updateConfig,
|
|
12773
|
-
saveConfig: () => saveConfig,
|
|
12774
|
-
resetConfig: () => resetConfig,
|
|
12775
|
-
normalizeApiUrl: () => normalizeApiUrl,
|
|
12776
|
-
loadConfig: () => loadConfig,
|
|
12777
|
-
getTaskPrefixConfig: () => getTaskPrefixConfig,
|
|
12778
|
-
getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
|
|
12779
|
-
getLocalApiConfig: () => getLocalApiConfig,
|
|
12780
|
-
getConfigPath: () => getConfigPath,
|
|
12781
|
-
getCompletionGuardConfig: () => getCompletionGuardConfig,
|
|
12782
|
-
getAgentTasksDir: () => getAgentTasksDir,
|
|
12783
|
-
getAgentTaskListId: () => getAgentTaskListId,
|
|
12784
|
-
getAgentPoolForProject: () => getAgentPoolForProject
|
|
12785
|
-
});
|
|
12786
|
-
import { existsSync as existsSync5 } from "fs";
|
|
12787
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
12788
|
-
function getConfigPath() {
|
|
12789
|
-
return join4(getTodosGlobalDir(), "config.json");
|
|
12790
|
-
}
|
|
12791
|
-
function resetConfig() {
|
|
12792
|
-
cached = null;
|
|
12793
|
-
}
|
|
12794
|
-
function normalizeAgent(agent) {
|
|
12795
|
-
return agent.trim().toLowerCase();
|
|
12796
|
-
}
|
|
12797
|
-
function loadConfig() {
|
|
12798
|
-
if (cached)
|
|
12799
|
-
return cached;
|
|
12800
|
-
if (!existsSync5(getConfigPath())) {
|
|
12801
|
-
cached = {};
|
|
12802
|
-
return cached;
|
|
12803
|
-
}
|
|
12804
|
-
const config = readJsonFile(getConfigPath()) || {};
|
|
12805
|
-
if (typeof config.sync_agents === "string") {
|
|
12806
|
-
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
12807
|
-
}
|
|
12808
|
-
cached = config;
|
|
12809
|
-
return cached;
|
|
12810
|
-
}
|
|
12811
|
-
function saveConfig(config) {
|
|
12812
|
-
const configPath = getConfigPath();
|
|
12813
|
-
ensureDir2(dirname3(configPath));
|
|
12814
|
-
writeJsonFile(configPath, config);
|
|
12815
|
-
cached = config;
|
|
12816
|
-
return config;
|
|
12817
|
-
}
|
|
12818
|
-
function updateConfig(patch) {
|
|
12819
|
-
return saveConfig({ ...loadConfig(), ...patch });
|
|
12820
|
-
}
|
|
12821
|
-
function normalizeApiUrl(value) {
|
|
12822
|
-
const trimmed = value?.trim();
|
|
12823
|
-
if (!trimmed)
|
|
12824
|
-
return null;
|
|
12825
|
-
return trimmed.replace(/\/+$/, "");
|
|
12826
|
-
}
|
|
12827
|
-
function getLocalApiConfig(env = process.env) {
|
|
12828
|
-
const config = loadConfig();
|
|
12829
|
-
const envApiUrl = normalizeApiUrl(env["TODOS_URL"]);
|
|
12830
|
-
const configApiUrl = normalizeApiUrl(config.apiUrl);
|
|
12831
|
-
const apiUrl = envApiUrl ?? configApiUrl;
|
|
12832
|
-
const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
|
|
12833
|
-
return {
|
|
12834
|
-
apiUrl,
|
|
12835
|
-
apiKey,
|
|
12836
|
-
source: {
|
|
12837
|
-
apiUrl: envApiUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
|
|
12838
|
-
apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
|
|
12839
|
-
}
|
|
12840
|
-
};
|
|
12841
|
-
}
|
|
12842
|
-
function getSyncAgentsFromConfig() {
|
|
12843
|
-
const config = loadConfig();
|
|
12844
|
-
const agents = config.sync_agents;
|
|
12845
|
-
if (Array.isArray(agents) && agents.length > 0)
|
|
12846
|
-
return agents.map(normalizeAgent);
|
|
12847
|
-
return null;
|
|
12848
|
-
}
|
|
12849
|
-
function getAgentTaskListId(agent) {
|
|
12850
|
-
const config = loadConfig();
|
|
12851
|
-
const key = normalizeAgent(agent);
|
|
12852
|
-
return config.agents?.[key]?.task_list_id || config.task_list_id || null;
|
|
12853
|
-
}
|
|
12854
|
-
function getAgentTasksDir(agent) {
|
|
12855
|
-
const config = loadConfig();
|
|
12856
|
-
const key = normalizeAgent(agent);
|
|
12857
|
-
return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
|
|
12858
|
-
}
|
|
12859
|
-
function getTaskPrefixConfig() {
|
|
12860
|
-
const config = loadConfig();
|
|
12861
|
-
return config.task_prefix || null;
|
|
12862
|
-
}
|
|
12863
|
-
function getAgentPoolForProject(workingDir) {
|
|
12864
|
-
const config = loadConfig();
|
|
12865
|
-
if (workingDir && config.project_pools) {
|
|
12866
|
-
let bestKey = null;
|
|
12867
|
-
let bestLen = 0;
|
|
12868
|
-
for (const key of Object.keys(config.project_pools)) {
|
|
12869
|
-
if (workingDir.startsWith(key) && key.length > bestLen) {
|
|
12870
|
-
bestKey = key;
|
|
12871
|
-
bestLen = key.length;
|
|
12872
|
-
}
|
|
12873
|
-
}
|
|
12874
|
-
if (bestKey && config.project_pools[bestKey]) {
|
|
12875
|
-
return config.project_pools[bestKey];
|
|
12876
|
-
}
|
|
12877
|
-
}
|
|
12878
|
-
return config.agent_pool || null;
|
|
12879
|
-
}
|
|
12880
|
-
function getCompletionGuardConfig(projectPath) {
|
|
12881
|
-
const config = loadConfig();
|
|
12882
|
-
const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
12883
|
-
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
12884
|
-
return { ...global, ...config.project_overrides[projectPath].completion_guard };
|
|
12885
|
-
}
|
|
12886
|
-
return global;
|
|
12887
|
-
}
|
|
12888
|
-
var cached = null, GUARD_DEFAULTS;
|
|
12889
|
-
var init_config2 = __esm(() => {
|
|
12890
|
-
init_sync_utils();
|
|
12891
|
-
GUARD_DEFAULTS = {
|
|
12892
|
-
enabled: false,
|
|
12893
|
-
min_work_seconds: 30,
|
|
12894
|
-
max_completions_per_window: 5,
|
|
12895
|
-
window_minutes: 10,
|
|
12896
|
-
cooldown_seconds: 60
|
|
12897
|
-
};
|
|
12898
|
-
});
|
|
12899
|
-
|
|
12900
13107
|
// src/lib/completion-guard.ts
|
|
12901
13108
|
function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
12902
13109
|
let config;
|
|
@@ -12942,7 +13149,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
12942
13149
|
}
|
|
12943
13150
|
var init_completion_guard = __esm(() => {
|
|
12944
13151
|
init_types();
|
|
12945
|
-
|
|
13152
|
+
init_config();
|
|
12946
13153
|
init_projects();
|
|
12947
13154
|
});
|
|
12948
13155
|
|
|
@@ -12997,117 +13204,6 @@ var init_event_emission_safety = __esm(() => {
|
|
|
12997
13204
|
init_sync_utils();
|
|
12998
13205
|
});
|
|
12999
13206
|
|
|
13000
|
-
// src/lib/redaction.ts
|
|
13001
|
-
var exports_redaction = {};
|
|
13002
|
-
__export(exports_redaction, {
|
|
13003
|
-
upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
|
|
13004
|
-
redactValue: () => redactValue,
|
|
13005
|
-
redactEvidenceText: () => redactEvidenceText,
|
|
13006
|
-
listSecretFindings: () => listSecretFindings,
|
|
13007
|
-
hasSecretFindings: () => hasSecretFindings,
|
|
13008
|
-
getSecretSafetyConfig: () => getSecretSafetyConfig
|
|
13009
|
-
});
|
|
13010
|
-
function unique(values) {
|
|
13011
|
-
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
13012
|
-
}
|
|
13013
|
-
function cloneRegex(regex) {
|
|
13014
|
-
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
13015
|
-
}
|
|
13016
|
-
function customPatterns() {
|
|
13017
|
-
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
13018
|
-
try {
|
|
13019
|
-
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
13020
|
-
} catch {
|
|
13021
|
-
return [];
|
|
13022
|
-
}
|
|
13023
|
-
});
|
|
13024
|
-
}
|
|
13025
|
-
function secretPatterns() {
|
|
13026
|
-
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
13027
|
-
}
|
|
13028
|
-
function isSecretKey(key) {
|
|
13029
|
-
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
13030
|
-
return false;
|
|
13031
|
-
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
13032
|
-
return true;
|
|
13033
|
-
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
13034
|
-
}
|
|
13035
|
-
function redactEvidenceText(value) {
|
|
13036
|
-
let redacted = value;
|
|
13037
|
-
for (const pattern of secretPatterns()) {
|
|
13038
|
-
const regex = cloneRegex(pattern.regex);
|
|
13039
|
-
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
13040
|
-
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
13041
|
-
}
|
|
13042
|
-
return redacted;
|
|
13043
|
-
}
|
|
13044
|
-
function redactValue(value) {
|
|
13045
|
-
if (typeof value === "string")
|
|
13046
|
-
return redactEvidenceText(value);
|
|
13047
|
-
if (Array.isArray(value))
|
|
13048
|
-
return value.map(redactValue);
|
|
13049
|
-
if (value && typeof value === "object") {
|
|
13050
|
-
const redacted = {};
|
|
13051
|
-
for (const [key, child] of Object.entries(value)) {
|
|
13052
|
-
if (isSecretKey(key)) {
|
|
13053
|
-
redacted[key] = "[REDACTED]";
|
|
13054
|
-
} else {
|
|
13055
|
-
redacted[key] = redactValue(child);
|
|
13056
|
-
}
|
|
13057
|
-
}
|
|
13058
|
-
return redacted;
|
|
13059
|
-
}
|
|
13060
|
-
return value;
|
|
13061
|
-
}
|
|
13062
|
-
function listSecretFindings(value) {
|
|
13063
|
-
const findings = [];
|
|
13064
|
-
for (const pattern of secretPatterns()) {
|
|
13065
|
-
const matches = value.match(cloneRegex(pattern.regex));
|
|
13066
|
-
if (matches?.length)
|
|
13067
|
-
findings.push({ pattern: pattern.name, count: matches.length });
|
|
13068
|
-
}
|
|
13069
|
-
return findings;
|
|
13070
|
-
}
|
|
13071
|
-
function hasSecretFindings(value) {
|
|
13072
|
-
return listSecretFindings(value).length > 0;
|
|
13073
|
-
}
|
|
13074
|
-
function getSecretSafetyConfig() {
|
|
13075
|
-
return {
|
|
13076
|
-
redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
|
|
13077
|
-
redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
|
|
13078
|
-
};
|
|
13079
|
-
}
|
|
13080
|
-
function upsertSecretSafetyConfig(input) {
|
|
13081
|
-
const config = loadConfig();
|
|
13082
|
-
const next = {
|
|
13083
|
-
redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
|
|
13084
|
-
redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
|
|
13085
|
-
};
|
|
13086
|
-
saveConfig({ ...config, secret_safety: next });
|
|
13087
|
-
return next;
|
|
13088
|
-
}
|
|
13089
|
-
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
|
|
13090
|
-
var init_redaction = __esm(() => {
|
|
13091
|
-
init_config2();
|
|
13092
|
-
DEFAULT_SECRET_PATTERNS = [
|
|
13093
|
-
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
13094
|
-
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
13095
|
-
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
13096
|
-
{ 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]" },
|
|
13097
|
-
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
13098
|
-
];
|
|
13099
|
-
DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
13100
|
-
NON_SECRET_USAGE_KEYS = new Set([
|
|
13101
|
-
"tokens",
|
|
13102
|
-
"total_tokens",
|
|
13103
|
-
"token_count",
|
|
13104
|
-
"input_tokens",
|
|
13105
|
-
"output_tokens",
|
|
13106
|
-
"prompt_tokens",
|
|
13107
|
-
"completion_tokens"
|
|
13108
|
-
]);
|
|
13109
|
-
});
|
|
13110
|
-
|
|
13111
13207
|
// src/lib/workspace-trust.ts
|
|
13112
13208
|
var exports_workspace_trust = {};
|
|
13113
13209
|
__export(exports_workspace_trust, {
|
|
@@ -13253,7 +13349,7 @@ function checkWorkspacePermission(input = {}) {
|
|
|
13253
13349
|
}
|
|
13254
13350
|
var DEFAULT_DENYLIST, DEFAULT_ENV_REDACTIONS, PRESET_DEFAULTS;
|
|
13255
13351
|
var init_workspace_trust = __esm(() => {
|
|
13256
|
-
|
|
13352
|
+
init_config();
|
|
13257
13353
|
DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13258
13354
|
DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
13259
13355
|
PRESET_DEFAULTS = {
|
|
@@ -13496,7 +13592,7 @@ function explainRunnerSandbox(input = {}) {
|
|
|
13496
13592
|
}
|
|
13497
13593
|
var DEFAULT_COMMAND_DENYLIST, DEFAULT_ENV_REDACTIONS2;
|
|
13498
13594
|
var init_runner_sandbox = __esm(() => {
|
|
13499
|
-
|
|
13595
|
+
init_config();
|
|
13500
13596
|
init_workspace_trust();
|
|
13501
13597
|
DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13502
13598
|
DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
@@ -13743,7 +13839,7 @@ var LOCAL_EVENT_TYPES, VALID_TARGETS;
|
|
|
13743
13839
|
var init_event_hooks = __esm(() => {
|
|
13744
13840
|
init_redaction();
|
|
13745
13841
|
init_runner_sandbox();
|
|
13746
|
-
|
|
13842
|
+
init_config();
|
|
13747
13843
|
init_event_emission_safety();
|
|
13748
13844
|
LOCAL_EVENT_TYPES = [
|
|
13749
13845
|
"task.created",
|
|
@@ -18628,7 +18724,7 @@ function getComment(id, db) {
|
|
|
18628
18724
|
}
|
|
18629
18725
|
function listComments(taskId, db) {
|
|
18630
18726
|
const d = db || getDatabase();
|
|
18631
|
-
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);
|
|
18632
18728
|
}
|
|
18633
18729
|
function updateComment(id, input, db) {
|
|
18634
18730
|
const d = db || getDatabase();
|
|
@@ -20256,10 +20352,27 @@ var init_task_routing = __esm(() => {
|
|
|
20256
20352
|
// src/cli/commands/task-commands.ts
|
|
20257
20353
|
var exports_task_commands = {};
|
|
20258
20354
|
__export(exports_task_commands, {
|
|
20259
|
-
registerTaskCommands: () => registerTaskCommands
|
|
20355
|
+
registerTaskCommands: () => registerTaskCommands,
|
|
20356
|
+
escapeTerminalControls: () => escapeTerminalControls
|
|
20260
20357
|
});
|
|
20261
20358
|
import chalk2 from "chalk";
|
|
20262
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
|
+
}
|
|
20263
20376
|
function resolveProjectIdOrSlug(input) {
|
|
20264
20377
|
const db = getDatabase();
|
|
20265
20378
|
if (isPathLike(input)) {
|
|
@@ -20416,18 +20529,24 @@ function registerTaskCommands(program2) {
|
|
|
20416
20529
|
if (cloud) {
|
|
20417
20530
|
let task3;
|
|
20418
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
|
+
}
|
|
20419
20537
|
task3 = await cloudCreateTask(cloud, {
|
|
20420
20538
|
title,
|
|
20421
20539
|
description: opts.description,
|
|
20422
20540
|
priority: parsePriority(opts.priority),
|
|
20541
|
+
parent_id: opts.parent,
|
|
20423
20542
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
20424
20543
|
plan_id: opts.plan,
|
|
20425
20544
|
assigned_to: opts.assign,
|
|
20426
20545
|
status: parseStatus(opts.status),
|
|
20427
|
-
task_list_id:
|
|
20546
|
+
task_list_id: cloudTaskListId,
|
|
20428
20547
|
agent_id: globalOpts.agent,
|
|
20429
20548
|
session_id: globalOpts.session,
|
|
20430
|
-
project_id:
|
|
20549
|
+
project_id: cloudProjectId,
|
|
20431
20550
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
20432
20551
|
sla_minutes: opts.slaMinutes !== undefined || opts.sla !== undefined ? parseIntOption(opts.slaMinutes ?? opts.sla, "--sla-minutes") : undefined,
|
|
20433
20552
|
requires_approval: opts.approval || undefined,
|
|
@@ -20786,7 +20905,22 @@ function registerTaskCommands(program2) {
|
|
|
20786
20905
|
let task2;
|
|
20787
20906
|
if (cloud) {
|
|
20788
20907
|
const remote = await cloudGetTask(cloud, resolveTaskId(id));
|
|
20789
|
-
|
|
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;
|
|
20790
20924
|
} else {
|
|
20791
20925
|
const resolvedId = resolveTaskId(id);
|
|
20792
20926
|
task2 = getTaskWithRelations(resolvedId);
|
|
@@ -20870,11 +21004,11 @@ function registerTaskCommands(program2) {
|
|
|
20870
21004
|
}
|
|
20871
21005
|
}
|
|
20872
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" : "";
|
|
20873
21008
|
console.log(chalk2.bold(`
|
|
20874
|
-
Comments (${task2.comments.length}):`));
|
|
21009
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
20875
21010
|
for (const c of task2.comments) {
|
|
20876
|
-
|
|
20877
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21011
|
+
console.log(formatHumanComment(c));
|
|
20878
21012
|
}
|
|
20879
21013
|
}
|
|
20880
21014
|
});
|
|
@@ -20900,7 +21034,23 @@ function registerTaskCommands(program2) {
|
|
|
20900
21034
|
let task2;
|
|
20901
21035
|
if (cloud) {
|
|
20902
21036
|
const remote = await cloudGetTask(cloud, resolvedId);
|
|
20903
|
-
|
|
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;
|
|
20904
21054
|
} else {
|
|
20905
21055
|
task2 = getTaskWithRelations(resolvedId);
|
|
20906
21056
|
}
|
|
@@ -21007,11 +21157,11 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21007
21157
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
21008
21158
|
}
|
|
21009
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" : "";
|
|
21010
21161
|
console.log(chalk2.bold(`
|
|
21011
|
-
Comments (${task2.comments.length}):`));
|
|
21162
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
21012
21163
|
for (const c of task2.comments) {
|
|
21013
|
-
|
|
21014
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21164
|
+
console.log(formatHumanComment(c));
|
|
21015
21165
|
}
|
|
21016
21166
|
}
|
|
21017
21167
|
if (task2.checklist && task2.checklist.length > 0) {
|
|
@@ -21307,7 +21457,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21307
21457
|
const resolvedId = resolveTaskId(id);
|
|
21308
21458
|
try {
|
|
21309
21459
|
if (cloud)
|
|
21310
|
-
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent);
|
|
21460
|
+
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent, !globalOpts.agent);
|
|
21311
21461
|
else
|
|
21312
21462
|
unlockTask(resolvedId, globalOpts.agent);
|
|
21313
21463
|
} catch (e) {
|
|
@@ -23262,7 +23412,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
23262
23412
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
23263
23413
|
const dir = getTaskListDir(taskListId);
|
|
23264
23414
|
if (!existsSync10(dir))
|
|
23265
|
-
|
|
23415
|
+
ensureDir(dir);
|
|
23266
23416
|
const filter = {};
|
|
23267
23417
|
if (projectId)
|
|
23268
23418
|
filter["project_id"] = projectId;
|
|
@@ -23445,7 +23595,7 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
23445
23595
|
}
|
|
23446
23596
|
var init_claude_tasks = __esm(() => {
|
|
23447
23597
|
init_tasks();
|
|
23448
|
-
|
|
23598
|
+
init_config();
|
|
23449
23599
|
init_sync_utils();
|
|
23450
23600
|
});
|
|
23451
23601
|
|
|
@@ -23489,7 +23639,7 @@ function metadataKey(agent) {
|
|
|
23489
23639
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
23490
23640
|
const dir = getTaskListDir2(agent, taskListId);
|
|
23491
23641
|
if (!existsSync11(dir))
|
|
23492
|
-
|
|
23642
|
+
ensureDir(dir);
|
|
23493
23643
|
const filter = {};
|
|
23494
23644
|
if (projectId)
|
|
23495
23645
|
filter["project_id"] = projectId;
|
|
@@ -23662,7 +23812,7 @@ function syncAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
23662
23812
|
var init_agent_tasks = __esm(() => {
|
|
23663
23813
|
init_tasks();
|
|
23664
23814
|
init_sync_utils();
|
|
23665
|
-
|
|
23815
|
+
init_config();
|
|
23666
23816
|
});
|
|
23667
23817
|
|
|
23668
23818
|
// src/lib/sync.ts
|
|
@@ -23734,7 +23884,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
23734
23884
|
var init_sync = __esm(() => {
|
|
23735
23885
|
init_claude_tasks();
|
|
23736
23886
|
init_agent_tasks();
|
|
23737
|
-
|
|
23887
|
+
init_config();
|
|
23738
23888
|
});
|
|
23739
23889
|
|
|
23740
23890
|
// src/lib/project-bootstrap.ts
|
|
@@ -31447,7 +31597,7 @@ function applyExportProfile(data, options = {}) {
|
|
|
31447
31597
|
}
|
|
31448
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;
|
|
31449
31599
|
var init_local_encryption = __esm(() => {
|
|
31450
|
-
|
|
31600
|
+
init_config();
|
|
31451
31601
|
init_redaction();
|
|
31452
31602
|
EncryptionKeyUnavailableError = class EncryptionKeyUnavailableError extends Error {
|
|
31453
31603
|
keyEnv;
|
|
@@ -32626,7 +32776,7 @@ var init_project_commands = __esm(() => {
|
|
|
32626
32776
|
init_cloud_router();
|
|
32627
32777
|
init_saved_search_views();
|
|
32628
32778
|
init_sync();
|
|
32629
|
-
|
|
32779
|
+
init_config();
|
|
32630
32780
|
init_helpers();
|
|
32631
32781
|
});
|
|
32632
32782
|
|
|
@@ -33626,9 +33776,11 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33626
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) => {
|
|
33627
33777
|
try {
|
|
33628
33778
|
const globalOpts = program2.opts();
|
|
33629
|
-
const
|
|
33779
|
+
const cloud = getTodosCloudClient();
|
|
33780
|
+
const projectId = cloud ? globalOpts.project : autoProject(globalOpts);
|
|
33630
33781
|
if (opts.add) {
|
|
33631
|
-
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);
|
|
33632
33784
|
if (globalOpts.json) {
|
|
33633
33785
|
output(list, true);
|
|
33634
33786
|
return;
|
|
@@ -33640,6 +33792,14 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33640
33792
|
return;
|
|
33641
33793
|
}
|
|
33642
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
|
+
}
|
|
33643
33803
|
const db = getDatabase();
|
|
33644
33804
|
const resolved = resolvePartialId(db, "task_lists", opts.delete);
|
|
33645
33805
|
if (!resolved) {
|
|
@@ -33650,7 +33810,6 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33650
33810
|
console.log(chalk5.green("Task list deleted."));
|
|
33651
33811
|
return;
|
|
33652
33812
|
}
|
|
33653
|
-
const cloud = getTodosCloudClient();
|
|
33654
33813
|
const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
|
|
33655
33814
|
if (globalOpts.json) {
|
|
33656
33815
|
output(lists, true);
|
|
@@ -35065,7 +35224,7 @@ function renderExtensionSummary(record) {
|
|
|
35065
35224
|
}
|
|
35066
35225
|
var BUILTIN_CLI_COMMANDS;
|
|
35067
35226
|
var init_local_extensions = __esm(() => {
|
|
35068
|
-
|
|
35227
|
+
init_config();
|
|
35069
35228
|
init_mcp();
|
|
35070
35229
|
init_package_version();
|
|
35071
35230
|
init_redaction();
|
|
@@ -35497,7 +35656,7 @@ var init_policy_packs = __esm(() => {
|
|
|
35497
35656
|
init_database();
|
|
35498
35657
|
init_tasks();
|
|
35499
35658
|
init_task_runs();
|
|
35500
|
-
|
|
35659
|
+
init_config();
|
|
35501
35660
|
});
|
|
35502
35661
|
|
|
35503
35662
|
// src/db/checkpoints.ts
|
|
@@ -36020,7 +36179,7 @@ function describeTerminalNotificationRule(rule) {
|
|
|
36020
36179
|
}
|
|
36021
36180
|
var SEVERITY_ORDER, EVENT_SEVERITY, VALID_SEVERITIES, VALID_FORMATS;
|
|
36022
36181
|
var init_terminal_notifications = __esm(() => {
|
|
36023
|
-
|
|
36182
|
+
init_config();
|
|
36024
36183
|
init_event_hooks();
|
|
36025
36184
|
init_redaction();
|
|
36026
36185
|
SEVERITY_ORDER = {
|
|
@@ -37523,7 +37682,7 @@ function createTodosCloudQueryClientFromEnv(env = process.env, options = {}) {
|
|
|
37523
37682
|
return createTodosCloudQueryClient(url, options);
|
|
37524
37683
|
}
|
|
37525
37684
|
var init_cloud_client = __esm(() => {
|
|
37526
|
-
|
|
37685
|
+
init_config2();
|
|
37527
37686
|
});
|
|
37528
37687
|
|
|
37529
37688
|
// src/storage/sqlite-snapshot.ts
|
|
@@ -37905,6 +38064,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37905
38064
|
list: (filter = {}) => listTasks(filter, database()),
|
|
37906
38065
|
count: (filter = {}) => countTasks(filter, database()),
|
|
37907
38066
|
update: (id, input) => updateTask(id, input, database()),
|
|
38067
|
+
unlock: (id, agentId) => {
|
|
38068
|
+
unlockTask(id, agentId, database());
|
|
38069
|
+
return true;
|
|
38070
|
+
},
|
|
37908
38071
|
delete: (id) => deleteTask(id, database()),
|
|
37909
38072
|
start: (id, agentId) => startTask(id, agentId, database()),
|
|
37910
38073
|
complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
|
|
@@ -37956,6 +38119,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37956
38119
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId) => logTaskChange(taskId, action, field, oldValue, newValue, agentId, database()),
|
|
37957
38120
|
addComment: (input) => addComment(input, database()),
|
|
37958
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
|
+
},
|
|
37959
38136
|
getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
|
|
37960
38137
|
getRecentActivity: (limit) => getRecentActivity(limit, database())
|
|
37961
38138
|
},
|
|
@@ -38013,6 +38190,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
|
|
|
38013
38190
|
)`
|
|
38014
38191
|
];
|
|
38015
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
|
+
}
|
|
38016
38199
|
|
|
38017
38200
|
class PostgresTodosSyncStore {
|
|
38018
38201
|
client;
|
|
@@ -38538,7 +38721,7 @@ function __resetRuntimeShadowForTests() {
|
|
|
38538
38721
|
}
|
|
38539
38722
|
var _capturedDb = null, _outbox = null, _cloud = null, _exitRegistered = false;
|
|
38540
38723
|
var init_shadow_runtime = __esm(() => {
|
|
38541
|
-
|
|
38724
|
+
init_config2();
|
|
38542
38725
|
init_cloud_client();
|
|
38543
38726
|
init_shadow_outbox();
|
|
38544
38727
|
});
|
|
@@ -38994,7 +39177,24 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
38994
39177
|
audit: {
|
|
38995
39178
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context),
|
|
38996
39179
|
addComment: (input, context) => addComment2(input, store, context),
|
|
38997
|
-
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
|
+
},
|
|
38998
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)),
|
|
38999
39199
|
getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
|
|
39000
39200
|
},
|
|
@@ -39044,6 +39244,27 @@ class PostgresJsonRecordStore {
|
|
|
39044
39244
|
async list(type) {
|
|
39045
39245
|
return (await this.listRecords(type)).map((record) => record.payload);
|
|
39046
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
|
+
}
|
|
39047
39268
|
async listRecords(type) {
|
|
39048
39269
|
await this.ensureSchema();
|
|
39049
39270
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -39437,7 +39658,7 @@ async function lockTask2(id, agentId, store) {
|
|
|
39437
39658
|
async function unlockTask2(id, agentId, store) {
|
|
39438
39659
|
const task = await requireRecord("tasks", id, store);
|
|
39439
39660
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
39440
|
-
throw new
|
|
39661
|
+
throw new LockError(id, task.locked_by);
|
|
39441
39662
|
}
|
|
39442
39663
|
await patchTask(task, { locked_by: null, locked_at: null }, store);
|
|
39443
39664
|
return true;
|
|
@@ -39802,13 +40023,16 @@ async function addComment2(input, store, context) {
|
|
|
39802
40023
|
task_id: input.task_id,
|
|
39803
40024
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
39804
40025
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
39805
|
-
content: input.content,
|
|
40026
|
+
content: redactEvidenceText(input.content),
|
|
39806
40027
|
type: input.type ?? "comment",
|
|
39807
40028
|
progress_pct: input.progress_pct ?? null,
|
|
39808
40029
|
created_at: new Date().toISOString()
|
|
39809
40030
|
};
|
|
39810
40031
|
return store.upsert("comments", comment, context);
|
|
39811
40032
|
}
|
|
40033
|
+
function redactComment2(comment) {
|
|
40034
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
40035
|
+
}
|
|
39812
40036
|
async function exportSnapshot(store) {
|
|
39813
40037
|
return {
|
|
39814
40038
|
exportedAt: new Date().toISOString(),
|
|
@@ -39954,7 +40178,109 @@ function numberValue2(value) {
|
|
|
39954
40178
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
39955
40179
|
}
|
|
39956
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";
|
|
39957
|
-
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
|
+
});
|
|
39958
40284
|
|
|
39959
40285
|
// src/server/cloud.ts
|
|
39960
40286
|
var exports_cloud = {};
|
|
@@ -39968,7 +40294,9 @@ __export(exports_cloud, {
|
|
|
39968
40294
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
39969
40295
|
getApiKeyStore: () => getApiKeyStore,
|
|
39970
40296
|
ensureCloudSchema: () => ensureCloudSchema,
|
|
40297
|
+
ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
|
|
39971
40298
|
closeCloud: () => closeCloud,
|
|
40299
|
+
backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
|
|
39972
40300
|
TODOS_APP_SLUG: () => TODOS_APP_SLUG
|
|
39973
40301
|
});
|
|
39974
40302
|
function resolveCloudDatabaseUrl(env = process.env) {
|
|
@@ -40046,6 +40374,9 @@ async function ensureCloudSchema() {
|
|
|
40046
40374
|
})();
|
|
40047
40375
|
return schemaEnsured;
|
|
40048
40376
|
}
|
|
40377
|
+
async function ensureCloudCommentCursorIndex() {
|
|
40378
|
+
await getClient().query(postgresTodosCommentCursorIndexSql());
|
|
40379
|
+
}
|
|
40049
40380
|
async function normalizeCloudPayloads() {
|
|
40050
40381
|
const client = getClient();
|
|
40051
40382
|
const res = await client.query(`UPDATE todos_sync_records
|
|
@@ -40054,6 +40385,9 @@ async function normalizeCloudPayloads() {
|
|
|
40054
40385
|
RETURNING object_id AS id`);
|
|
40055
40386
|
return res.rows.length;
|
|
40056
40387
|
}
|
|
40388
|
+
function backfillCloudCommentRedaction(options = {}) {
|
|
40389
|
+
return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
40390
|
+
}
|
|
40057
40391
|
async function pingCloud() {
|
|
40058
40392
|
const client = getClient();
|
|
40059
40393
|
const res = await client.query("select 1 as ok");
|
|
@@ -40074,6 +40408,7 @@ var init_cloud = __esm(() => {
|
|
|
40074
40408
|
init_auth();
|
|
40075
40409
|
init_cloud_client();
|
|
40076
40410
|
init_postgres_adapter();
|
|
40411
|
+
init_comment_redaction_backfill();
|
|
40077
40412
|
});
|
|
40078
40413
|
|
|
40079
40414
|
// src/server/openapi.ts
|
|
@@ -40097,6 +40432,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40097
40432
|
schemas: {
|
|
40098
40433
|
Task: taskSchema,
|
|
40099
40434
|
Project: projectSchema,
|
|
40435
|
+
TaskComment: taskCommentSchema,
|
|
40100
40436
|
CreateTaskInput: {
|
|
40101
40437
|
type: "object",
|
|
40102
40438
|
required: ["title"],
|
|
@@ -40131,6 +40467,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40131
40467
|
description: { type: "string" },
|
|
40132
40468
|
task_prefix: { type: "string" }
|
|
40133
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
|
+
}
|
|
40134
40481
|
}
|
|
40135
40482
|
}
|
|
40136
40483
|
},
|
|
@@ -40229,6 +40576,61 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40229
40576
|
}
|
|
40230
40577
|
}
|
|
40231
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
|
+
},
|
|
40232
40634
|
"/v1/tasks/{id}/start": {
|
|
40233
40635
|
post: {
|
|
40234
40636
|
operationId: "startTask",
|
|
@@ -40347,7 +40749,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40347
40749
|
}
|
|
40348
40750
|
};
|
|
40349
40751
|
}
|
|
40350
|
-
var taskSchema, projectSchema;
|
|
40752
|
+
var taskSchema, projectSchema, taskCommentSchema;
|
|
40351
40753
|
var init_openapi = __esm(() => {
|
|
40352
40754
|
init_package_version();
|
|
40353
40755
|
taskSchema = {
|
|
@@ -40378,6 +40780,20 @@ var init_openapi = __esm(() => {
|
|
|
40378
40780
|
updated_at: { type: "string" }
|
|
40379
40781
|
}
|
|
40380
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
|
+
};
|
|
40381
40797
|
});
|
|
40382
40798
|
|
|
40383
40799
|
// src/server/v1.ts
|
|
@@ -40407,6 +40823,29 @@ function contextFromPrincipal(principal, body) {
|
|
|
40407
40823
|
const agentId = body?.agent_id || principal.agent || undefined;
|
|
40408
40824
|
return agentId ? { agentId } : {};
|
|
40409
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
|
+
}
|
|
40410
40849
|
function normalizeImportSnapshot(raw) {
|
|
40411
40850
|
const body = raw && typeof raw === "object" ? raw : {};
|
|
40412
40851
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -40427,7 +40866,7 @@ function normalizeImportSnapshot(raw) {
|
|
|
40427
40866
|
function countSnapshotRecords(s) {
|
|
40428
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);
|
|
40429
40868
|
}
|
|
40430
|
-
async function handleV1Request(req, url) {
|
|
40869
|
+
async function handleV1Request(req, url, dependencies = {}) {
|
|
40431
40870
|
const path = url.pathname;
|
|
40432
40871
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
40433
40872
|
return null;
|
|
@@ -40436,7 +40875,7 @@ async function handleV1Request(req, url) {
|
|
|
40436
40875
|
const requiredScopes = [isWrite ? "todos:write" : "todos:read"];
|
|
40437
40876
|
let verifier;
|
|
40438
40877
|
try {
|
|
40439
|
-
verifier = getCloudVerifier();
|
|
40878
|
+
verifier = (dependencies.getVerifier ?? getCloudVerifier)();
|
|
40440
40879
|
} catch (e) {
|
|
40441
40880
|
return error(503, e.message);
|
|
40442
40881
|
}
|
|
@@ -40445,8 +40884,8 @@ async function handleV1Request(req, url) {
|
|
|
40445
40884
|
return error(decision.status, decision.message, { reason: decision.reason });
|
|
40446
40885
|
}
|
|
40447
40886
|
const principal = decision.principal;
|
|
40448
|
-
await ensureCloudSchema();
|
|
40449
|
-
const store = getCloudStorageAdapter();
|
|
40887
|
+
await (dependencies.ensureSchema ?? ensureCloudSchema)();
|
|
40888
|
+
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
40450
40889
|
const segments = path.split("/").filter(Boolean);
|
|
40451
40890
|
const resource = segments[1];
|
|
40452
40891
|
const id = segments[2];
|
|
@@ -40532,10 +40971,16 @@ async function handleV1Request(req, url) {
|
|
|
40532
40971
|
if (!id) {
|
|
40533
40972
|
if (method === "GET") {
|
|
40534
40973
|
const filter = {
|
|
40535
|
-
...url.searchParams.get("status") ? {
|
|
40536
|
-
|
|
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
|
+
} : {},
|
|
40537
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 } : {},
|
|
40538
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") } : {},
|
|
40539
40984
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
40540
40985
|
...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
|
|
40541
40986
|
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
@@ -40559,8 +41004,47 @@ async function handleV1Request(req, url) {
|
|
|
40559
41004
|
if (action) {
|
|
40560
41005
|
if (action === "comments") {
|
|
40561
41006
|
if (method === "GET") {
|
|
40562
|
-
|
|
40563
|
-
|
|
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
|
+
});
|
|
40564
41048
|
}
|
|
40565
41049
|
if (method === "POST") {
|
|
40566
41050
|
const body2 = await readJson(req) ?? {};
|
|
@@ -40578,7 +41062,7 @@ async function handleV1Request(req, url) {
|
|
|
40578
41062
|
type: body2.type,
|
|
40579
41063
|
progress_pct: body2.progress_pct
|
|
40580
41064
|
}, contextFromPrincipal(principal, body2));
|
|
40581
|
-
return json2({ comment }, 201);
|
|
41065
|
+
return json2({ comment: redactComment3(comment) }, 201);
|
|
40582
41066
|
}
|
|
40583
41067
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
40584
41068
|
}
|
|
@@ -40593,17 +41077,30 @@ async function handleV1Request(req, url) {
|
|
|
40593
41077
|
if (action === "lock" || action === "unlock") {
|
|
40594
41078
|
if (method !== "POST")
|
|
40595
41079
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
40596
|
-
if (typeof store.tasks.lock !== "function" || typeof store.tasks.unlock !== "function") {
|
|
40597
|
-
return error(501, "task locking is not supported by this storage backend");
|
|
40598
|
-
}
|
|
40599
41080
|
const body2 = await readJson(req) ?? {};
|
|
40600
41081
|
if (!await store.tasks.get(id))
|
|
40601
41082
|
return error(404, "task not found");
|
|
40602
41083
|
if (action === "lock") {
|
|
40603
|
-
|
|
40604
|
-
|
|
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) });
|
|
40605
41088
|
}
|
|
40606
|
-
|
|
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 });
|
|
41096
|
+
}
|
|
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);
|
|
40607
41104
|
return json2({ success: released });
|
|
40608
41105
|
}
|
|
40609
41106
|
if (action === "dependencies") {
|
|
@@ -40886,12 +41383,28 @@ async function handleV1Request(req, url) {
|
|
|
40886
41383
|
const activity = await store.audit.getRecentActivity(limit);
|
|
40887
41384
|
return json2({ activity, count: activity.length });
|
|
40888
41385
|
}
|
|
40889
|
-
if (resource === "task-lists"
|
|
40890
|
-
if (method
|
|
40891
|
-
|
|
40892
|
-
|
|
40893
|
-
|
|
40894
|
-
|
|
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" : ""}`);
|
|
40895
41408
|
}
|
|
40896
41409
|
if (resource === "dependencies" && !id) {
|
|
40897
41410
|
if (method !== "GET")
|
|
@@ -40899,8 +41412,8 @@ async function handleV1Request(req, url) {
|
|
|
40899
41412
|
if (typeof store.dependencies?.listAll !== "function") {
|
|
40900
41413
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
40901
41414
|
}
|
|
40902
|
-
const
|
|
40903
|
-
return json2({ dependencies, count:
|
|
41415
|
+
const dependencies2 = await store.dependencies.listAll();
|
|
41416
|
+
return json2({ dependencies: dependencies2, count: dependencies2.length });
|
|
40904
41417
|
}
|
|
40905
41418
|
if (resource === "commits" && id) {
|
|
40906
41419
|
if (method !== "GET")
|
|
@@ -40957,12 +41470,16 @@ async function handleV1Request(req, url) {
|
|
|
40957
41470
|
}
|
|
40958
41471
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
40959
41472
|
} catch (e) {
|
|
41473
|
+
if (e instanceof LockError)
|
|
41474
|
+
return error(409, e.message, { code: LockError.code });
|
|
40960
41475
|
return error(500, e.message || "internal error");
|
|
40961
41476
|
}
|
|
40962
41477
|
}
|
|
40963
|
-
var JSON_HEADERS;
|
|
41478
|
+
var JSON_HEADERS, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
40964
41479
|
var init_v1 = __esm(() => {
|
|
41480
|
+
init_types();
|
|
40965
41481
|
init_cloud();
|
|
41482
|
+
init_redaction();
|
|
40966
41483
|
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
40967
41484
|
});
|
|
40968
41485
|
|
|
@@ -47197,7 +47714,7 @@ var init_workflow_states = __esm(() => {
|
|
|
47197
47714
|
init_tasks();
|
|
47198
47715
|
init_types();
|
|
47199
47716
|
init_database();
|
|
47200
|
-
|
|
47717
|
+
init_config();
|
|
47201
47718
|
init_local_fields();
|
|
47202
47719
|
DEFAULT_WORKFLOW_STATES = [
|
|
47203
47720
|
{ name: "pending", canonical_status: "pending", aliases: ["todo", "backlog"], transitions: null, terminal: false },
|
|
@@ -48362,7 +48879,7 @@ var init_roadmaps = __esm(() => {
|
|
|
48362
48879
|
init_tasks();
|
|
48363
48880
|
init_plans();
|
|
48364
48881
|
init_task_runs();
|
|
48365
|
-
|
|
48882
|
+
init_config();
|
|
48366
48883
|
});
|
|
48367
48884
|
|
|
48368
48885
|
// src/lib/capacity-forecasts.ts
|
|
@@ -48587,7 +49104,7 @@ var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
|
|
|
48587
49104
|
var init_capacity_forecasts = __esm(() => {
|
|
48588
49105
|
init_tasks();
|
|
48589
49106
|
init_task_relations();
|
|
48590
|
-
|
|
49107
|
+
init_config();
|
|
48591
49108
|
});
|
|
48592
49109
|
|
|
48593
49110
|
// src/lib/audit-ledger.ts
|
|
@@ -48879,7 +49396,7 @@ var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1, LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "
|
|
|
48879
49396
|
var init_audit_ledger = __esm(() => {
|
|
48880
49397
|
init_database();
|
|
48881
49398
|
init_task_runs();
|
|
48882
|
-
|
|
49399
|
+
init_config();
|
|
48883
49400
|
init_redaction();
|
|
48884
49401
|
LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
|
|
48885
49402
|
});
|
|
@@ -54760,7 +55277,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
54760
55277
|
init_database();
|
|
54761
55278
|
init_redaction();
|
|
54762
55279
|
init_runner_sandbox();
|
|
54763
|
-
|
|
55280
|
+
init_config();
|
|
54764
55281
|
});
|
|
54765
55282
|
|
|
54766
55283
|
// src/lib/verification-providers.ts
|
|
@@ -55105,7 +55622,7 @@ var init_verification_providers = __esm(() => {
|
|
|
55105
55622
|
init_task_commits();
|
|
55106
55623
|
init_database();
|
|
55107
55624
|
init_tasks();
|
|
55108
|
-
|
|
55625
|
+
init_config();
|
|
55109
55626
|
init_redaction();
|
|
55110
55627
|
DEFAULT_RETRY = {
|
|
55111
55628
|
attempts: 1,
|
|
@@ -61931,7 +62448,7 @@ var init_review_queues = __esm(() => {
|
|
|
61931
62448
|
init_audit();
|
|
61932
62449
|
init_database();
|
|
61933
62450
|
init_tasks();
|
|
61934
|
-
|
|
62451
|
+
init_config();
|
|
61935
62452
|
init_event_emission_safety();
|
|
61936
62453
|
init_event_hooks();
|
|
61937
62454
|
init_task_contracts();
|
|
@@ -63818,7 +64335,7 @@ ID: ${updated.id}${taskNote}`
|
|
|
63818
64335
|
var init_agents2 = __esm(() => {
|
|
63819
64336
|
init_zod();
|
|
63820
64337
|
init_agents();
|
|
63821
|
-
|
|
64338
|
+
init_config();
|
|
63822
64339
|
init_database();
|
|
63823
64340
|
init_cloud_router();
|
|
63824
64341
|
});
|
|
@@ -64252,7 +64769,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
64252
64769
|
}
|
|
64253
64770
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
64254
64771
|
const path = outputPath ? resolve20(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
64255
|
-
|
|
64772
|
+
ensureDir(dirname9(path));
|
|
64256
64773
|
writeJsonFile(path, snapshot);
|
|
64257
64774
|
return path;
|
|
64258
64775
|
}
|
|
@@ -67895,7 +68412,7 @@ Commit Links (${commitRows.length}):`));
|
|
|
67895
68412
|
var init_config_serve_commands = __esm(() => {
|
|
67896
68413
|
init_database();
|
|
67897
68414
|
init_tasks();
|
|
67898
|
-
|
|
68415
|
+
init_config();
|
|
67899
68416
|
init_sync_utils();
|
|
67900
68417
|
init_helpers();
|
|
67901
68418
|
});
|
|
@@ -70101,7 +70618,7 @@ Findings`));
|
|
|
70101
70618
|
checks.push({ name: "Migrations", ok: false, message: "Could not read migration version" });
|
|
70102
70619
|
}
|
|
70103
70620
|
try {
|
|
70104
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (
|
|
70621
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
70105
70622
|
loadConfig2();
|
|
70106
70623
|
checks.push({ name: "Config", ok: true, message: "Loaded successfully" });
|
|
70107
70624
|
} catch (e) {
|
|
@@ -79532,7 +80049,7 @@ var init_factory = __esm(() => {
|
|
|
79532
80049
|
init_postgres_adapter();
|
|
79533
80050
|
init_shadow();
|
|
79534
80051
|
init_cloud_client();
|
|
79535
|
-
|
|
80052
|
+
init_config2();
|
|
79536
80053
|
});
|
|
79537
80054
|
|
|
79538
80055
|
// src/storage/s3-artifacts.ts
|
|
@@ -79974,6 +80491,7 @@ __export(exports_storage, {
|
|
|
79974
80491
|
signAwsV4Request: () => signAwsV4Request,
|
|
79975
80492
|
registerShadowExitFlush: () => registerShadowExitFlush,
|
|
79976
80493
|
postgresTodosSyncSchemaSql: () => postgresTodosSyncSchemaSql,
|
|
80494
|
+
postgresTodosCommentCursorIndexSql: () => postgresTodosCommentCursorIndexSql,
|
|
79977
80495
|
planRunArtifactsS3Sync: () => planRunArtifactsS3Sync,
|
|
79978
80496
|
parseStorageMode: () => parseStorageMode,
|
|
79979
80497
|
maybeInstallShadowCapture: () => maybeInstallShadowCapture2,
|
|
@@ -79981,6 +80499,7 @@ __export(exports_storage, {
|
|
|
79981
80499
|
loadStorageConfig: () => loadStorageConfig,
|
|
79982
80500
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
79983
80501
|
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
80502
|
+
isCommentRedactionBackfillComplete: () => isCommentRedactionBackfillComplete,
|
|
79984
80503
|
installShadowOutboxSchema: () => installShadowOutboxSchema,
|
|
79985
80504
|
importSqliteTodosStorageSnapshot: () => importSqliteTodosStorageSnapshot,
|
|
79986
80505
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
@@ -80008,6 +80527,7 @@ __export(exports_storage, {
|
|
|
80008
80527
|
closeRuntimeShadowCloud: () => closeRuntimeShadowCloud,
|
|
80009
80528
|
buildS3ObjectUrl: () => buildS3ObjectUrl,
|
|
80010
80529
|
buildS3ObjectKey: () => buildS3ObjectKey,
|
|
80530
|
+
backfillPostgresCommentRedaction: () => backfillPostgresCommentRedaction,
|
|
80011
80531
|
assertTodosShadowConfig: () => assertTodosShadowConfig,
|
|
80012
80532
|
assertTodosRemoteStorageConfig: () => assertTodosRemoteStorageConfig,
|
|
80013
80533
|
TodosShadowOutbox: () => TodosShadowOutbox,
|
|
@@ -80020,12 +80540,13 @@ __export(exports_storage, {
|
|
|
80020
80540
|
PostgresTodosSyncStore: () => PostgresTodosSyncStore,
|
|
80021
80541
|
DEFAULT_TODOS_POSTGRES_SYNC_TABLE: () => DEFAULT_TODOS_POSTGRES_SYNC_TABLE,
|
|
80022
80542
|
DEFAULT_TODOS_POSTGRES_CURSOR_TABLE: () => DEFAULT_TODOS_POSTGRES_CURSOR_TABLE,
|
|
80543
|
+
COMMENT_REDACTION_BACKFILL_CONFIRMATION: () => COMMENT_REDACTION_BACKFILL_CONFIRMATION,
|
|
80023
80544
|
CANONICAL_TODOS_RDS_RUNTIME_PATH: () => CANONICAL_TODOS_RDS_RUNTIME_PATH,
|
|
80024
80545
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
80025
80546
|
CANONICAL_TODOS_RDS_CLUSTER: () => CANONICAL_TODOS_RDS_CLUSTER
|
|
80026
80547
|
});
|
|
80027
80548
|
var init_storage2 = __esm(() => {
|
|
80028
|
-
|
|
80549
|
+
init_config2();
|
|
80029
80550
|
init_factory();
|
|
80030
80551
|
init_shadow();
|
|
80031
80552
|
init_shadow_outbox();
|
|
@@ -80034,6 +80555,7 @@ var init_storage2 = __esm(() => {
|
|
|
80034
80555
|
init_hybrid();
|
|
80035
80556
|
init_local_sqlite();
|
|
80036
80557
|
init_sqlite_snapshot();
|
|
80558
|
+
init_comment_redaction_backfill();
|
|
80037
80559
|
init_postgres_adapter();
|
|
80038
80560
|
init_s3_artifacts();
|
|
80039
80561
|
init_s3_artifact_sync();
|