@hasna/todos 0.11.86 → 0.11.88
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 +35 -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 +989 -416
- 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,7 +8478,312 @@ 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
|
|
8786
|
+
import { resolve as resolvePath } from "path";
|
|
8482
8787
|
function getTodosCloudClient(env = process.env) {
|
|
8483
8788
|
if (_cache !== undefined)
|
|
8484
8789
|
return _cache.client;
|
|
@@ -8503,14 +8808,18 @@ function unwrapTask(raw) {
|
|
|
8503
8808
|
}
|
|
8504
8809
|
function toListQuery(filter = {}) {
|
|
8505
8810
|
const query = {};
|
|
8506
|
-
if (
|
|
8507
|
-
query["status"] = filter.status;
|
|
8508
|
-
if (
|
|
8509
|
-
query["priority"] = filter.priority;
|
|
8811
|
+
if (filter.status)
|
|
8812
|
+
query["status"] = Array.isArray(filter.status) ? filter.status.join(",") : filter.status;
|
|
8813
|
+
if (filter.priority)
|
|
8814
|
+
query["priority"] = Array.isArray(filter.priority) ? filter.priority.join(",") : filter.priority;
|
|
8510
8815
|
if (filter.project_id)
|
|
8511
8816
|
query["project_id"] = filter.project_id;
|
|
8817
|
+
if (filter.parent_id !== undefined)
|
|
8818
|
+
query["parent_id"] = filter.parent_id ?? "";
|
|
8512
8819
|
if (filter.plan_id)
|
|
8513
8820
|
query["plan_id"] = filter.plan_id;
|
|
8821
|
+
if (filter.task_list_id)
|
|
8822
|
+
query["task_list_id"] = filter.task_list_id;
|
|
8514
8823
|
if (filter.assigned_to)
|
|
8515
8824
|
query["assigned_to"] = filter.assigned_to;
|
|
8516
8825
|
if (filter.agent_id)
|
|
@@ -8558,6 +8867,37 @@ async function cloudListProjects(client) {
|
|
|
8558
8867
|
const envelope = res.raw;
|
|
8559
8868
|
return Array.isArray(envelope?.projects) ? envelope.projects : res.items;
|
|
8560
8869
|
}
|
|
8870
|
+
function cloudProjectSlug(value) {
|
|
8871
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
8872
|
+
}
|
|
8873
|
+
function cloudProjectPathBasename(value) {
|
|
8874
|
+
return value.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? value;
|
|
8875
|
+
}
|
|
8876
|
+
function uniqueProjectMatches(projects, predicate) {
|
|
8877
|
+
return [...new Map(projects.filter(predicate).map((project) => [project.id, project])).values()];
|
|
8878
|
+
}
|
|
8879
|
+
async function cloudResolveProjectRef(client, ref) {
|
|
8880
|
+
const input = ref.trim();
|
|
8881
|
+
const projects = await cloudListProjects(client);
|
|
8882
|
+
const normalizedRef = input.toLowerCase();
|
|
8883
|
+
const pathLike = input.startsWith(".") || input.includes("/") || input.includes("\\");
|
|
8884
|
+
const normalizedPath = pathLike ? resolvePath(input) : undefined;
|
|
8885
|
+
const slug = cloudProjectSlug(pathLike ? cloudProjectPathBasename(input) : input);
|
|
8886
|
+
const matchGroups = [
|
|
8887
|
+
uniqueProjectMatches(projects, (project) => project.id.toLowerCase() === normalizedRef),
|
|
8888
|
+
uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath(project.path) === normalizedPath),
|
|
8889
|
+
uniqueProjectMatches(projects, (project) => project.name.toLowerCase() === normalizedRef),
|
|
8890
|
+
uniqueProjectMatches(projects, (project) => project.task_list_id === input || cloudProjectSlug(project.name) === slug || cloudProjectSlug(cloudProjectPathBasename(project.path)) === slug),
|
|
8891
|
+
uniqueProjectMatches(projects, (project) => project.id.toLowerCase().startsWith(normalizedRef))
|
|
8892
|
+
];
|
|
8893
|
+
for (const matches of matchGroups) {
|
|
8894
|
+
if (matches.length === 1)
|
|
8895
|
+
return matches[0].id;
|
|
8896
|
+
if (matches.length > 1)
|
|
8897
|
+
throw new Error(`Project reference is ambiguous: "${input}"`);
|
|
8898
|
+
}
|
|
8899
|
+
throw new Error(`Project not found: "${input}"`);
|
|
8900
|
+
}
|
|
8561
8901
|
async function cloudListPlans(client, projectId) {
|
|
8562
8902
|
const query = projectId ? { project_id: projectId } : {};
|
|
8563
8903
|
const res = await client.list("plans", { query });
|
|
@@ -8566,10 +8906,82 @@ async function cloudListPlans(client, projectId) {
|
|
|
8566
8906
|
}
|
|
8567
8907
|
async function cloudAddComment(client, taskId, input) {
|
|
8568
8908
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/comments`, input);
|
|
8569
|
-
|
|
8570
|
-
|
|
8909
|
+
const comment = raw && typeof raw === "object" && "comment" in raw ? raw.comment : raw;
|
|
8910
|
+
if (!isTaskComment(comment))
|
|
8911
|
+
throw new Error("Invalid cloud comment response");
|
|
8912
|
+
return redactComment(comment);
|
|
8913
|
+
}
|
|
8914
|
+
async function cloudListComments(client, taskId, options = {}) {
|
|
8915
|
+
const limit = options.limit ?? 100;
|
|
8916
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
|
|
8917
|
+
throw new Error("Cloud comment limit must be an integer between 1 and 500");
|
|
8571
8918
|
}
|
|
8572
|
-
|
|
8919
|
+
if (options.cursor !== undefined && (typeof options.cursor !== "string" || !options.cursor || options.cursor.length > 1024)) {
|
|
8920
|
+
throw new Error("Cloud comment cursor must be a non-empty string");
|
|
8921
|
+
}
|
|
8922
|
+
let raw;
|
|
8923
|
+
try {
|
|
8924
|
+
raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/comments`, {
|
|
8925
|
+
query: { limit, ...options.cursor ? { cursor: options.cursor } : {} }
|
|
8926
|
+
});
|
|
8927
|
+
} catch (error) {
|
|
8928
|
+
const status = error && typeof error === "object" ? error.status : undefined;
|
|
8929
|
+
if (status === 404 || status === 405) {
|
|
8930
|
+
throw new Error("Cloud task comments require a compatible @hasna/todos server; deploy the server endpoint before this CLI.", { cause: error });
|
|
8931
|
+
}
|
|
8932
|
+
throw error;
|
|
8933
|
+
}
|
|
8934
|
+
const envelope = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
8935
|
+
const candidate = Array.isArray(raw) ? raw : envelope?.comments;
|
|
8936
|
+
if (!Array.isArray(candidate) || !candidate.every(isTaskComment)) {
|
|
8937
|
+
throw new Error("Invalid cloud comments response");
|
|
8938
|
+
}
|
|
8939
|
+
if (envelope?.count !== undefined && (!Number.isSafeInteger(envelope.count) || envelope.count < 0 || envelope.count !== candidate.length)) {
|
|
8940
|
+
throw new Error("Invalid cloud comments response count");
|
|
8941
|
+
}
|
|
8942
|
+
const hasHasMore = envelope ? Object.prototype.hasOwnProperty.call(envelope, "has_more") : false;
|
|
8943
|
+
const hasNextCursor = envelope ? Object.prototype.hasOwnProperty.call(envelope, "next_cursor") : false;
|
|
8944
|
+
if (hasHasMore !== hasNextCursor)
|
|
8945
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8946
|
+
const paginationSupported = hasHasMore && hasNextCursor;
|
|
8947
|
+
if (!paginationSupported) {
|
|
8948
|
+
const comments = candidate.slice(-limit).map(redactComment);
|
|
8949
|
+
return {
|
|
8950
|
+
comments,
|
|
8951
|
+
count: comments.length,
|
|
8952
|
+
has_more: candidate.length > limit,
|
|
8953
|
+
next_cursor: null,
|
|
8954
|
+
limit,
|
|
8955
|
+
pagination_supported: false
|
|
8956
|
+
};
|
|
8957
|
+
}
|
|
8958
|
+
if (candidate.length > limit)
|
|
8959
|
+
throw new Error("Invalid cloud comments response: page exceeds requested limit");
|
|
8960
|
+
const hasMore = envelope.has_more;
|
|
8961
|
+
const nextCursor = envelope.next_cursor;
|
|
8962
|
+
if (typeof hasMore !== "boolean" || nextCursor !== null && (typeof nextCursor !== "string" || !nextCursor)) {
|
|
8963
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8964
|
+
}
|
|
8965
|
+
if (hasMore && nextCursor === null || !hasMore && nextCursor !== null) {
|
|
8966
|
+
throw new Error("Invalid cloud comments pagination response");
|
|
8967
|
+
}
|
|
8968
|
+
return {
|
|
8969
|
+
comments: candidate.map(redactComment),
|
|
8970
|
+
count: candidate.length,
|
|
8971
|
+
has_more: hasMore,
|
|
8972
|
+
next_cursor: nextCursor,
|
|
8973
|
+
limit,
|
|
8974
|
+
pagination_supported: true
|
|
8975
|
+
};
|
|
8976
|
+
}
|
|
8977
|
+
function isTaskComment(value) {
|
|
8978
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8979
|
+
return false;
|
|
8980
|
+
const comment = value;
|
|
8981
|
+
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";
|
|
8982
|
+
}
|
|
8983
|
+
function redactComment(comment) {
|
|
8984
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
8573
8985
|
}
|
|
8574
8986
|
async function cloudTaskHistory(client, taskId) {
|
|
8575
8987
|
const raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/history`);
|
|
@@ -8657,8 +9069,8 @@ async function cloudLockTask(client, id, agentId) {
|
|
|
8657
9069
|
}
|
|
8658
9070
|
return raw ?? { success: true };
|
|
8659
9071
|
}
|
|
8660
|
-
async function cloudUnlockTask(client, id, agentId) {
|
|
8661
|
-
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, agentId ? { agent_id: agentId } : {});
|
|
9072
|
+
async function cloudUnlockTask(client, id, agentId, force = false) {
|
|
9073
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, { ...agentId ? { agent_id: agentId } : {}, ...force ? { force: true } : {} });
|
|
8662
9074
|
if (raw && typeof raw === "object" && "success" in raw) {
|
|
8663
9075
|
return Boolean(raw.success);
|
|
8664
9076
|
}
|
|
@@ -8787,6 +9199,43 @@ async function cloudListTaskLists(client, projectId) {
|
|
|
8787
9199
|
return envelope.taskLists;
|
|
8788
9200
|
return Array.isArray(raw) ? raw : [];
|
|
8789
9201
|
}
|
|
9202
|
+
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
9203
|
+
const input = ref.trim();
|
|
9204
|
+
const normalizedIdRef = input.toLowerCase();
|
|
9205
|
+
if (UUID_RE.test(input) && !projectId)
|
|
9206
|
+
return normalizedIdRef;
|
|
9207
|
+
const lists = await cloudListTaskLists(client, projectId);
|
|
9208
|
+
const exactIds = lists.filter((list) => list.id.toLowerCase() === normalizedIdRef);
|
|
9209
|
+
if (exactIds.length === 1)
|
|
9210
|
+
return exactIds[0].id;
|
|
9211
|
+
if (exactIds.length > 1) {
|
|
9212
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
9213
|
+
}
|
|
9214
|
+
const slugs = lists.filter((list) => list.slug === input);
|
|
9215
|
+
if (slugs.length === 1)
|
|
9216
|
+
return slugs[0].id;
|
|
9217
|
+
if (slugs.length > 1) {
|
|
9218
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
9219
|
+
}
|
|
9220
|
+
const prefixes = lists.filter((list) => list.id.toLowerCase().startsWith(normalizedIdRef));
|
|
9221
|
+
if (prefixes.length === 1)
|
|
9222
|
+
return prefixes[0].id;
|
|
9223
|
+
if (prefixes.length > 1) {
|
|
9224
|
+
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
9225
|
+
}
|
|
9226
|
+
throw new Error(`Task list not found: "${input}"`);
|
|
9227
|
+
}
|
|
9228
|
+
async function cloudCreateTaskList(client, input) {
|
|
9229
|
+
const raw = await client.transport.post("/task-lists", input);
|
|
9230
|
+
if (raw && typeof raw === "object" && "task_list" in raw) {
|
|
9231
|
+
return raw.task_list;
|
|
9232
|
+
}
|
|
9233
|
+
return raw;
|
|
9234
|
+
}
|
|
9235
|
+
async function cloudDeleteTaskList(client, id) {
|
|
9236
|
+
await client.delete("task-lists", id);
|
|
9237
|
+
return true;
|
|
9238
|
+
}
|
|
8790
9239
|
async function cloudNextTask(client, agent, filters) {
|
|
8791
9240
|
const query = {};
|
|
8792
9241
|
if (agent)
|
|
@@ -8811,11 +9260,11 @@ async function cloudAllDependencies(client) {
|
|
|
8811
9260
|
return Array.isArray(raw) ? raw : [];
|
|
8812
9261
|
}
|
|
8813
9262
|
async function cloudGetTasksByIds(client, ids) {
|
|
8814
|
-
const
|
|
9263
|
+
const unique2 = Array.from(new Set(ids));
|
|
8815
9264
|
const map = new Map;
|
|
8816
9265
|
const CONCURRENCY = 8;
|
|
8817
|
-
for (let i = 0;i <
|
|
8818
|
-
const batch =
|
|
9266
|
+
for (let i = 0;i < unique2.length; i += CONCURRENCY) {
|
|
9267
|
+
const batch = unique2.slice(i, i + CONCURRENCY);
|
|
8819
9268
|
const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
|
|
8820
9269
|
for (const task of tasks)
|
|
8821
9270
|
if (task && task.id)
|
|
@@ -8910,9 +9359,11 @@ async function cloudTimeline(client, options = {}) {
|
|
|
8910
9359
|
const limit = options.limit ?? 50;
|
|
8911
9360
|
return { entries: entries.slice(offset, offset + limit), total, limit, offset };
|
|
8912
9361
|
}
|
|
8913
|
-
var _cache, PRIORITY_RANK;
|
|
9362
|
+
var UUID_RE, _cache, PRIORITY_RANK;
|
|
8914
9363
|
var init_cloud_router = __esm(() => {
|
|
8915
9364
|
init_storage();
|
|
9365
|
+
init_redaction();
|
|
9366
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8916
9367
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
8917
9368
|
});
|
|
8918
9369
|
|
|
@@ -11215,7 +11666,7 @@ var init_schema = __esm(() => {
|
|
|
11215
11666
|
});
|
|
11216
11667
|
|
|
11217
11668
|
// src/db/machines.ts
|
|
11218
|
-
import { existsSync as
|
|
11669
|
+
import { existsSync as existsSync4 } from "fs";
|
|
11219
11670
|
import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
|
|
11220
11671
|
import { resolve } from "path";
|
|
11221
11672
|
import { spawnSync } from "child_process";
|
|
@@ -11422,7 +11873,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11422
11873
|
message: `${project.name} has ${distinctPaths.length} different machine-local paths`
|
|
11423
11874
|
});
|
|
11424
11875
|
}
|
|
11425
|
-
if (localRow && !
|
|
11876
|
+
if (localRow && !existsSync4(localRow.path)) {
|
|
11426
11877
|
pathIssues.push({
|
|
11427
11878
|
type: "path_missing",
|
|
11428
11879
|
project_id: project.id,
|
|
@@ -11433,7 +11884,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
|
|
|
11433
11884
|
message: `Local path does not exist on this machine: ${localRow.path}`
|
|
11434
11885
|
});
|
|
11435
11886
|
}
|
|
11436
|
-
if (!localRow && project.path && machineById.has(localMachine.id) && !
|
|
11887
|
+
if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync4(project.path)) {
|
|
11437
11888
|
pathIssues.push({
|
|
11438
11889
|
type: "path_missing",
|
|
11439
11890
|
project_id: project.id,
|
|
@@ -11541,8 +11992,8 @@ var init_machines = __esm(() => {
|
|
|
11541
11992
|
});
|
|
11542
11993
|
|
|
11543
11994
|
// src/storage/config.ts
|
|
11544
|
-
var
|
|
11545
|
-
__export(
|
|
11995
|
+
var exports_config2 = {};
|
|
11996
|
+
__export(exports_config2, {
|
|
11546
11997
|
parseStorageMode: () => parseStorageMode,
|
|
11547
11998
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
11548
11999
|
loadStorageConfig: () => loadStorageConfig,
|
|
@@ -11705,7 +12156,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
11705
12156
|
return parsed;
|
|
11706
12157
|
}
|
|
11707
12158
|
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
|
|
12159
|
+
var init_config2 = __esm(() => {
|
|
11709
12160
|
TODOS_STORAGE_TABLES = [
|
|
11710
12161
|
"todos_sync_records",
|
|
11711
12162
|
"todos_sync_cursors"
|
|
@@ -11844,8 +12295,8 @@ __export(exports_database, {
|
|
|
11844
12295
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
11845
12296
|
});
|
|
11846
12297
|
import { Database } from "bun:sqlite";
|
|
11847
|
-
import { existsSync as
|
|
11848
|
-
import { dirname as
|
|
12298
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
12299
|
+
import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
11849
12300
|
function isInMemoryDb(path) {
|
|
11850
12301
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
11851
12302
|
}
|
|
@@ -11854,12 +12305,12 @@ function findNearestProjectDb(startDir) {
|
|
|
11854
12305
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
11855
12306
|
let dir = resolve2(startDir);
|
|
11856
12307
|
while (true) {
|
|
11857
|
-
const candidate =
|
|
11858
|
-
if (
|
|
12308
|
+
const candidate = join4(dir, ".hasna", "todos", "todos.db");
|
|
12309
|
+
if (existsSync5(candidate))
|
|
11859
12310
|
return candidate;
|
|
11860
12311
|
if (dir === stopAt)
|
|
11861
12312
|
break;
|
|
11862
|
-
const parent =
|
|
12313
|
+
const parent = dirname3(dir);
|
|
11863
12314
|
if (parent === dir)
|
|
11864
12315
|
break;
|
|
11865
12316
|
dir = parent;
|
|
@@ -11869,9 +12320,9 @@ function findNearestProjectDb(startDir) {
|
|
|
11869
12320
|
function findGitRoot(startDir) {
|
|
11870
12321
|
let dir = resolve2(startDir);
|
|
11871
12322
|
while (true) {
|
|
11872
|
-
if (
|
|
12323
|
+
if (existsSync5(join4(dir, ".git")))
|
|
11873
12324
|
return dir;
|
|
11874
|
-
const parent =
|
|
12325
|
+
const parent = dirname3(dir);
|
|
11875
12326
|
if (parent === dir)
|
|
11876
12327
|
break;
|
|
11877
12328
|
dir = parent;
|
|
@@ -11892,25 +12343,25 @@ function getDbPath() {
|
|
|
11892
12343
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
11893
12344
|
const gitRoot = findGitRoot(cwd);
|
|
11894
12345
|
if (gitRoot) {
|
|
11895
|
-
return
|
|
12346
|
+
return join4(gitRoot, ".hasna", "todos", "todos.db");
|
|
11896
12347
|
}
|
|
11897
12348
|
}
|
|
11898
12349
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
11899
|
-
return
|
|
12350
|
+
return join4(home, ".hasna", "todos", "todos.db");
|
|
11900
12351
|
}
|
|
11901
12352
|
function getDatabasePath() {
|
|
11902
12353
|
return getDbPath();
|
|
11903
12354
|
}
|
|
11904
|
-
function
|
|
12355
|
+
function ensureDir2(filePath) {
|
|
11905
12356
|
if (isInMemoryDb(filePath))
|
|
11906
12357
|
return;
|
|
11907
|
-
const dir =
|
|
11908
|
-
if (!
|
|
11909
|
-
|
|
12358
|
+
const dir = dirname3(resolve2(filePath));
|
|
12359
|
+
if (!existsSync5(dir)) {
|
|
12360
|
+
mkdirSync2(dir, { recursive: true });
|
|
11910
12361
|
}
|
|
11911
12362
|
}
|
|
11912
12363
|
function openDatabase(path) {
|
|
11913
|
-
|
|
12364
|
+
ensureDir2(path);
|
|
11914
12365
|
const db = new Database(path);
|
|
11915
12366
|
db.run("PRAGMA journal_mode = WAL");
|
|
11916
12367
|
db.run("PRAGMA busy_timeout = 5000");
|
|
@@ -11923,7 +12374,7 @@ function openDatabase(path) {
|
|
|
11923
12374
|
}
|
|
11924
12375
|
function maybeInstallShadowCapture(db) {
|
|
11925
12376
|
try {
|
|
11926
|
-
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (
|
|
12377
|
+
const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (init_config2(), __toCommonJS(exports_config2));
|
|
11927
12378
|
if (!isTodosShadowEnabled2())
|
|
11928
12379
|
return;
|
|
11929
12380
|
const { installShadowOutboxSchema: installShadowOutboxSchema2 } = (init_shadow_outbox_schema(), __toCommonJS(exports_shadow_outbox_schema));
|
|
@@ -12704,199 +13155,6 @@ var init_helpers = __esm(() => {
|
|
|
12704
13155
|
};
|
|
12705
13156
|
});
|
|
12706
13157
|
|
|
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
13158
|
// src/lib/completion-guard.ts
|
|
12901
13159
|
function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
12902
13160
|
let config;
|
|
@@ -12942,7 +13200,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
12942
13200
|
}
|
|
12943
13201
|
var init_completion_guard = __esm(() => {
|
|
12944
13202
|
init_types();
|
|
12945
|
-
|
|
13203
|
+
init_config();
|
|
12946
13204
|
init_projects();
|
|
12947
13205
|
});
|
|
12948
13206
|
|
|
@@ -12997,117 +13255,6 @@ var init_event_emission_safety = __esm(() => {
|
|
|
12997
13255
|
init_sync_utils();
|
|
12998
13256
|
});
|
|
12999
13257
|
|
|
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
13258
|
// src/lib/workspace-trust.ts
|
|
13112
13259
|
var exports_workspace_trust = {};
|
|
13113
13260
|
__export(exports_workspace_trust, {
|
|
@@ -13253,7 +13400,7 @@ function checkWorkspacePermission(input = {}) {
|
|
|
13253
13400
|
}
|
|
13254
13401
|
var DEFAULT_DENYLIST, DEFAULT_ENV_REDACTIONS, PRESET_DEFAULTS;
|
|
13255
13402
|
var init_workspace_trust = __esm(() => {
|
|
13256
|
-
|
|
13403
|
+
init_config();
|
|
13257
13404
|
DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13258
13405
|
DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
13259
13406
|
PRESET_DEFAULTS = {
|
|
@@ -13496,7 +13643,7 @@ function explainRunnerSandbox(input = {}) {
|
|
|
13496
13643
|
}
|
|
13497
13644
|
var DEFAULT_COMMAND_DENYLIST, DEFAULT_ENV_REDACTIONS2;
|
|
13498
13645
|
var init_runner_sandbox = __esm(() => {
|
|
13499
|
-
|
|
13646
|
+
init_config();
|
|
13500
13647
|
init_workspace_trust();
|
|
13501
13648
|
DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
13502
13649
|
DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
@@ -13743,7 +13890,7 @@ var LOCAL_EVENT_TYPES, VALID_TARGETS;
|
|
|
13743
13890
|
var init_event_hooks = __esm(() => {
|
|
13744
13891
|
init_redaction();
|
|
13745
13892
|
init_runner_sandbox();
|
|
13746
|
-
|
|
13893
|
+
init_config();
|
|
13747
13894
|
init_event_emission_safety();
|
|
13748
13895
|
LOCAL_EVENT_TYPES = [
|
|
13749
13896
|
"task.created",
|
|
@@ -18628,7 +18775,7 @@ function getComment(id, db) {
|
|
|
18628
18775
|
}
|
|
18629
18776
|
function listComments(taskId, db) {
|
|
18630
18777
|
const d = db || getDatabase();
|
|
18631
|
-
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
18778
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
|
|
18632
18779
|
}
|
|
18633
18780
|
function updateComment(id, input, db) {
|
|
18634
18781
|
const d = db || getDatabase();
|
|
@@ -20256,10 +20403,27 @@ var init_task_routing = __esm(() => {
|
|
|
20256
20403
|
// src/cli/commands/task-commands.ts
|
|
20257
20404
|
var exports_task_commands = {};
|
|
20258
20405
|
__export(exports_task_commands, {
|
|
20259
|
-
registerTaskCommands: () => registerTaskCommands
|
|
20406
|
+
registerTaskCommands: () => registerTaskCommands,
|
|
20407
|
+
escapeTerminalControls: () => escapeTerminalControls
|
|
20260
20408
|
});
|
|
20261
20409
|
import chalk2 from "chalk";
|
|
20262
20410
|
import { basename as basename3, resolve as resolve9 } from "path";
|
|
20411
|
+
function escapeTerminalControls(value) {
|
|
20412
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
|
|
20413
|
+
const code = character.charCodeAt(0);
|
|
20414
|
+
if (code === 10)
|
|
20415
|
+
return "\\n";
|
|
20416
|
+
if (code === 13)
|
|
20417
|
+
return "\\r";
|
|
20418
|
+
if (code === 9)
|
|
20419
|
+
return "\\t";
|
|
20420
|
+
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
20421
|
+
});
|
|
20422
|
+
}
|
|
20423
|
+
function formatHumanComment(comment) {
|
|
20424
|
+
const agent = comment.agent_id ? chalk2.cyan(`[${escapeTerminalControls(comment.agent_id)}] `) : "";
|
|
20425
|
+
return ` ${agent}${chalk2.dim(escapeTerminalControls(comment.created_at))}: ${escapeTerminalControls(comment.content)}`;
|
|
20426
|
+
}
|
|
20263
20427
|
function resolveProjectIdOrSlug(input) {
|
|
20264
20428
|
const db = getDatabase();
|
|
20265
20429
|
if (isPathLike(input)) {
|
|
@@ -20416,18 +20580,24 @@ function registerTaskCommands(program2) {
|
|
|
20416
20580
|
if (cloud) {
|
|
20417
20581
|
let task3;
|
|
20418
20582
|
try {
|
|
20583
|
+
const cloudProjectId = opts.project || globalOpts.project;
|
|
20584
|
+
const cloudTaskListId = opts.list ? await cloudResolveTaskListRef(cloud, opts.list, cloudProjectId) : undefined;
|
|
20585
|
+
if (opts.list && !cloudTaskListId) {
|
|
20586
|
+
throw new Error(`Could not resolve task list ID or slug: ${opts.list}`);
|
|
20587
|
+
}
|
|
20419
20588
|
task3 = await cloudCreateTask(cloud, {
|
|
20420
20589
|
title,
|
|
20421
20590
|
description: opts.description,
|
|
20422
20591
|
priority: parsePriority(opts.priority),
|
|
20592
|
+
parent_id: opts.parent,
|
|
20423
20593
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
20424
20594
|
plan_id: opts.plan,
|
|
20425
20595
|
assigned_to: opts.assign,
|
|
20426
20596
|
status: parseStatus(opts.status),
|
|
20427
|
-
task_list_id:
|
|
20597
|
+
task_list_id: cloudTaskListId,
|
|
20428
20598
|
agent_id: globalOpts.agent,
|
|
20429
20599
|
session_id: globalOpts.session,
|
|
20430
|
-
project_id:
|
|
20600
|
+
project_id: cloudProjectId,
|
|
20431
20601
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
20432
20602
|
sla_minutes: opts.slaMinutes !== undefined || opts.sla !== undefined ? parseIntOption(opts.slaMinutes ?? opts.sla, "--sla-minutes") : undefined,
|
|
20433
20603
|
requires_approval: opts.approval || undefined,
|
|
@@ -20624,12 +20794,12 @@ function registerTaskCommands(program2) {
|
|
|
20624
20794
|
console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
|
|
20625
20795
|
}
|
|
20626
20796
|
});
|
|
20627
|
-
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <
|
|
20797
|
+
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug").option("--task-list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action(async (opts) => {
|
|
20628
20798
|
const globalOpts = program2.opts();
|
|
20629
20799
|
opts.tags = opts.tags || opts.tag;
|
|
20630
20800
|
opts.list = opts.list || opts.taskList;
|
|
20631
20801
|
const cloud = getTodosCloudClient();
|
|
20632
|
-
const projectId = cloud ? undefined : autoProject(globalOpts);
|
|
20802
|
+
const projectId = cloud && globalOpts.project ? await cloudResolveProjectRef(cloud, globalOpts.project) : cloud ? undefined : autoProject(globalOpts);
|
|
20633
20803
|
const hasAssignedFilter = Boolean(opts.assigned || opts.agentName);
|
|
20634
20804
|
const hasExplicitProjectFilter = Boolean(globalOpts.project || opts.projectName);
|
|
20635
20805
|
const allowedSortFields = new Set(["updated", "created", "priority", "status"]);
|
|
@@ -20647,7 +20817,7 @@ function registerTaskCommands(program2) {
|
|
|
20647
20817
|
filter["project_id"] = projectId;
|
|
20648
20818
|
}
|
|
20649
20819
|
if (opts.list && cloud) {
|
|
20650
|
-
filter["task_list_id"] = opts.list;
|
|
20820
|
+
filter["task_list_id"] = await cloudResolveTaskListRef(cloud, opts.list, projectId);
|
|
20651
20821
|
} else if (opts.list) {
|
|
20652
20822
|
const db = getDatabase();
|
|
20653
20823
|
const listId = resolvePartialId(db, "task_lists", opts.list);
|
|
@@ -20786,7 +20956,22 @@ function registerTaskCommands(program2) {
|
|
|
20786
20956
|
let task2;
|
|
20787
20957
|
if (cloud) {
|
|
20788
20958
|
const remote = await cloudGetTask(cloud, resolveTaskId(id));
|
|
20789
|
-
|
|
20959
|
+
const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
|
|
20960
|
+
task2 = remote ? {
|
|
20961
|
+
subtasks: [],
|
|
20962
|
+
dependencies: [],
|
|
20963
|
+
blocked_by: [],
|
|
20964
|
+
...remote,
|
|
20965
|
+
tags: remote.tags ?? [],
|
|
20966
|
+
comments: commentPage.comments,
|
|
20967
|
+
comments_page: {
|
|
20968
|
+
count: commentPage.count,
|
|
20969
|
+
limit: commentPage.limit,
|
|
20970
|
+
has_more: commentPage.has_more,
|
|
20971
|
+
next_cursor: commentPage.next_cursor,
|
|
20972
|
+
pagination_supported: commentPage.pagination_supported
|
|
20973
|
+
}
|
|
20974
|
+
} : null;
|
|
20790
20975
|
} else {
|
|
20791
20976
|
const resolvedId = resolveTaskId(id);
|
|
20792
20977
|
task2 = getTaskWithRelations(resolvedId);
|
|
@@ -20870,11 +21055,11 @@ function registerTaskCommands(program2) {
|
|
|
20870
21055
|
}
|
|
20871
21056
|
}
|
|
20872
21057
|
if (task2.comments.length > 0) {
|
|
21058
|
+
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
21059
|
console.log(chalk2.bold(`
|
|
20874
|
-
Comments (${task2.comments.length}):`));
|
|
21060
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
20875
21061
|
for (const c of task2.comments) {
|
|
20876
|
-
|
|
20877
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21062
|
+
console.log(formatHumanComment(c));
|
|
20878
21063
|
}
|
|
20879
21064
|
}
|
|
20880
21065
|
});
|
|
@@ -20900,7 +21085,23 @@ function registerTaskCommands(program2) {
|
|
|
20900
21085
|
let task2;
|
|
20901
21086
|
if (cloud) {
|
|
20902
21087
|
const remote = await cloudGetTask(cloud, resolvedId);
|
|
20903
|
-
|
|
21088
|
+
const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
|
|
21089
|
+
task2 = remote ? {
|
|
21090
|
+
subtasks: [],
|
|
21091
|
+
dependencies: [],
|
|
21092
|
+
blocked_by: [],
|
|
21093
|
+
checklist: [],
|
|
21094
|
+
...remote,
|
|
21095
|
+
tags: remote.tags ?? [],
|
|
21096
|
+
comments: commentPage.comments,
|
|
21097
|
+
comments_page: {
|
|
21098
|
+
count: commentPage.count,
|
|
21099
|
+
limit: commentPage.limit,
|
|
21100
|
+
has_more: commentPage.has_more,
|
|
21101
|
+
next_cursor: commentPage.next_cursor,
|
|
21102
|
+
pagination_supported: commentPage.pagination_supported
|
|
21103
|
+
}
|
|
21104
|
+
} : null;
|
|
20904
21105
|
} else {
|
|
20905
21106
|
task2 = getTaskWithRelations(resolvedId);
|
|
20906
21107
|
}
|
|
@@ -21007,11 +21208,11 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21007
21208
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
21008
21209
|
}
|
|
21009
21210
|
if (task2.comments.length > 0) {
|
|
21211
|
+
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
21212
|
console.log(chalk2.bold(`
|
|
21011
|
-
Comments (${task2.comments.length}):`));
|
|
21213
|
+
Comments (${task2.comments.length}${suffix}):`));
|
|
21012
21214
|
for (const c of task2.comments) {
|
|
21013
|
-
|
|
21014
|
-
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
21215
|
+
console.log(formatHumanComment(c));
|
|
21015
21216
|
}
|
|
21016
21217
|
}
|
|
21017
21218
|
if (task2.checklist && task2.checklist.length > 0) {
|
|
@@ -21307,7 +21508,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21307
21508
|
const resolvedId = resolveTaskId(id);
|
|
21308
21509
|
try {
|
|
21309
21510
|
if (cloud)
|
|
21310
|
-
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent);
|
|
21511
|
+
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent, !globalOpts.agent);
|
|
21311
21512
|
else
|
|
21312
21513
|
unlockTask(resolvedId, globalOpts.agent);
|
|
21313
21514
|
} catch (e) {
|
|
@@ -23262,7 +23463,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
23262
23463
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
23263
23464
|
const dir = getTaskListDir(taskListId);
|
|
23264
23465
|
if (!existsSync10(dir))
|
|
23265
|
-
|
|
23466
|
+
ensureDir(dir);
|
|
23266
23467
|
const filter = {};
|
|
23267
23468
|
if (projectId)
|
|
23268
23469
|
filter["project_id"] = projectId;
|
|
@@ -23445,7 +23646,7 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
23445
23646
|
}
|
|
23446
23647
|
var init_claude_tasks = __esm(() => {
|
|
23447
23648
|
init_tasks();
|
|
23448
|
-
|
|
23649
|
+
init_config();
|
|
23449
23650
|
init_sync_utils();
|
|
23450
23651
|
});
|
|
23451
23652
|
|
|
@@ -23489,7 +23690,7 @@ function metadataKey(agent) {
|
|
|
23489
23690
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
23490
23691
|
const dir = getTaskListDir2(agent, taskListId);
|
|
23491
23692
|
if (!existsSync11(dir))
|
|
23492
|
-
|
|
23693
|
+
ensureDir(dir);
|
|
23493
23694
|
const filter = {};
|
|
23494
23695
|
if (projectId)
|
|
23495
23696
|
filter["project_id"] = projectId;
|
|
@@ -23662,7 +23863,7 @@ function syncAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
23662
23863
|
var init_agent_tasks = __esm(() => {
|
|
23663
23864
|
init_tasks();
|
|
23664
23865
|
init_sync_utils();
|
|
23665
|
-
|
|
23866
|
+
init_config();
|
|
23666
23867
|
});
|
|
23667
23868
|
|
|
23668
23869
|
// src/lib/sync.ts
|
|
@@ -23734,7 +23935,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
23734
23935
|
var init_sync = __esm(() => {
|
|
23735
23936
|
init_claude_tasks();
|
|
23736
23937
|
init_agent_tasks();
|
|
23737
|
-
|
|
23938
|
+
init_config();
|
|
23738
23939
|
});
|
|
23739
23940
|
|
|
23740
23941
|
// src/lib/project-bootstrap.ts
|
|
@@ -31447,7 +31648,7 @@ function applyExportProfile(data, options = {}) {
|
|
|
31447
31648
|
}
|
|
31448
31649
|
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
31650
|
var init_local_encryption = __esm(() => {
|
|
31450
|
-
|
|
31651
|
+
init_config();
|
|
31451
31652
|
init_redaction();
|
|
31452
31653
|
EncryptionKeyUnavailableError = class EncryptionKeyUnavailableError extends Error {
|
|
31453
31654
|
keyEnv;
|
|
@@ -32626,7 +32827,7 @@ var init_project_commands = __esm(() => {
|
|
|
32626
32827
|
init_cloud_router();
|
|
32627
32828
|
init_saved_search_views();
|
|
32628
32829
|
init_sync();
|
|
32629
|
-
|
|
32830
|
+
init_config();
|
|
32630
32831
|
init_helpers();
|
|
32631
32832
|
});
|
|
32632
32833
|
|
|
@@ -33626,9 +33827,11 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33626
33827
|
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
33828
|
try {
|
|
33628
33829
|
const globalOpts = program2.opts();
|
|
33629
|
-
const
|
|
33830
|
+
const cloud = getTodosCloudClient();
|
|
33831
|
+
const projectId = cloud ? globalOpts.project : autoProject(globalOpts);
|
|
33630
33832
|
if (opts.add) {
|
|
33631
|
-
const
|
|
33833
|
+
const input = { name: opts.add, slug: opts.slug, description: opts.description, project_id: projectId };
|
|
33834
|
+
const list = cloud ? await cloudCreateTaskList(cloud, input) : createTaskList(input);
|
|
33632
33835
|
if (globalOpts.json) {
|
|
33633
33836
|
output(list, true);
|
|
33634
33837
|
return;
|
|
@@ -33640,6 +33843,14 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33640
33843
|
return;
|
|
33641
33844
|
}
|
|
33642
33845
|
if (opts.delete) {
|
|
33846
|
+
if (cloud) {
|
|
33847
|
+
const resolved2 = await cloudResolveTaskListRef(cloud, opts.delete, projectId ?? undefined);
|
|
33848
|
+
if (!resolved2)
|
|
33849
|
+
throw new Error(`Task list not found or ambiguous: ${opts.delete}`);
|
|
33850
|
+
await cloudDeleteTaskList(cloud, resolved2);
|
|
33851
|
+
console.log(chalk5.green("Task list deleted."));
|
|
33852
|
+
return;
|
|
33853
|
+
}
|
|
33643
33854
|
const db = getDatabase();
|
|
33644
33855
|
const resolved = resolvePartialId(db, "task_lists", opts.delete);
|
|
33645
33856
|
if (!resolved) {
|
|
@@ -33650,7 +33861,6 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33650
33861
|
console.log(chalk5.green("Task list deleted."));
|
|
33651
33862
|
return;
|
|
33652
33863
|
}
|
|
33653
|
-
const cloud = getTodosCloudClient();
|
|
33654
33864
|
const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
|
|
33655
33865
|
if (globalOpts.json) {
|
|
33656
33866
|
output(lists, true);
|
|
@@ -35065,7 +35275,7 @@ function renderExtensionSummary(record) {
|
|
|
35065
35275
|
}
|
|
35066
35276
|
var BUILTIN_CLI_COMMANDS;
|
|
35067
35277
|
var init_local_extensions = __esm(() => {
|
|
35068
|
-
|
|
35278
|
+
init_config();
|
|
35069
35279
|
init_mcp();
|
|
35070
35280
|
init_package_version();
|
|
35071
35281
|
init_redaction();
|
|
@@ -35497,7 +35707,7 @@ var init_policy_packs = __esm(() => {
|
|
|
35497
35707
|
init_database();
|
|
35498
35708
|
init_tasks();
|
|
35499
35709
|
init_task_runs();
|
|
35500
|
-
|
|
35710
|
+
init_config();
|
|
35501
35711
|
});
|
|
35502
35712
|
|
|
35503
35713
|
// src/db/checkpoints.ts
|
|
@@ -36020,7 +36230,7 @@ function describeTerminalNotificationRule(rule) {
|
|
|
36020
36230
|
}
|
|
36021
36231
|
var SEVERITY_ORDER, EVENT_SEVERITY, VALID_SEVERITIES, VALID_FORMATS;
|
|
36022
36232
|
var init_terminal_notifications = __esm(() => {
|
|
36023
|
-
|
|
36233
|
+
init_config();
|
|
36024
36234
|
init_event_hooks();
|
|
36025
36235
|
init_redaction();
|
|
36026
36236
|
SEVERITY_ORDER = {
|
|
@@ -37523,7 +37733,7 @@ function createTodosCloudQueryClientFromEnv(env = process.env, options = {}) {
|
|
|
37523
37733
|
return createTodosCloudQueryClient(url, options);
|
|
37524
37734
|
}
|
|
37525
37735
|
var init_cloud_client = __esm(() => {
|
|
37526
|
-
|
|
37736
|
+
init_config2();
|
|
37527
37737
|
});
|
|
37528
37738
|
|
|
37529
37739
|
// src/storage/sqlite-snapshot.ts
|
|
@@ -37905,6 +38115,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37905
38115
|
list: (filter = {}) => listTasks(filter, database()),
|
|
37906
38116
|
count: (filter = {}) => countTasks(filter, database()),
|
|
37907
38117
|
update: (id, input) => updateTask(id, input, database()),
|
|
38118
|
+
unlock: (id, agentId) => {
|
|
38119
|
+
unlockTask(id, agentId, database());
|
|
38120
|
+
return true;
|
|
38121
|
+
},
|
|
37908
38122
|
delete: (id) => deleteTask(id, database()),
|
|
37909
38123
|
start: (id, agentId) => startTask(id, agentId, database()),
|
|
37910
38124
|
complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
|
|
@@ -37956,6 +38170,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
37956
38170
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId) => logTaskChange(taskId, action, field, oldValue, newValue, agentId, database()),
|
|
37957
38171
|
addComment: (input) => addComment(input, database()),
|
|
37958
38172
|
getComments: (taskId) => listComments(taskId, database()),
|
|
38173
|
+
getCommentsPage: (taskId, options2) => {
|
|
38174
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
38175
|
+
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
38176
|
+
}
|
|
38177
|
+
let comments = listComments(taskId, database());
|
|
38178
|
+
comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
|
|
38179
|
+
if (options2?.before) {
|
|
38180
|
+
const before = options2.before;
|
|
38181
|
+
comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
|
|
38182
|
+
}
|
|
38183
|
+
if (options2?.limit !== undefined)
|
|
38184
|
+
comments = comments.slice(-options2.limit);
|
|
38185
|
+
return comments;
|
|
38186
|
+
},
|
|
37959
38187
|
getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
|
|
37960
38188
|
getRecentActivity: (limit) => getRecentActivity(limit, database())
|
|
37961
38189
|
},
|
|
@@ -38013,6 +38241,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
|
|
|
38013
38241
|
)`
|
|
38014
38242
|
];
|
|
38015
38243
|
}
|
|
38244
|
+
function postgresTodosCommentCursorIndexSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABLE) {
|
|
38245
|
+
assertSafeIdentifier(tableName);
|
|
38246
|
+
return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${tableName}_comment_task_created_idx
|
|
38247
|
+
ON ${tableName} (service, (payload->>'task_id'), (payload->>'created_at'), object_id)
|
|
38248
|
+
WHERE object_type = 'comments' AND deleted_at IS NULL`;
|
|
38249
|
+
}
|
|
38016
38250
|
|
|
38017
38251
|
class PostgresTodosSyncStore {
|
|
38018
38252
|
client;
|
|
@@ -38538,7 +38772,7 @@ function __resetRuntimeShadowForTests() {
|
|
|
38538
38772
|
}
|
|
38539
38773
|
var _capturedDb = null, _outbox = null, _cloud = null, _exitRegistered = false;
|
|
38540
38774
|
var init_shadow_runtime = __esm(() => {
|
|
38541
|
-
|
|
38775
|
+
init_config2();
|
|
38542
38776
|
init_cloud_client();
|
|
38543
38777
|
init_shadow_outbox();
|
|
38544
38778
|
});
|
|
@@ -38994,7 +39228,24 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
38994
39228
|
audit: {
|
|
38995
39229
|
logTaskChange: (taskId, action, field, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context),
|
|
38996
39230
|
addComment: (input, context) => addComment2(input, store, context),
|
|
38997
|
-
getComments: async (taskId) =>
|
|
39231
|
+
getComments: async (taskId) => {
|
|
39232
|
+
const pages = [];
|
|
39233
|
+
let before;
|
|
39234
|
+
while (true) {
|
|
39235
|
+
const page = await store.listComments(taskId, { limit: 1000, ...before ? { before } : {} });
|
|
39236
|
+
if (page.length === 0)
|
|
39237
|
+
break;
|
|
39238
|
+
pages.unshift(page);
|
|
39239
|
+
if (page.length < 1000)
|
|
39240
|
+
break;
|
|
39241
|
+
const oldest = page[0];
|
|
39242
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
39243
|
+
}
|
|
39244
|
+
return pages.flat().map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
39245
|
+
},
|
|
39246
|
+
getCommentsPage: async (taskId, options2) => {
|
|
39247
|
+
return (await store.listComments(taskId, options2)).map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
39248
|
+
},
|
|
38998
39249
|
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
39250
|
getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
|
|
39000
39251
|
},
|
|
@@ -39044,6 +39295,27 @@ class PostgresJsonRecordStore {
|
|
|
39044
39295
|
async list(type) {
|
|
39045
39296
|
return (await this.listRecords(type)).map((record) => record.payload);
|
|
39046
39297
|
}
|
|
39298
|
+
async listComments(taskId, options = {}) {
|
|
39299
|
+
await this.ensureSchema();
|
|
39300
|
+
const limit = options.limit ?? 100;
|
|
39301
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
39302
|
+
throw new Error("Postgres comment limit must be an integer between 1 and 1001");
|
|
39303
|
+
}
|
|
39304
|
+
const params = [this.service, taskId];
|
|
39305
|
+
let cursorPredicate = "";
|
|
39306
|
+
if (options.before) {
|
|
39307
|
+
params.push(options.before.created_at, options.before.id);
|
|
39308
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
39309
|
+
}
|
|
39310
|
+
params.push(limit);
|
|
39311
|
+
const result = await this.options.client.query(`/* todos:list-comments */ SELECT payload FROM ${this.tableName}
|
|
39312
|
+
WHERE service = $1 AND object_type = 'comments' AND deleted_at IS NULL
|
|
39313
|
+
AND payload->>'task_id' = $2
|
|
39314
|
+
${cursorPredicate}
|
|
39315
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
39316
|
+
LIMIT $${params.length}`, params);
|
|
39317
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
39318
|
+
}
|
|
39047
39319
|
async listRecords(type) {
|
|
39048
39320
|
await this.ensureSchema();
|
|
39049
39321
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -39437,7 +39709,7 @@ async function lockTask2(id, agentId, store) {
|
|
|
39437
39709
|
async function unlockTask2(id, agentId, store) {
|
|
39438
39710
|
const task = await requireRecord("tasks", id, store);
|
|
39439
39711
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
39440
|
-
throw new
|
|
39712
|
+
throw new LockError(id, task.locked_by);
|
|
39441
39713
|
}
|
|
39442
39714
|
await patchTask(task, { locked_by: null, locked_at: null }, store);
|
|
39443
39715
|
return true;
|
|
@@ -39802,13 +40074,16 @@ async function addComment2(input, store, context) {
|
|
|
39802
40074
|
task_id: input.task_id,
|
|
39803
40075
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
39804
40076
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
39805
|
-
content: input.content,
|
|
40077
|
+
content: redactEvidenceText(input.content),
|
|
39806
40078
|
type: input.type ?? "comment",
|
|
39807
40079
|
progress_pct: input.progress_pct ?? null,
|
|
39808
40080
|
created_at: new Date().toISOString()
|
|
39809
40081
|
};
|
|
39810
40082
|
return store.upsert("comments", comment, context);
|
|
39811
40083
|
}
|
|
40084
|
+
function redactComment2(comment) {
|
|
40085
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
40086
|
+
}
|
|
39812
40087
|
async function exportSnapshot(store) {
|
|
39813
40088
|
return {
|
|
39814
40089
|
exportedAt: new Date().toISOString(),
|
|
@@ -39954,7 +40229,109 @@ function numberValue2(value) {
|
|
|
39954
40229
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
39955
40230
|
}
|
|
39956
40231
|
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 = () => {
|
|
40232
|
+
var init_postgres_adapter = __esm(() => {
|
|
40233
|
+
init_types();
|
|
40234
|
+
init_redaction();
|
|
40235
|
+
});
|
|
40236
|
+
|
|
40237
|
+
// src/storage/comment-redaction-backfill.ts
|
|
40238
|
+
function assertSafeIdentifier2(value) {
|
|
40239
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
40240
|
+
throw new Error(`Unsafe Postgres identifier: ${value}`);
|
|
40241
|
+
}
|
|
40242
|
+
}
|
|
40243
|
+
function payloadObject(value) {
|
|
40244
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
40245
|
+
return value;
|
|
40246
|
+
if (typeof value !== "string")
|
|
40247
|
+
return null;
|
|
40248
|
+
try {
|
|
40249
|
+
const parsed = JSON.parse(value);
|
|
40250
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
40251
|
+
} catch {
|
|
40252
|
+
return null;
|
|
40253
|
+
}
|
|
40254
|
+
}
|
|
40255
|
+
async function backfillPostgresCommentRedaction(client, options = {}) {
|
|
40256
|
+
const apply = options.apply === true;
|
|
40257
|
+
if (apply && options.confirmation !== COMMENT_REDACTION_BACKFILL_CONFIRMATION) {
|
|
40258
|
+
throw new Error(`Applying the comment redaction backfill requires confirmation ${COMMENT_REDACTION_BACKFILL_CONFIRMATION}`);
|
|
40259
|
+
}
|
|
40260
|
+
const tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
|
|
40261
|
+
assertSafeIdentifier2(tableName);
|
|
40262
|
+
const service = options.service ?? "todos";
|
|
40263
|
+
const batchSize = options.batchSize ?? 100;
|
|
40264
|
+
if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 500) {
|
|
40265
|
+
throw new Error("Comment redaction backfill batchSize must be an integer between 1 and 500");
|
|
40266
|
+
}
|
|
40267
|
+
const result = {
|
|
40268
|
+
dry_run: !apply,
|
|
40269
|
+
scanned: 0,
|
|
40270
|
+
candidates: 0,
|
|
40271
|
+
updated: 0,
|
|
40272
|
+
conflicts: 0,
|
|
40273
|
+
batches: 0,
|
|
40274
|
+
remaining_candidates: 0
|
|
40275
|
+
};
|
|
40276
|
+
let afterId = "";
|
|
40277
|
+
while (true) {
|
|
40278
|
+
const page = await client.query(`/* todos:comment-redaction-backfill-scan */
|
|
40279
|
+
SELECT object_id, payload
|
|
40280
|
+
FROM ${tableName}
|
|
40281
|
+
WHERE service = $1 AND object_type = 'comments'
|
|
40282
|
+
AND object_id > $2
|
|
40283
|
+
ORDER BY object_id ASC
|
|
40284
|
+
LIMIT $3`, [service, afterId, batchSize]);
|
|
40285
|
+
if (page.rows.length === 0)
|
|
40286
|
+
break;
|
|
40287
|
+
result.batches += 1;
|
|
40288
|
+
for (const row of page.rows) {
|
|
40289
|
+
afterId = row.object_id;
|
|
40290
|
+
result.scanned += 1;
|
|
40291
|
+
const payload = payloadObject(row.payload);
|
|
40292
|
+
const original = payload?.["content"];
|
|
40293
|
+
if (typeof original !== "string")
|
|
40294
|
+
continue;
|
|
40295
|
+
const redacted = redactEvidenceText(original);
|
|
40296
|
+
if (redacted === original)
|
|
40297
|
+
continue;
|
|
40298
|
+
result.candidates += 1;
|
|
40299
|
+
if (!apply)
|
|
40300
|
+
continue;
|
|
40301
|
+
const nextPayload = { ...payload, content: redacted };
|
|
40302
|
+
const update = await client.query(`/* todos:comment-redaction-backfill-apply */
|
|
40303
|
+
UPDATE ${tableName}
|
|
40304
|
+
SET payload = $3::jsonb
|
|
40305
|
+
WHERE service = $1 AND object_type = 'comments' AND object_id = $2
|
|
40306
|
+
AND payload = $4::jsonb
|
|
40307
|
+
RETURNING object_id`, [service, row.object_id, nextPayload, row.payload]);
|
|
40308
|
+
if (update.rows.length === 1)
|
|
40309
|
+
result.updated += 1;
|
|
40310
|
+
else
|
|
40311
|
+
result.conflicts += 1;
|
|
40312
|
+
}
|
|
40313
|
+
if (page.rows.length < batchSize)
|
|
40314
|
+
break;
|
|
40315
|
+
}
|
|
40316
|
+
if (!apply) {
|
|
40317
|
+
result.remaining_candidates = result.candidates;
|
|
40318
|
+
return result;
|
|
40319
|
+
}
|
|
40320
|
+
const verification = await backfillPostgresCommentRedaction(client, {
|
|
40321
|
+
...options,
|
|
40322
|
+
apply: false,
|
|
40323
|
+
confirmation: undefined
|
|
40324
|
+
});
|
|
40325
|
+
result.remaining_candidates = verification.candidates;
|
|
40326
|
+
return result;
|
|
40327
|
+
}
|
|
40328
|
+
function isCommentRedactionBackfillComplete(result) {
|
|
40329
|
+
return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
|
|
40330
|
+
}
|
|
40331
|
+
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
40332
|
+
var init_comment_redaction_backfill = __esm(() => {
|
|
40333
|
+
init_redaction();
|
|
40334
|
+
});
|
|
39958
40335
|
|
|
39959
40336
|
// src/server/cloud.ts
|
|
39960
40337
|
var exports_cloud = {};
|
|
@@ -39968,7 +40345,9 @@ __export(exports_cloud, {
|
|
|
39968
40345
|
getCloudStorageAdapter: () => getCloudStorageAdapter,
|
|
39969
40346
|
getApiKeyStore: () => getApiKeyStore,
|
|
39970
40347
|
ensureCloudSchema: () => ensureCloudSchema,
|
|
40348
|
+
ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
|
|
39971
40349
|
closeCloud: () => closeCloud,
|
|
40350
|
+
backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
|
|
39972
40351
|
TODOS_APP_SLUG: () => TODOS_APP_SLUG
|
|
39973
40352
|
});
|
|
39974
40353
|
function resolveCloudDatabaseUrl(env = process.env) {
|
|
@@ -40046,6 +40425,9 @@ async function ensureCloudSchema() {
|
|
|
40046
40425
|
})();
|
|
40047
40426
|
return schemaEnsured;
|
|
40048
40427
|
}
|
|
40428
|
+
async function ensureCloudCommentCursorIndex() {
|
|
40429
|
+
await getClient().query(postgresTodosCommentCursorIndexSql());
|
|
40430
|
+
}
|
|
40049
40431
|
async function normalizeCloudPayloads() {
|
|
40050
40432
|
const client = getClient();
|
|
40051
40433
|
const res = await client.query(`UPDATE todos_sync_records
|
|
@@ -40054,6 +40436,9 @@ async function normalizeCloudPayloads() {
|
|
|
40054
40436
|
RETURNING object_id AS id`);
|
|
40055
40437
|
return res.rows.length;
|
|
40056
40438
|
}
|
|
40439
|
+
function backfillCloudCommentRedaction(options = {}) {
|
|
40440
|
+
return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
|
|
40441
|
+
}
|
|
40057
40442
|
async function pingCloud() {
|
|
40058
40443
|
const client = getClient();
|
|
40059
40444
|
const res = await client.query("select 1 as ok");
|
|
@@ -40074,6 +40459,7 @@ var init_cloud = __esm(() => {
|
|
|
40074
40459
|
init_auth();
|
|
40075
40460
|
init_cloud_client();
|
|
40076
40461
|
init_postgres_adapter();
|
|
40462
|
+
init_comment_redaction_backfill();
|
|
40077
40463
|
});
|
|
40078
40464
|
|
|
40079
40465
|
// src/server/openapi.ts
|
|
@@ -40097,6 +40483,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40097
40483
|
schemas: {
|
|
40098
40484
|
Task: taskSchema,
|
|
40099
40485
|
Project: projectSchema,
|
|
40486
|
+
TaskComment: taskCommentSchema,
|
|
40100
40487
|
CreateTaskInput: {
|
|
40101
40488
|
type: "object",
|
|
40102
40489
|
required: ["title"],
|
|
@@ -40131,6 +40518,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40131
40518
|
description: { type: "string" },
|
|
40132
40519
|
task_prefix: { type: "string" }
|
|
40133
40520
|
}
|
|
40521
|
+
},
|
|
40522
|
+
CreateTaskCommentInput: {
|
|
40523
|
+
type: "object",
|
|
40524
|
+
required: ["content"],
|
|
40525
|
+
properties: {
|
|
40526
|
+
content: { type: "string", minLength: 1 },
|
|
40527
|
+
agent_id: { type: "string" },
|
|
40528
|
+
session_id: { type: "string" },
|
|
40529
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
40530
|
+
progress_pct: { type: "number" }
|
|
40531
|
+
}
|
|
40134
40532
|
}
|
|
40135
40533
|
}
|
|
40136
40534
|
},
|
|
@@ -40229,6 +40627,61 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40229
40627
|
}
|
|
40230
40628
|
}
|
|
40231
40629
|
},
|
|
40630
|
+
"/v1/tasks/{id}/comments": {
|
|
40631
|
+
get: {
|
|
40632
|
+
operationId: "listTaskComments",
|
|
40633
|
+
summary: "List a bounded page of task comments",
|
|
40634
|
+
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.",
|
|
40635
|
+
parameters: [
|
|
40636
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
40637
|
+
{ name: "limit", in: "query", required: true, schema: { type: "integer", minimum: 1, maximum: 500, default: 100 } },
|
|
40638
|
+
{ name: "cursor", in: "query", schema: { type: "string" } }
|
|
40639
|
+
],
|
|
40640
|
+
responses: {
|
|
40641
|
+
"200": {
|
|
40642
|
+
content: {
|
|
40643
|
+
"application/json": {
|
|
40644
|
+
schema: {
|
|
40645
|
+
type: "object",
|
|
40646
|
+
required: ["comments", "count", "has_more", "next_cursor"],
|
|
40647
|
+
properties: {
|
|
40648
|
+
comments: { type: "array", maxItems: 500, items: { $ref: "#/components/schemas/TaskComment" } },
|
|
40649
|
+
count: { type: "integer", minimum: 0, maximum: 500 },
|
|
40650
|
+
has_more: { type: "boolean" },
|
|
40651
|
+
next_cursor: { type: "string", nullable: true }
|
|
40652
|
+
}
|
|
40653
|
+
}
|
|
40654
|
+
}
|
|
40655
|
+
}
|
|
40656
|
+
},
|
|
40657
|
+
"426": {
|
|
40658
|
+
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."
|
|
40659
|
+
}
|
|
40660
|
+
}
|
|
40661
|
+
},
|
|
40662
|
+
post: {
|
|
40663
|
+
operationId: "createTaskComment",
|
|
40664
|
+
summary: "Create a task comment",
|
|
40665
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
40666
|
+
requestBody: {
|
|
40667
|
+
required: true,
|
|
40668
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTaskCommentInput" } } }
|
|
40669
|
+
},
|
|
40670
|
+
responses: {
|
|
40671
|
+
"201": {
|
|
40672
|
+
content: {
|
|
40673
|
+
"application/json": {
|
|
40674
|
+
schema: {
|
|
40675
|
+
type: "object",
|
|
40676
|
+
required: ["comment"],
|
|
40677
|
+
properties: { comment: { $ref: "#/components/schemas/TaskComment" } }
|
|
40678
|
+
}
|
|
40679
|
+
}
|
|
40680
|
+
}
|
|
40681
|
+
}
|
|
40682
|
+
}
|
|
40683
|
+
}
|
|
40684
|
+
},
|
|
40232
40685
|
"/v1/tasks/{id}/start": {
|
|
40233
40686
|
post: {
|
|
40234
40687
|
operationId: "startTask",
|
|
@@ -40347,7 +40800,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
40347
40800
|
}
|
|
40348
40801
|
};
|
|
40349
40802
|
}
|
|
40350
|
-
var taskSchema, projectSchema;
|
|
40803
|
+
var taskSchema, projectSchema, taskCommentSchema;
|
|
40351
40804
|
var init_openapi = __esm(() => {
|
|
40352
40805
|
init_package_version();
|
|
40353
40806
|
taskSchema = {
|
|
@@ -40378,6 +40831,20 @@ var init_openapi = __esm(() => {
|
|
|
40378
40831
|
updated_at: { type: "string" }
|
|
40379
40832
|
}
|
|
40380
40833
|
};
|
|
40834
|
+
taskCommentSchema = {
|
|
40835
|
+
type: "object",
|
|
40836
|
+
required: ["id", "task_id", "agent_id", "session_id", "content", "type", "progress_pct", "created_at"],
|
|
40837
|
+
properties: {
|
|
40838
|
+
id: { type: "string" },
|
|
40839
|
+
task_id: { type: "string" },
|
|
40840
|
+
agent_id: { type: "string", nullable: true },
|
|
40841
|
+
session_id: { type: "string", nullable: true },
|
|
40842
|
+
content: { type: "string" },
|
|
40843
|
+
type: { type: "string", enum: ["comment", "progress", "note"] },
|
|
40844
|
+
progress_pct: { type: "number", nullable: true },
|
|
40845
|
+
created_at: { type: "string", format: "date-time" }
|
|
40846
|
+
}
|
|
40847
|
+
};
|
|
40381
40848
|
});
|
|
40382
40849
|
|
|
40383
40850
|
// src/server/v1.ts
|
|
@@ -40407,6 +40874,29 @@ function contextFromPrincipal(principal, body) {
|
|
|
40407
40874
|
const agentId = body?.agent_id || principal.agent || undefined;
|
|
40408
40875
|
return agentId ? { agentId } : {};
|
|
40409
40876
|
}
|
|
40877
|
+
function redactComment3(comment) {
|
|
40878
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
40879
|
+
}
|
|
40880
|
+
function encodeCommentCursor(comment) {
|
|
40881
|
+
return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
|
|
40882
|
+
}
|
|
40883
|
+
function decodeCommentCursor(value) {
|
|
40884
|
+
if (value.length > 1024)
|
|
40885
|
+
throw new Error("invalid comment cursor");
|
|
40886
|
+
let parsed;
|
|
40887
|
+
try {
|
|
40888
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
40889
|
+
} catch {
|
|
40890
|
+
throw new Error("invalid comment cursor");
|
|
40891
|
+
}
|
|
40892
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
40893
|
+
throw new Error("invalid comment cursor");
|
|
40894
|
+
const cursor = parsed;
|
|
40895
|
+
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) {
|
|
40896
|
+
throw new Error("invalid comment cursor");
|
|
40897
|
+
}
|
|
40898
|
+
return { created_at: cursor["created_at"], id: cursor["id"] };
|
|
40899
|
+
}
|
|
40410
40900
|
function normalizeImportSnapshot(raw) {
|
|
40411
40901
|
const body = raw && typeof raw === "object" ? raw : {};
|
|
40412
40902
|
const arr = (v) => Array.isArray(v) ? v : [];
|
|
@@ -40427,7 +40917,7 @@ function normalizeImportSnapshot(raw) {
|
|
|
40427
40917
|
function countSnapshotRecords(s) {
|
|
40428
40918
|
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
40919
|
}
|
|
40430
|
-
async function handleV1Request(req, url) {
|
|
40920
|
+
async function handleV1Request(req, url, dependencies = {}) {
|
|
40431
40921
|
const path = url.pathname;
|
|
40432
40922
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
40433
40923
|
return null;
|
|
@@ -40436,7 +40926,7 @@ async function handleV1Request(req, url) {
|
|
|
40436
40926
|
const requiredScopes = [isWrite ? "todos:write" : "todos:read"];
|
|
40437
40927
|
let verifier;
|
|
40438
40928
|
try {
|
|
40439
|
-
verifier = getCloudVerifier();
|
|
40929
|
+
verifier = (dependencies.getVerifier ?? getCloudVerifier)();
|
|
40440
40930
|
} catch (e) {
|
|
40441
40931
|
return error(503, e.message);
|
|
40442
40932
|
}
|
|
@@ -40445,8 +40935,8 @@ async function handleV1Request(req, url) {
|
|
|
40445
40935
|
return error(decision.status, decision.message, { reason: decision.reason });
|
|
40446
40936
|
}
|
|
40447
40937
|
const principal = decision.principal;
|
|
40448
|
-
await ensureCloudSchema();
|
|
40449
|
-
const store = getCloudStorageAdapter();
|
|
40938
|
+
await (dependencies.ensureSchema ?? ensureCloudSchema)();
|
|
40939
|
+
const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
|
|
40450
40940
|
const segments = path.split("/").filter(Boolean);
|
|
40451
40941
|
const resource = segments[1];
|
|
40452
40942
|
const id = segments[2];
|
|
@@ -40532,10 +41022,16 @@ async function handleV1Request(req, url) {
|
|
|
40532
41022
|
if (!id) {
|
|
40533
41023
|
if (method === "GET") {
|
|
40534
41024
|
const filter = {
|
|
40535
|
-
...url.searchParams.get("status") ? {
|
|
40536
|
-
|
|
41025
|
+
...url.searchParams.get("status") ? {
|
|
41026
|
+
status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
|
|
41027
|
+
} : {},
|
|
41028
|
+
...url.searchParams.get("priority") ? {
|
|
41029
|
+
priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
|
|
41030
|
+
} : {},
|
|
40537
41031
|
...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
|
|
41032
|
+
...url.searchParams.has("parent_id") ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : {},
|
|
40538
41033
|
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
|
|
41034
|
+
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
40539
41035
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
40540
41036
|
...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
|
|
40541
41037
|
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
@@ -40559,8 +41055,47 @@ async function handleV1Request(req, url) {
|
|
|
40559
41055
|
if (action) {
|
|
40560
41056
|
if (action === "comments") {
|
|
40561
41057
|
if (method === "GET") {
|
|
40562
|
-
|
|
40563
|
-
|
|
41058
|
+
if (!await store.tasks.get(id))
|
|
41059
|
+
return error(404, "task not found");
|
|
41060
|
+
const rawLimit = url.searchParams.get("limit");
|
|
41061
|
+
const cursor = url.searchParams.get("cursor");
|
|
41062
|
+
if (rawLimit === null && cursor === null) {
|
|
41063
|
+
const storageContext = contextFromPrincipal(principal);
|
|
41064
|
+
const legacyPage = (await (store.audit.getCommentsPage ? store.audit.getCommentsPage(id, { limit: LEGACY_COMMENT_RESPONSE_LIMIT + 1 }, storageContext) : store.audit.getComments(id, storageContext))).map(redactComment3);
|
|
41065
|
+
if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
|
|
41066
|
+
return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
|
|
41067
|
+
}
|
|
41068
|
+
return json2({
|
|
41069
|
+
comments: legacyPage,
|
|
41070
|
+
count: legacyPage.length,
|
|
41071
|
+
has_more: false,
|
|
41072
|
+
next_cursor: null
|
|
41073
|
+
});
|
|
41074
|
+
}
|
|
41075
|
+
const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
|
|
41076
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
|
|
41077
|
+
return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
|
|
41078
|
+
}
|
|
41079
|
+
let before;
|
|
41080
|
+
if (cursor) {
|
|
41081
|
+
try {
|
|
41082
|
+
before = decodeCommentCursor(cursor);
|
|
41083
|
+
} catch {
|
|
41084
|
+
return error(400, "invalid comment cursor");
|
|
41085
|
+
}
|
|
41086
|
+
}
|
|
41087
|
+
if (!store.audit.getCommentsPage) {
|
|
41088
|
+
return error(426, "storage adapter must be upgraded to support cursor-paginated comments");
|
|
41089
|
+
}
|
|
41090
|
+
const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
|
|
41091
|
+
const hasMore = page.length > limit;
|
|
41092
|
+
const comments = hasMore ? page.slice(1) : page;
|
|
41093
|
+
return json2({
|
|
41094
|
+
comments,
|
|
41095
|
+
count: comments.length,
|
|
41096
|
+
has_more: hasMore,
|
|
41097
|
+
next_cursor: hasMore && comments[0] ? encodeCommentCursor(comments[0]) : null
|
|
41098
|
+
});
|
|
40564
41099
|
}
|
|
40565
41100
|
if (method === "POST") {
|
|
40566
41101
|
const body2 = await readJson(req) ?? {};
|
|
@@ -40578,7 +41113,7 @@ async function handleV1Request(req, url) {
|
|
|
40578
41113
|
type: body2.type,
|
|
40579
41114
|
progress_pct: body2.progress_pct
|
|
40580
41115
|
}, contextFromPrincipal(principal, body2));
|
|
40581
|
-
return json2({ comment }, 201);
|
|
41116
|
+
return json2({ comment: redactComment3(comment) }, 201);
|
|
40582
41117
|
}
|
|
40583
41118
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
40584
41119
|
}
|
|
@@ -40593,17 +41128,30 @@ async function handleV1Request(req, url) {
|
|
|
40593
41128
|
if (action === "lock" || action === "unlock") {
|
|
40594
41129
|
if (method !== "POST")
|
|
40595
41130
|
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
41131
|
const body2 = await readJson(req) ?? {};
|
|
40600
41132
|
if (!await store.tasks.get(id))
|
|
40601
41133
|
return error(404, "task not found");
|
|
40602
41134
|
if (action === "lock") {
|
|
40603
|
-
|
|
40604
|
-
|
|
41135
|
+
if (typeof store.tasks.lock !== "function")
|
|
41136
|
+
return error(501, "task locking is not supported by this storage backend");
|
|
41137
|
+
const agentId3 = body2.agent_id || principal.agent || "todos-serve";
|
|
41138
|
+
return json2({ result: await store.tasks.lock(id, agentId3) });
|
|
40605
41139
|
}
|
|
40606
|
-
|
|
41140
|
+
if (typeof store.tasks.unlock !== "function")
|
|
41141
|
+
return error(501, "task unlocking is not supported by this storage backend");
|
|
41142
|
+
if (body2.force === true) {
|
|
41143
|
+
if (!principal.scopes.includes("todos:*"))
|
|
41144
|
+
return error(403, "force unlock requires todos:* scope");
|
|
41145
|
+
const released2 = await store.tasks.unlock(id);
|
|
41146
|
+
return json2({ success: released2 });
|
|
41147
|
+
}
|
|
41148
|
+
if (body2.agent_id && principal.agent && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
41149
|
+
return error(403, "unlock agent_id must match the authenticated agent");
|
|
41150
|
+
}
|
|
41151
|
+
const agentId2 = principal.agent || body2.agent_id;
|
|
41152
|
+
if (!agentId2)
|
|
41153
|
+
return error(403, "unlock requires an agent-bound key or force=true");
|
|
41154
|
+
const released = await store.tasks.unlock(id, agentId2);
|
|
40607
41155
|
return json2({ success: released });
|
|
40608
41156
|
}
|
|
40609
41157
|
if (action === "dependencies") {
|
|
@@ -40886,12 +41434,28 @@ async function handleV1Request(req, url) {
|
|
|
40886
41434
|
const activity = await store.audit.getRecentActivity(limit);
|
|
40887
41435
|
return json2({ activity, count: activity.length });
|
|
40888
41436
|
}
|
|
40889
|
-
if (resource === "task-lists"
|
|
40890
|
-
if (method
|
|
40891
|
-
|
|
40892
|
-
|
|
40893
|
-
|
|
40894
|
-
|
|
41437
|
+
if (resource === "task-lists") {
|
|
41438
|
+
if (!id && method === "GET") {
|
|
41439
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
41440
|
+
const taskLists = await store.taskLists.list(projectId);
|
|
41441
|
+
return json2({ task_lists: taskLists, count: taskLists.length });
|
|
41442
|
+
}
|
|
41443
|
+
if (!id && method === "POST") {
|
|
41444
|
+
const body = await readJson(req);
|
|
41445
|
+
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
41446
|
+
return error(400, "name is required");
|
|
41447
|
+
const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
|
|
41448
|
+
return json2({ task_list: taskList }, 201);
|
|
41449
|
+
}
|
|
41450
|
+
if (id && method === "GET") {
|
|
41451
|
+
const taskList = await store.taskLists.get(id);
|
|
41452
|
+
return taskList ? json2({ task_list: taskList }) : error(404, "task list not found");
|
|
41453
|
+
}
|
|
41454
|
+
if (id && method === "DELETE") {
|
|
41455
|
+
const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
|
|
41456
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "task list not found");
|
|
41457
|
+
}
|
|
41458
|
+
return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
|
|
40895
41459
|
}
|
|
40896
41460
|
if (resource === "dependencies" && !id) {
|
|
40897
41461
|
if (method !== "GET")
|
|
@@ -40899,8 +41463,8 @@ async function handleV1Request(req, url) {
|
|
|
40899
41463
|
if (typeof store.dependencies?.listAll !== "function") {
|
|
40900
41464
|
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
40901
41465
|
}
|
|
40902
|
-
const
|
|
40903
|
-
return json2({ dependencies, count:
|
|
41466
|
+
const dependencies2 = await store.dependencies.listAll();
|
|
41467
|
+
return json2({ dependencies: dependencies2, count: dependencies2.length });
|
|
40904
41468
|
}
|
|
40905
41469
|
if (resource === "commits" && id) {
|
|
40906
41470
|
if (method !== "GET")
|
|
@@ -40957,12 +41521,16 @@ async function handleV1Request(req, url) {
|
|
|
40957
41521
|
}
|
|
40958
41522
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
40959
41523
|
} catch (e) {
|
|
41524
|
+
if (e instanceof LockError)
|
|
41525
|
+
return error(409, e.message, { code: LockError.code });
|
|
40960
41526
|
return error(500, e.message || "internal error");
|
|
40961
41527
|
}
|
|
40962
41528
|
}
|
|
40963
|
-
var JSON_HEADERS;
|
|
41529
|
+
var JSON_HEADERS, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
|
|
40964
41530
|
var init_v1 = __esm(() => {
|
|
41531
|
+
init_types();
|
|
40965
41532
|
init_cloud();
|
|
41533
|
+
init_redaction();
|
|
40966
41534
|
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
40967
41535
|
});
|
|
40968
41536
|
|
|
@@ -47197,7 +47765,7 @@ var init_workflow_states = __esm(() => {
|
|
|
47197
47765
|
init_tasks();
|
|
47198
47766
|
init_types();
|
|
47199
47767
|
init_database();
|
|
47200
|
-
|
|
47768
|
+
init_config();
|
|
47201
47769
|
init_local_fields();
|
|
47202
47770
|
DEFAULT_WORKFLOW_STATES = [
|
|
47203
47771
|
{ name: "pending", canonical_status: "pending", aliases: ["todo", "backlog"], transitions: null, terminal: false },
|
|
@@ -48362,7 +48930,7 @@ var init_roadmaps = __esm(() => {
|
|
|
48362
48930
|
init_tasks();
|
|
48363
48931
|
init_plans();
|
|
48364
48932
|
init_task_runs();
|
|
48365
|
-
|
|
48933
|
+
init_config();
|
|
48366
48934
|
});
|
|
48367
48935
|
|
|
48368
48936
|
// src/lib/capacity-forecasts.ts
|
|
@@ -48587,7 +49155,7 @@ var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
|
|
|
48587
49155
|
var init_capacity_forecasts = __esm(() => {
|
|
48588
49156
|
init_tasks();
|
|
48589
49157
|
init_task_relations();
|
|
48590
|
-
|
|
49158
|
+
init_config();
|
|
48591
49159
|
});
|
|
48592
49160
|
|
|
48593
49161
|
// src/lib/audit-ledger.ts
|
|
@@ -48879,7 +49447,7 @@ var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1, LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "
|
|
|
48879
49447
|
var init_audit_ledger = __esm(() => {
|
|
48880
49448
|
init_database();
|
|
48881
49449
|
init_task_runs();
|
|
48882
|
-
|
|
49450
|
+
init_config();
|
|
48883
49451
|
init_redaction();
|
|
48884
49452
|
LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
|
|
48885
49453
|
});
|
|
@@ -54760,7 +55328,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
54760
55328
|
init_database();
|
|
54761
55329
|
init_redaction();
|
|
54762
55330
|
init_runner_sandbox();
|
|
54763
|
-
|
|
55331
|
+
init_config();
|
|
54764
55332
|
});
|
|
54765
55333
|
|
|
54766
55334
|
// src/lib/verification-providers.ts
|
|
@@ -55105,7 +55673,7 @@ var init_verification_providers = __esm(() => {
|
|
|
55105
55673
|
init_task_commits();
|
|
55106
55674
|
init_database();
|
|
55107
55675
|
init_tasks();
|
|
55108
|
-
|
|
55676
|
+
init_config();
|
|
55109
55677
|
init_redaction();
|
|
55110
55678
|
DEFAULT_RETRY = {
|
|
55111
55679
|
attempts: 1,
|
|
@@ -61931,7 +62499,7 @@ var init_review_queues = __esm(() => {
|
|
|
61931
62499
|
init_audit();
|
|
61932
62500
|
init_database();
|
|
61933
62501
|
init_tasks();
|
|
61934
|
-
|
|
62502
|
+
init_config();
|
|
61935
62503
|
init_event_emission_safety();
|
|
61936
62504
|
init_event_hooks();
|
|
61937
62505
|
init_task_contracts();
|
|
@@ -63818,7 +64386,7 @@ ID: ${updated.id}${taskNote}`
|
|
|
63818
64386
|
var init_agents2 = __esm(() => {
|
|
63819
64387
|
init_zod();
|
|
63820
64388
|
init_agents();
|
|
63821
|
-
|
|
64389
|
+
init_config();
|
|
63822
64390
|
init_database();
|
|
63823
64391
|
init_cloud_router();
|
|
63824
64392
|
});
|
|
@@ -64252,7 +64820,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
64252
64820
|
}
|
|
64253
64821
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
64254
64822
|
const path = outputPath ? resolve20(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
64255
|
-
|
|
64823
|
+
ensureDir(dirname9(path));
|
|
64256
64824
|
writeJsonFile(path, snapshot);
|
|
64257
64825
|
return path;
|
|
64258
64826
|
}
|
|
@@ -67895,7 +68463,7 @@ Commit Links (${commitRows.length}):`));
|
|
|
67895
68463
|
var init_config_serve_commands = __esm(() => {
|
|
67896
68464
|
init_database();
|
|
67897
68465
|
init_tasks();
|
|
67898
|
-
|
|
68466
|
+
init_config();
|
|
67899
68467
|
init_sync_utils();
|
|
67900
68468
|
init_helpers();
|
|
67901
68469
|
});
|
|
@@ -70101,7 +70669,7 @@ Findings`));
|
|
|
70101
70669
|
checks.push({ name: "Migrations", ok: false, message: "Could not read migration version" });
|
|
70102
70670
|
}
|
|
70103
70671
|
try {
|
|
70104
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (
|
|
70672
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
70105
70673
|
loadConfig2();
|
|
70106
70674
|
checks.push({ name: "Config", ok: true, message: "Loaded successfully" });
|
|
70107
70675
|
} catch (e) {
|
|
@@ -79532,7 +80100,7 @@ var init_factory = __esm(() => {
|
|
|
79532
80100
|
init_postgres_adapter();
|
|
79533
80101
|
init_shadow();
|
|
79534
80102
|
init_cloud_client();
|
|
79535
|
-
|
|
80103
|
+
init_config2();
|
|
79536
80104
|
});
|
|
79537
80105
|
|
|
79538
80106
|
// src/storage/s3-artifacts.ts
|
|
@@ -79974,6 +80542,7 @@ __export(exports_storage, {
|
|
|
79974
80542
|
signAwsV4Request: () => signAwsV4Request,
|
|
79975
80543
|
registerShadowExitFlush: () => registerShadowExitFlush,
|
|
79976
80544
|
postgresTodosSyncSchemaSql: () => postgresTodosSyncSchemaSql,
|
|
80545
|
+
postgresTodosCommentCursorIndexSql: () => postgresTodosCommentCursorIndexSql,
|
|
79977
80546
|
planRunArtifactsS3Sync: () => planRunArtifactsS3Sync,
|
|
79978
80547
|
parseStorageMode: () => parseStorageMode,
|
|
79979
80548
|
maybeInstallShadowCapture: () => maybeInstallShadowCapture2,
|
|
@@ -79981,6 +80550,7 @@ __export(exports_storage, {
|
|
|
79981
80550
|
loadStorageConfig: () => loadStorageConfig,
|
|
79982
80551
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
79983
80552
|
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
80553
|
+
isCommentRedactionBackfillComplete: () => isCommentRedactionBackfillComplete,
|
|
79984
80554
|
installShadowOutboxSchema: () => installShadowOutboxSchema,
|
|
79985
80555
|
importSqliteTodosStorageSnapshot: () => importSqliteTodosStorageSnapshot,
|
|
79986
80556
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
@@ -80008,6 +80578,7 @@ __export(exports_storage, {
|
|
|
80008
80578
|
closeRuntimeShadowCloud: () => closeRuntimeShadowCloud,
|
|
80009
80579
|
buildS3ObjectUrl: () => buildS3ObjectUrl,
|
|
80010
80580
|
buildS3ObjectKey: () => buildS3ObjectKey,
|
|
80581
|
+
backfillPostgresCommentRedaction: () => backfillPostgresCommentRedaction,
|
|
80011
80582
|
assertTodosShadowConfig: () => assertTodosShadowConfig,
|
|
80012
80583
|
assertTodosRemoteStorageConfig: () => assertTodosRemoteStorageConfig,
|
|
80013
80584
|
TodosShadowOutbox: () => TodosShadowOutbox,
|
|
@@ -80020,12 +80591,13 @@ __export(exports_storage, {
|
|
|
80020
80591
|
PostgresTodosSyncStore: () => PostgresTodosSyncStore,
|
|
80021
80592
|
DEFAULT_TODOS_POSTGRES_SYNC_TABLE: () => DEFAULT_TODOS_POSTGRES_SYNC_TABLE,
|
|
80022
80593
|
DEFAULT_TODOS_POSTGRES_CURSOR_TABLE: () => DEFAULT_TODOS_POSTGRES_CURSOR_TABLE,
|
|
80594
|
+
COMMENT_REDACTION_BACKFILL_CONFIRMATION: () => COMMENT_REDACTION_BACKFILL_CONFIRMATION,
|
|
80023
80595
|
CANONICAL_TODOS_RDS_RUNTIME_PATH: () => CANONICAL_TODOS_RDS_RUNTIME_PATH,
|
|
80024
80596
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
80025
80597
|
CANONICAL_TODOS_RDS_CLUSTER: () => CANONICAL_TODOS_RDS_CLUSTER
|
|
80026
80598
|
});
|
|
80027
80599
|
var init_storage2 = __esm(() => {
|
|
80028
|
-
|
|
80600
|
+
init_config2();
|
|
80029
80601
|
init_factory();
|
|
80030
80602
|
init_shadow();
|
|
80031
80603
|
init_shadow_outbox();
|
|
@@ -80034,6 +80606,7 @@ var init_storage2 = __esm(() => {
|
|
|
80034
80606
|
init_hybrid();
|
|
80035
80607
|
init_local_sqlite();
|
|
80036
80608
|
init_sqlite_snapshot();
|
|
80609
|
+
init_comment_redaction_backfill();
|
|
80037
80610
|
init_postgres_adapter();
|
|
80038
80611
|
init_s3_artifacts();
|
|
80039
80612
|
init_s3_artifact_sync();
|