This commit is contained in:
thePR0M3TH3AN committed 2026-02-20 10:26:39 -05:00
1 parent cc1ba691cb
commit deadeaafac
73 files changed
+3085 -699

No files matched your search

+1
View File
@@ -0,0 +1 @@
torch/node_modules
+1
View File
@@ -0,0 +1 @@
MEMORY_STORED
+376 -1
View File
@@ -17,7 +17,7 @@
"importance": 0.4, "importance": 0.4,
"embedding_id": null, "embedding_id": null,
"created_at": 1771558399336, "created_at": 1771558399336,
"last_seen": 1771558399343, "last_seen": 1771596755883,
"source": "scheduler-retrieve", "source": "scheduler-retrieve",
"ttl_days": null, "ttl_days": null,
"merged_into": null, "merged_into": null,
@@ -48,5 +48,380 @@
"merged_into": null, "merged_into": null,
"pinned": false "pinned": false
} }
],
[
"7b35c3e0-e01a-4015-b27e-dfbe8c43ef7a",
{
"schema_version": 1,
"id": "7b35c3e0-e01a-4015-b27e-dfbe8c43ef7a",
"agent_id": "scheduler-memory-daily",
"session_id": "session-mluyytgn",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771596755879,
"last_seen": 1771596755883,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"fbc6aad6-ae89-4bf2-8c67-0053cb5c67d9",
{
"schema_version": 1,
"id": "fbc6aad6-ae89-4bf2-8c67-0053cb5c67d9",
"agent_id": "scheduler-memory-weekly",
"session_id": "session-mluyztmu",
"type": "batch_window",
"content": "Store memory event A for weekly :: scheduler memory store\nStore memory event B for weekly :: scheduler memory store",
"summary": "Store memory event A for weekly :: scheduler memory store Store memory event B for weekly :: scheduler memory store",
"tags": [
"scheduler",
"weekly",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771596802758,
"last_seen": 1771596802759,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"05791126-ee9b-4c8e-913d-30a6c930b5bf",
{
"schema_version": 1,
"id": "05791126-ee9b-4c8e-913d-30a6c930b5bf",
"agent_id": "style-agent",
"session_id": "session-mluz1yd4",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771596902200,
"last_seen": 1771596902201,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"b8705dfd-20f0-42a2-af23-de692718013d",
{
"schema_version": 1,
"id": "b8705dfd-20f0-42a2-af23-de692718013d",
"agent_id": "test-audit-agent",
"session_id": "session-mluzff2k",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771597530380,
"last_seen": 1771597530385,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"12378812-8347-4458-a373-12c3abe905e4",
{
"schema_version": 1,
"id": "12378812-8347-4458-a373-12c3abe905e4",
"agent_id": "test-audit-agent",
"session_id": "session-mluzfucz",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771597550195,
"last_seen": 1771597550196,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"c1b3098d-fc75-4e2f-a087-718806d32c7d",
{
"schema_version": 1,
"id": "c1b3098d-fc75-4e2f-a087-718806d32c7d",
"agent_id": "todo-triage-agent",
"session_id": "session-mluzkanb",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771597757927,
"last_seen": 1771597757937,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"2c83e058-51fc-42fe-a68c-06b08a9a8e29",
{
"schema_version": 1,
"id": "2c83e058-51fc-42fe-a68c-06b08a9a8e29",
"agent_id": "todo-triage-agent",
"session_id": "session-mluzkknh",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771597770893,
"last_seen": 1771597770894,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"69ba2c33-e762-4d4b-9143-3b9bcb45412f",
{
"schema_version": 1,
"id": "69ba2c33-e762-4d4b-9143-3b9bcb45412f",
"agent_id": "torch-garbage-collection-agent",
"session_id": "session-mluzsu17",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771598156299,
"last_seen": 1771598156305,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"3f6fef8d-b01f-45d4-925c-ad692059bd35",
{
"schema_version": 1,
"id": "3f6fef8d-b01f-45d4-925c-ad692059bd35",
"agent_id": "torch-garbage-collection-agent",
"session_id": "session-mluzt2t2",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771598167670,
"last_seen": 1771598167671,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"a047de7f-bbeb-40d0-ac23-f5a43753f664",
{
"schema_version": 1,
"id": "a047de7f-bbeb-40d0-ac23-f5a43753f664",
"agent_id": "audit-agent",
"session_id": "session-mlv03xay",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771598673754,
"last_seen": 1771598673758,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"02b1409a-fd8f-478e-816c-bbd985e106d8",
{
"schema_version": 1,
"id": "02b1409a-fd8f-478e-816c-bbd985e106d8",
"agent_id": "audit-agent",
"session_id": "session-mlv0498w",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771598689232,
"last_seen": 1771598689233,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"0da3cadf-a44a-4055-8e3c-838b924a2f48",
{
"schema_version": 1,
"id": "0da3cadf-a44a-4055-8e3c-838b924a2f48",
"agent_id": "ci-health-agent",
"session_id": "session-mlv16ngi",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771600480578,
"last_seen": 1771600480583,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"24c22a90-aa05-4b71-b525-2274313658b5",
{
"schema_version": 1,
"id": "24c22a90-aa05-4b71-b525-2274313658b5",
"agent_id": "ci-health-agent",
"session_id": "session-mlv16wbs",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771600492072,
"last_seen": 1771600492073,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"e4405f5d-ff1f-481b-9981-b5f78d344860",
{
"schema_version": 1,
"id": "e4405f5d-ff1f-481b-9981-b5f78d344860",
"agent_id": "const-refactor-agent",
"session_id": "session-mlv1h4b5",
"type": "batch_window",
"content": "Memory retrieval seed for daily :: scheduler memory retrieval",
"summary": "Memory retrieval seed for daily :: scheduler memory retrieval",
"tags": [
"scheduler",
"daily",
"retrieve"
],
"importance": 0.4,
"embedding_id": null,
"created_at": 1771600968977,
"last_seen": 1771600968992,
"source": "scheduler-retrieve",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
],
[
"fcbf743f-3818-4502-99ec-273e691f0840",
{
"schema_version": 1,
"id": "fcbf743f-3818-4502-99ec-273e691f0840",
"agent_id": "const-refactor-agent",
"session_id": "session-mlv1hbp7",
"type": "batch_window",
"content": "Store memory event A for daily :: scheduler memory store\nStore memory event B for daily :: scheduler memory store",
"summary": "Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store",
"tags": [
"scheduler",
"daily",
"store"
],
"importance": 0.55,
"embedding_id": null,
"created_at": 1771600978555,
"last_seen": 1771600978556,
"source": "scheduler-store",
"ttl_days": null,
"merged_into": null,
"pinned": false
}
] ]
] ]
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mluyytgn",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "scheduler-memory-daily",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | scheduler-memory-daily | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 2
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "weekly",
"operation": "store",
"runId": "session-mluyztmu",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "scheduler-memory-weekly",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for weekly :: scheduler memory store Store memory event B for weekly :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mluz1yd4",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "style-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mluzff2k",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "test-audit-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | test-audit-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mluzfucz",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "test-audit-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mluzkanb",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "todo-triage-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | todo-triage-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mluzkknh",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "todo-triage-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mluzsu17",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "torch-garbage-collection-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | torch-garbage-collection-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mluzt2t2",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "torch-garbage-collection-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mlv03xay",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "audit-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | audit-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mlv0498w",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "audit-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mlv16ngi",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "ci-health-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | ci-health-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mlv16wbs",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "ci-health-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,18 @@
{
"cadence": "daily",
"operation": "retrieve",
"runId": "session-mlv1h4b5",
"servicePath": "src/services/memory/index.js#getRelevantMemories",
"inputs": {
"agentId": "const-refactor-agent",
"promptPath": "",
"promptIntent": "scheduler memory retrieval",
"query": "scheduler | daily | agent | const-refactor-agent | intent | scheduler memory retrieval",
"events": 1
},
"outputs": {
"ingestedCount": 1,
"retrievedCount": 1
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_RETRIEVED
@@ -0,0 +1,20 @@
{
"cadence": "daily",
"operation": "store",
"runId": "session-mlv1hbp7",
"servicePath": "src/services/memory/index.js#ingestEvents",
"inputs": {
"agentId": "const-refactor-agent",
"promptPath": "",
"promptIntent": "scheduler memory store",
"events": 2,
"sourceFile": "fallback"
},
"outputs": {
"storedCount": 1,
"summaries": [
"Store memory event A for daily :: scheduler memory store Store memory event B for daily :: scheduler memory store"
]
},
"status": "ok"
}
@@ -0,0 +1 @@
MEMORY_STORED
@@ -0,0 +1,70 @@
---
agent: style-agent
cadence: daily
platform: qwen-code
status: failed
failure_category: execution_error
timestamp: 2026-02-20T14:16:47Z
lock_event_id: c8e8381ce5bf9cb55b51a81f0e14cafc2ce6b080424af43b6e51190bab8306f3
---
# style-agent Daily Run — Failed
## Failure Summary
**Validation command failed:** `npm run lint` (in torch/ subdirectory)
**Exit code:** 2
**Reason:** ESLint configuration file missing. The lint command exited with a configuration error, not a code quality issue.
## Error Details
```
> [email protected] lint
> eslint .
Oops! Something went wrong! :(
ESLint: 10.0.0
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
```
## Context
- **Agent selected:** style-agent (via roster round-robin after scheduler-update-agent)
- **Excluded agents:** scheduler-update-agent (locked by same agent in previous run)
- **Lock acquired:** Yes (event ID: c8e8381ce5bf9cb55b51a81f0e14cafc2ce6b080424af43b6e51190bab8306f3)
- **Memory retrieval:** Completed successfully (MEMORY_RETRIEVED)
- **Memory storage:** Completed successfully (MEMORY_STORED)
- **Validation failure:** Repository lint infrastructure not configured (missing eslint.config.js)
## Agent Execution Notes
The style-agent prompt requires `format` and `lint` scripts in package.json. The root package.json does not define these scripts. The torch/ subdirectory has a lint script but lacks the required ESLint v9+ configuration file.
Per style-agent.md "Failure modes" section:
> - Missing/undefined scripts in `package.json`.
> Open an issue including: exact failing command(s), error output snippet, files involved, recommended next step.
## Next Steps / Retry Guidance
1. **Add ESLint configuration:** Create `torch/eslint.config.js` or `torch/eslint.config.mjs` with ESLint v9+ flat config format.
2. **Alternative:** If linting is not required for this repository, remove the lint validation step from the scheduler cycle or mark it as optional.
3. **For style-agent work:** The root repository would need `format` and `lint` scripts defined in the root package.json for the style-agent to execute its workflow.
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage)
- Step 9 (validation): **Failed** — lint exited non-zero
- Step 10 (lock:complete): **Skipped** — per scheduler-flow.md, completion publish is forbidden when validation fails
- Step 11: Writing _failed.md (this file)
---
*This failure log was written by the daily scheduler. The lock remains active until TTL expiration.*
@@ -0,0 +1,69 @@
---
agent: test-audit-agent
cadence: daily
platform: qwen-code
status: failed
failure_category: execution_error
timestamp: 2026-02-20T14:26:16Z
lock_event_id: 255d7212a7dad5f8fe626f8ded2017d9b3c5d48d578ed69ecea3e20a72b6ba49
---
# test-audit-agent Daily Run — Failed
## Failure Summary
**Validation command failed:** `npm run lint` (in torch/ subdirectory)
**Exit code:** 2
**Reason:** ESLint configuration file missing. The lint command exited with a configuration error, not a code quality issue.
## Error Details
```
> [email protected] lint
> eslint .
Oops! Something went wrong! :(
ESLint: 10.0.0
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
```
## Context
- **Agent selected:** test-audit-agent (via roster round-robin after style-agent)
- **Excluded agents:** scheduler-update-agent, style-agent (both locked)
- **Lock acquired:** Yes (event ID: 255d7212a7dad5f8fe626f8ded2017d9b3c5d48d578ed69ecea3e20a72b6ba49)
- **Memory retrieval:** Completed successfully (MEMORY_RETRIEVED)
- **Memory storage:** Completed successfully (MEMORY_STORED)
- **Agent work:** No test files found in repository; agent exited without changes per prompt guidance
- **Validation failure:** Repository lint infrastructure not configured (missing eslint.config.js)
## Agent Execution Notes
The test-audit-agent prompt states: "If no work is required, exit without making changes."
Repository inspection found:
- No test files in root repository (package.json test script echoes error)
- Test audit tools exist in torch/scripts/test-audit/ but no tests to audit
## Next Steps / Retry Guidance
1. **Add ESLint configuration:** Create `torch/eslint.config.js` or `torch/eslint.config.mjs` with ESLint v9+ flat config format.
2. **Alternative:** If linting is not required for this repository, remove the lint validation step from the scheduler cycle or mark it as optional.
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Failed** — lint exited non-zero
- Step 10 (lock:complete): **Skipped** — per scheduler-flow.md, completion publish is forbidden when validation fails
- Step 11: Writing _failed.md (this file)
---
*This failure log was written by the daily scheduler. The lock remains active until TTL expiration.*
@@ -0,0 +1,68 @@
---
agent: todo-triage-agent
cadence: daily
platform: qwen-code
status: failed
failure_category: execution_error
timestamp: 2026-02-20T14:29:58Z
lock_event_id: 18c93e4a5476427bae5d77e3944ca4a9fc8bf504b0bee2890a37422c2afdded5
---
# todo-triage-agent Daily Run — Failed
## Failure Summary
**Validation command failed:** `npm run lint` (in torch/ subdirectory)
**Exit code:** 2
**Reason:** ESLint configuration file missing. The lint command exited with a configuration error, not a code quality issue.
## Error Details
```
> [email protected] lint
> eslint .
Oops! Something went wrong! :(
ESLint: 10.0.0
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
```
## Context
- **Agent selected:** todo-triage-agent (via roster round-robin after test-audit-agent)
- **Excluded agents:** scheduler-update-agent, style-agent, test-audit-agent (all locked)
- **Lock acquired:** Yes (event ID: 18c93e4a5476427bae5d77e3944ca4a9fc8bf504b0bee2890a37422c2afdded5)
- **Memory retrieval:** Completed successfully (MEMORY_RETRIEVED)
- **Memory storage:** Completed successfully (MEMORY_STORED)
- **Agent work:** No actionable TODOs found in repository (only 1 config prefix marker)
- **Validation failure:** Repository lint infrastructure not configured (missing eslint.config.js)
## Agent Execution Notes
The todo-triage-agent scanned for TODO/FIXME/XXX markers:
- Found 1 match: `torch/scripts/agent/verify-run-artifacts.mjs:15` (config prefix `expectedPrefix: 'TODO_'`, not an actual TODO)
- No actionable TODOs requiring triage
- Per prompt guidance: "If no work is required, exit without making changes."
## Next Steps / Retry Guidance
1. **Add ESLint configuration:** Create `torch/eslint.config.js` or `torch/eslint.config.mjs` with ESLint v9+ flat config format.
2. **Alternative:** If linting is not required for this repository, remove the lint validation step from the scheduler cycle or mark it as optional.
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Failed** — lint exited non-zero
- Step 10 (lock:complete): **Skipped** — per scheduler-flow.md, completion publish is forbidden when validation fails
- Step 11: Writing _failed.md (this file)
---
*This failure log was written by the daily scheduler. The lock remains active until TTL expiration.*
@@ -0,0 +1,68 @@
---
agent: torch-garbage-collection-agent
cadence: daily
platform: qwen-code
status: failed
failure_category: execution_error
timestamp: 2026-02-20T14:36:33Z
lock_event_id: cfcb929b090e97e9b96544869639f2a0787f1bb131b2c4d402c707d30d815c9e
---
# torch-garbage-collection-agent Daily Run — Failed
## Failure Summary
**Validation command failed:** `npm run lint` (in torch/ subdirectory)
**Exit code:** 2
**Reason:** ESLint configuration file missing. The lint command exited with a configuration error, not a code quality issue.
## Error Details
```
> [email protected] lint
> eslint .
Oops! Something went wrong! :(
ESLint: 10.0.0
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
```
## Context
- **Agent selected:** torch-garbage-collection-agent (via roster round-robin after todo-triage-agent)
- **Excluded agents:** scheduler-update-agent, style-agent, test-audit-agent, todo-triage-agent (all locked)
- **Lock acquired:** Yes (event ID: cfcb929b090e97e9b96544869639f2a0787f1bb131b2c4d402c707d30d815c9e)
- **Memory retrieval:** Completed successfully (MEMORY_RETRIEVED)
- **Memory storage:** Completed successfully (MEMORY_STORED)
- **Agent work:** No stale log files found (files older than 14 days); no changes needed per prompt
- **Validation failure:** Repository lint infrastructure not configured (missing eslint.config.js)
## Agent Execution Notes
The torch-garbage-collection-agent scanned for stale files:
- Searched for: `*.log`, `*.log.*`, `*.out.log`, `./memory-updates/*.md` with mtime > 14 days
- Result: No stale files found
- Per prompt guidance: "If list is empty: report 'No stale files found' and stop."
## Next Steps / Retry Guidance
1. **Add ESLint configuration:** Create `torch/eslint.config.js` or `torch/eslint.config.mjs` with ESLint v9+ flat config format.
2. **Alternative:** If linting is not required for this repository, remove the lint validation step from the scheduler cycle or mark it as optional.
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Failed** — lint exited non-zero
- Step 10 (lock:complete): **Skipped** — per scheduler-flow.md, completion publish is forbidden when validation fails
- Step 11: Writing _failed.md (this file)
---
*This failure log was written by the daily scheduler. The lock remains active until TTL expiration.*
@@ -0,0 +1,71 @@
---
agent: audit-agent
cadence: daily
platform: qwen-code
status: failed
failure_category: execution_error
timestamp: 2026-02-20T14:45:13Z
lock_event_id: 4e4a12b34be821831fe64fe440f39238cff582cca743a7479fbfcf854987b190
---
# audit-agent Daily Run — Failed
## Failure Summary
**Validation command failed:** `npm run lint` (in torch/ subdirectory)
**Exit code:** 2
**Reason:** ESLint configuration file missing. The lint command exited with a configuration error, not a code quality issue.
## Error Details
```
> [email protected] lint
> eslint .
Oops! Something went wrong! :(
ESLint: 10.0.0
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
```
## Context
- **Agent selected:** audit-agent (via roster round-robin after torch-garbage-collection-agent, wrapping to index 0)
- **Excluded agents:** scheduler-update-agent, style-agent, test-audit-agent, todo-triage-agent, torch-garbage-collection-agent (all locked)
- **Lock acquired:** Yes (event ID: 4e4a12b34be821831fe64fe440f39238cff582cca743a7479fbfcf854987b190)
- **Memory retrieval:** Completed successfully (MEMORY_RETRIEVED)
- **Memory storage:** Completed successfully (MEMORY_STORED)
- **Agent work:** Audit scripts not found in repository (`scripts/check-file-size.mjs`, `scripts/check-innerhtml.mjs` missing); skipped per prompt guidance
- **Validation failure:** Repository lint infrastructure not configured (missing eslint.config.js)
## Agent Execution Notes
The audit-agent requires the following scripts which do not exist in this repository:
- `scripts/check-file-size.mjs` — NOT FOUND
- `scripts/check-innerhtml.mjs` — NOT FOUND
Per audit-agent.md failure modes: "If specific resources (files, URLs) are unavailable, log the error and skip."
## Next Steps / Retry Guidance
1. **Add ESLint configuration:** Create `torch/eslint.config.js` or `torch/eslint.config.mjs` with ESLint v9+ flat config format.
2. **Alternative:** If linting is not required for this repository, remove the lint validation step from the scheduler cycle or mark it as optional.
3. **For audit-agent work:** The repository would need the audit scripts (`scripts/check-file-size.mjs`, `scripts/check-innerhtml.mjs`) to be added for the agent to execute its workflow.
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Failed** — lint exited non-zero
- Step 10 (lock:complete): **Skipped** — per scheduler-flow.md, completion publish is forbidden when validation fails
- Step 11: Writing _failed.md (this file)
---
*This failure log was written by the daily scheduler. The lock remains active until TTL expiration.*
@@ -0,0 +1,57 @@
---
agent: ci-health-agent
cadence: daily
platform: qwen-code
status: completed
timestamp: 2026-02-20T15:15:44Z
lock_event_id: 536172682a928268c1be9adf11f751cd67c1f185fb2b8f6d2e4e356d8ec7b073
completion_event_id: ceefcfbab98f06ee008de1bda721af63fa4084ef40845f6f746a2cee6f569973
---
# ci-health-agent Daily Run — Completed
## Summary
**Status:** Success
**Agent:** ci-health-agent
**Prompt:** torch/src/prompts/daily/ci-health-agent.md
## Execution Summary
### Agent Work
The ci-health-agent inspected the repository for CI health issues:
1. **CI Workflows:** No `.github/workflows/` directory found — CI not yet configured
2. **Unit Tests:** All tests pass (`npm run test:unit:lock-backend` — 4/4 tests passing)
3. **Lint:** All checks pass (`npm run lint` — 0 errors)
**Conclusion:** No flaky tests or CI issues identified. Repository is in good health. No changes required per prompt guidance: "If no work is required, exit without making changes."
### Memory Workflow
- **Retrieval:** Completed successfully (`MEMORY_RETRIEVED`)
- **Storage:** Completed successfully (`MEMORY_STORED`)
- **Evidence:** retrieve.ok ✓, store.ok ✓
### Validation
- **Lint:** Passed (0 errors)
- **Completion Publish:** Successful (event ID: `ceefcfbab98f06ee008de1bda721af63fa4084ef40845f6f746a2cee6f569973`)
## Learnings
No learnings recorded (memory storage ran with fallback placeholder).
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Passed** — lint exited 0
- Step 10 (lock:complete): **Completed** — published successfully
- Step 11: Writing _completed.md (this file)
---
*This completion log was written by the daily scheduler.*
@@ -0,0 +1,66 @@
---
agent: const-refactor-agent
cadence: daily
platform: qwen-code
status: completed
timestamp: 2026-02-20T15:23:32Z
lock_event_id: 3327522086c83e33b527acca300fdb183a73640ed29329f8f10cfbc8e1432366
completion_event_id: 30e5f8e7722e77e6e9d63b1b6d21a8e48814b73bf00382781ffa5a8079e6062b
---
# const-refactor-agent Daily Run — Completed
## Summary
**Status:** Success
**Agent:** const-refactor-agent
**Prompt:** torch/src/prompts/daily/const-refactor-agent.md
## Execution Summary
### Agent Work
The const-refactor-agent inspected the repository for duplicate numeric constants:
1. **Searched for numeric literals** in `src/**/*.mjs` files
2. **Reviewed existing constants** in `src/constants.mjs`:
- `DEFAULT_TTL = 7200`
- `DEFAULT_QUERY_TIMEOUT_MS = 30_000`
- `DEFAULT_PUBLISH_TIMEOUT_MS = 15_000`
- `DEFAULT_RETRY_ATTEMPTS = 4`
- `DEFAULT_RETRY_BASE_DELAY_MS = 500`
- `DEFAULT_QUARANTINE_COOLDOWN_MS = 30_000`
- `DEFAULT_SNAPSHOT_INTERVAL_MS = 60_000`
- And many more well-named constants
**Conclusion:** The codebase already follows best practices for constants. All numeric values are properly extracted to `src/constants.mjs` with semantic naming (e.g., `DEFAULT_QUERY_TIMEOUT_MS`, `RACE_CHECK_DELAY_MS`). No duplicate numeric literals found in source code that need refactoring.
Per prompt guidance: "If no work is required, exit without making changes."
### Memory Workflow
- **Retrieval:** Completed successfully (`MEMORY_RETRIEVED`)
- **Storage:** Completed successfully (`MEMORY_STORED`)
- **Evidence:** retrieve.ok ✓, store.ok ✓
### Validation
- **Lint:** Passed (0 errors)
- **Completion Publish:** Successful (event ID: `30e5f8e7722e77e6e9d63b1b6d21a8e48814b73bf00382781ffa5a8079e6062b`)
## Learnings
No learnings recorded (memory storage ran with fallback placeholder).
## Scheduler Flow Compliance
- Step 1-8: Completed (preflight, lock acquisition, memory retrieval/storage, evidence validation)
- Step 9 (validation): **Passed** — lint exited 0
- Step 10 (lock:complete): **Completed** — published successfully
- Step 11: Writing _completed.md (this file)
---
*This completion log was written by the daily scheduler.*
+55
View File
@@ -0,0 +1,55 @@
import js from '@eslint/js';
import globals from 'globals';
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
sourceType: 'module',
globals: {
...globals.node,
...globals.es2024,
},
},
rules: {
// Basic code quality
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'no-console': 'off',
'no-debugger': 'warn',
// Style preferences
'indent': ['error', 2],
'quotes': ['error', 'single', { avoidEscape: true }],
'semi': ['error', 'always'],
'comma-dangle': ['error', 'always-multiline'],
'no-trailing-spaces': 'error',
'eol-last': 'error',
// Best practices
'eqeqeq': ['error', 'always'],
'no-var': 'error',
'prefer-const': 'error',
'no-implied-eval': 'error',
'no-new-func': 'error',
'no-return-await': 'error',
// Error handling
'no-throw-literal': 'error',
'prefer-promise-reject-errors': 'error',
},
},
{
// Ignore patterns
ignores: [
'node_modules/**',
'dist/**',
'build/**',
'coverage/**',
'.scheduler-memory/**',
'task-logs/**',
'dashboard/**',
'landing/**',
],
},
];
+1036
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -46,9 +46,9 @@ const LOG_FILE = 'artifacts/debug_frontend.log';
}); });
try { try {
log(`Navigating to http://localhost:4173/dashboard/...`); log('Navigating to http://localhost:4173/dashboard/...');
await page.goto('http://localhost:4173/dashboard/', { waitUntil: 'networkidle', timeout: 10000 }); await page.goto('http://localhost:4173/dashboard/', { waitUntil: 'networkidle', timeout: 10000 });
log(`Navigation complete.`); log('Navigation complete.');
// Give it a bit more time for any delayed errors // Give it a bit more time for any delayed errors
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
} catch (e) { } catch (e) {
@@ -64,7 +64,7 @@ const LOG_FILE = 'artifacts/debug_frontend.log';
console.error(`\nFound ${errors.length} errors.`); console.error(`\nFound ${errors.length} errors.`);
process.exit(1); process.exit(1);
} else { } else {
console.log(`\nNo errors found.`); console.log('\nNo errors found.');
process.exit(0); process.exit(0);
} }
})(); })();
+1 -1
View File
@@ -91,7 +91,7 @@ function generateEvent() {
pubkey: randomString(64), // Mock Pubkey pubkey: randomString(64), // Mock Pubkey
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
tags: tags, tags: tags,
content: content content: content,
}; };
} }
+31 -31
View File
@@ -40,7 +40,7 @@ const stats = {
latencyMax: 0, latencyMax: 0,
errors: {}, errors: {},
startTime: Date.now(), startTime: Date.now(),
endTime: 0 endTime: 0,
}; };
async function sleep(ms) { async function sleep(ms) {
@@ -103,35 +103,35 @@ async function main() {
return new Promise((resolve) => { return new Promise((resolve) => {
const timer = setInterval(async () => { const timer = setInterval(async () => {
if (Date.now() >= end) { if (Date.now() >= end) {
clearInterval(timer); clearInterval(timer);
pool.close([RELAY_URL]); pool.close([RELAY_URL]);
stats.endTime = Date.now(); stats.endTime = Date.now();
writeReport(reportDir); writeReport(reportDir);
resolve(); resolve();
return; return;
} }
const clientIdx = Math.floor(Math.random() * CLIENTS); const clientIdx = Math.floor(Math.random() * CLIENTS);
const sk = sks[clientIdx]; const sk = sks[clientIdx];
const event = generateEvent(sk); const event = generateEvent(sk);
stats.totalAttempts++; stats.totalAttempts++;
const sendStart = Date.now(); const sendStart = Date.now();
try { try {
await Promise.any(pool.publish([RELAY_URL], event)); await Promise.any(pool.publish([RELAY_URL], event));
const latency = Date.now() - sendStart; const latency = Date.now() - sendStart;
stats.success++; stats.success++;
stats.latencySum += latency; stats.latencySum += latency;
stats.latencyMin = Math.min(stats.latencyMin, latency); stats.latencyMin = Math.min(stats.latencyMin, latency);
stats.latencyMax = Math.max(stats.latencyMax, latency); stats.latencyMax = Math.max(stats.latencyMax, latency);
} catch (err) { } catch (err) {
stats.failed++; stats.failed++;
const msg = err.message || String(err); const msg = err.message || String(err);
stats.errors[msg] = (stats.errors[msg] || 0) + 1; stats.errors[msg] = (stats.errors[msg] || 0) + 1;
} }
}, interval); }, interval);
}); });
} }
@@ -147,7 +147,7 @@ function writeReport(dir) {
durationSec: DURATION_SEC, durationSec: DURATION_SEC,
rateEps: RATE_EPS, rateEps: RATE_EPS,
mix: MIX, mix: MIX,
dryRun: DRY_RUN dryRun: DRY_RUN,
}, },
results: { results: {
totalAttempts: stats.totalAttempts, totalAttempts: stats.totalAttempts,
@@ -157,11 +157,11 @@ function writeReport(dir) {
latency: { latency: {
min: stats.latencyMin === Infinity ? 0 : stats.latencyMin, min: stats.latencyMin === Infinity ? 0 : stats.latencyMin,
max: stats.latencyMax, max: stats.latencyMax,
avg: avgLatency avg: avgLatency,
}, },
errors: stats.errors errors: stats.errors,
}, },
timestamp: new Date().toISOString() timestamp: new Date().toISOString(),
}; };
const dateStr = new Date().toISOString().slice(0, 10); const dateStr = new Date().toISOString().slice(0, 10);
+4 -4
View File
@@ -435,10 +435,10 @@ async function main() {
const excluded = Array.isArray(checkPayload.excluded) const excluded = Array.isArray(checkPayload.excluded)
? checkPayload.excluded ? checkPayload.excluded
: [...new Set([ : [...new Set([
...(Array.isArray(checkPayload.locked) ? checkPayload.locked : []), ...(Array.isArray(checkPayload.locked) ? checkPayload.locked : []),
...(Array.isArray(checkPayload.paused) ? checkPayload.paused : []), ...(Array.isArray(checkPayload.paused) ? checkPayload.paused : []),
...(Array.isArray(checkPayload.completed) ? checkPayload.completed : []), ...(Array.isArray(checkPayload.completed) ? checkPayload.completed : []),
])]; ])];
const excludedSet = new Set(excluded); const excludedSet = new Set(excluded);
const latestFile = await getLatestFile(logDir); const latestFile = await getLatestFile(logDir);
+1 -1
View File
@@ -26,7 +26,7 @@ function resolveRunner(promptMarkdown) {
} }
if (platform === 'linux') { if (platform === 'linux') {
return { command: 'echo', args: [`[scheduler] Platform 'linux' detected. simulated execution for prompt.`] }; return { command: 'echo', args: ['[scheduler] Platform \'linux\' detected. simulated execution for prompt.'] };
} }
fail(`Unsupported AGENT_PLATFORM="${platform}". Set SCHEDULER_AGENT_RUNNER_COMMAND to override.`); fail(`Unsupported AGENT_PLATFORM="${platform}". Set SCHEDULER_AGENT_RUNNER_COMMAND to override.`);
+3 -3
View File
@@ -42,7 +42,7 @@ async function waitForServer(url, timeoutMs = 10000) {
try { try {
const res = await fetch(url); const res = await fetch(url);
if (res.ok) return true; if (res.ok) return true;
} catch (_e) { } catch {
// ignore // ignore
} }
await sleep(500); await sleep(500);
@@ -63,7 +63,7 @@ async function main() {
const results = { const results = {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
steps: [], steps: [],
success: false success: false,
}; };
let serverProcess = null; let serverProcess = null;
@@ -133,7 +133,7 @@ async function main() {
}, },
oneose() { oneose() {
sub.close(); sub.close();
} },
}); });
// Wait for event // Wait for event
+2 -2
View File
@@ -109,7 +109,7 @@ async function listRecentFiles(dirPath, sinceMs) {
if (!entry.isFile()) continue; if (!entry.isFile()) continue;
const filePath = path.join(dirPath, entry.name); const filePath = path.join(dirPath, entry.name);
const stat = await fs.stat(filePath); const stat = await fs.stat(filePath);
if (sinceMs != null && stat.mtimeMs < sinceMs) continue; if (sinceMs !== null && stat.mtimeMs < sinceMs) continue;
files.push({ name: entry.name, filePath, mtimeMs: stat.mtimeMs }); files.push({ name: entry.name, filePath, mtimeMs: stat.mtimeMs });
} }
return files.sort((a, b) => a.mtimeMs - b.mtimeMs); return files.sort((a, b) => a.mtimeMs - b.mtimeMs);
@@ -305,7 +305,7 @@ async function main() {
const activePromptPath = args.promptPath || process.env.SCHEDULER_PROMPT_PATH || null; const activePromptPath = args.promptPath || process.env.SCHEDULER_PROMPT_PATH || null;
const activeRunStart = args.runStart || null; const activeRunStart = args.runStart || null;
if (args.since && sinceMs == null) { if (args.since && sinceMs === null) {
console.error(`Invalid --since value: ${args.since}`); console.error(`Invalid --since value: ${args.since}`);
process.exit(2); process.exit(2);
} }
+11 -11
View File
@@ -22,7 +22,7 @@ async function waitForServer() {
req.end(); req.end();
}); });
return; return;
} catch (_e) { } catch {
await sleep(100); await sleep(100);
} }
} }
@@ -43,7 +43,7 @@ function makeRequest() {
async function benchmark() { async function benchmark() {
console.log('Starting server...'); console.log('Starting server...');
const serverProcess = spawn('node', ['bin/torch-lock.mjs', 'dashboard', '--port', String(PORT)], { const serverProcess = spawn('node', ['bin/torch-lock.mjs', 'dashboard', '--port', String(PORT)], {
stdio: 'ignore' // Suppress logs for cleaner output stdio: 'ignore', // Suppress logs for cleaner output
}); });
try { try {
@@ -54,20 +54,20 @@ async function benchmark() {
let sent = 0; let sent = 0;
const worker = async () => { const worker = async () => {
while (true) { while (true) {
const current = sent++; const current = sent++;
if (current >= REQUESTS) break; if (current >= REQUESTS) break;
try { try {
await makeRequest(); await makeRequest();
} catch (_e) { } catch {
// console.error(_e); // console.error(_e);
}
} }
}
}; };
const workers = []; const workers = [];
for (let i = 0; i < CONCURRENCY; i++) { for (let i = 0; i < CONCURRENCY; i++) {
workers.push(worker()); workers.push(worker());
} }
await Promise.all(workers); await Promise.all(workers);
+1 -1
View File
@@ -121,7 +121,7 @@ async function runBenchmark() {
const condensedCount = plan.condensedGroups.length; const condensedCount = plan.condensedGroups.length;
// We expect exactly GROUPS condensed groups. // We expect exactly GROUPS condensed groups.
if (condensedCount !== GROUPS) { if (condensedCount !== GROUPS) {
console.warn(`Warning: Expected ${GROUPS} condensed groups, but got ${condensedCount}. Check grouping logic.`); console.warn(`Warning: Expected ${GROUPS} condensed groups, but got ${condensedCount}. Check grouping logic.`);
} }
times.push(duration); times.push(duration);
+1 -1
View File
@@ -74,7 +74,7 @@ Options:
continue; continue;
} }
if (['.js', '.mjs', '.ts', '.html', '.css', '.json', '.md'].includes(path.extname(item))) { if (['.js', '.mjs', '.ts', '.html', '.css', '.json', '.md'].includes(path.extname(item))) {
files.push(fullPath); files.push(fullPath);
} }
} }
} }
+2 -2
View File
@@ -69,14 +69,14 @@ Options:
if (stat.isDirectory()) { if (stat.isDirectory()) {
if (!EXCLUDED_DIRS.includes(item)) { if (!EXCLUDED_DIRS.includes(item)) {
files = files.concat(scanDirectory(fullPath)); files = files.concat(scanDirectory(fullPath));
} }
} else if (stat.isFile()) { } else if (stat.isFile()) {
if (EXCLUDED_FILES.includes(item)) { if (EXCLUDED_FILES.includes(item)) {
continue; continue;
} }
if (TARGET_EXTENSIONS.includes(path.extname(item))) { if (TARGET_EXTENSIONS.includes(path.extname(item))) {
files.push(fullPath); files.push(fullPath);
} }
} }
} }
+58 -58
View File
@@ -13,20 +13,20 @@ const runId = process.env.SCHEDULER_RUN_ID ||
`session-${Date.now().toString(36)}`; `session-${Date.now().toString(36)}`;
// Extract prompt intent // Extract prompt intent
let promptIntent = `scheduler memory retrieval`; let promptIntent = 'scheduler memory retrieval';
if (promptPath) { if (promptPath) {
try { try {
const promptRaw = readFileSync(promptPath, 'utf8'); const promptRaw = readFileSync(promptPath, 'utf8');
const promptLines = promptRaw.split(/\r?\n/).map(line => line.trim()).filter(Boolean); const promptLines = promptRaw.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
const intentLine = promptLines.find(line => line.startsWith('#') || line.toLowerCase().startsWith('goal')); const intentLine = promptLines.find(line => line.startsWith('#') || line.toLowerCase().startsWith('goal'));
if (intentLine) { if (intentLine) {
promptIntent = intentLine.replace(/^#+\s*/, ''); promptIntent = intentLine.replace(/^#+\s*/, '');
} else if (promptLines.length > 0) { } else if (promptLines.length > 0) {
promptIntent = promptLines[0]; promptIntent = promptLines[0];
}
} catch (err) {
console.warn(`Could not read prompt file: ${promptPath}`, err.message);
} }
} catch (err) {
console.warn(`Could not read prompt file: ${promptPath}`, err.message);
}
} }
// Construct query // Construct query
@@ -35,60 +35,60 @@ const timestamp = Date.now();
// Create retrieval seed event // Create retrieval seed event
const events = [{ const events = [{
agent_id: agentId, agent_id: agentId,
content: `Memory retrieval seed for ${cadence} :: ${promptIntent}`, content: `Memory retrieval seed for ${cadence} :: ${promptIntent}`,
timestamp, timestamp,
tags: ['scheduler', cadence, 'retrieve'], tags: ['scheduler', cadence, 'retrieve'],
metadata: { metadata: {
session_id: runId, session_id: runId,
source: 'scheduler-retrieve', source: 'scheduler-retrieve',
importance: 0.4, importance: 0.4,
prompt_path: promptPath prompt_path: promptPath,
} },
}]; }];
try { try {
// 1. Ingest seed event // 1. Ingest seed event
const ingested = await ingestEvents(events, { agent_id: agentId }); const ingested = await ingestEvents(events, { agent_id: agentId });
// 2. Retrieve memories // 2. Retrieve memories
const retrieved = await getRelevantMemories({ agent_id: agentId, query, k: 5 }); const retrieved = await getRelevantMemories({ agent_id: agentId, query, k: 5 });
// 3. Prepare artifacts // 3. Prepare artifacts
const sessionDir = path.join('.scheduler-memory', runId); const sessionDir = path.join('.scheduler-memory', runId);
const latestDir = path.join('.scheduler-memory', 'latest', cadence); const latestDir = path.join('.scheduler-memory', 'latest', cadence);
mkdirSync(sessionDir, { recursive: true }); mkdirSync(sessionDir, { recursive: true });
mkdirSync(latestDir, { recursive: true }); mkdirSync(latestDir, { recursive: true });
const artifact = { const artifact = {
cadence, cadence,
operation: 'retrieve', operation: 'retrieve',
runId, runId,
servicePath: 'src/services/memory/index.js#getRelevantMemories', servicePath: 'src/services/memory/index.js#getRelevantMemories',
inputs: { inputs: {
agentId, agentId,
promptPath, promptPath,
promptIntent, promptIntent,
query, query,
events: events.length events: events.length,
}, },
outputs: { outputs: {
ingestedCount: ingested.length, ingestedCount: ingested.length,
retrievedCount: retrieved.length retrievedCount: retrieved.length,
}, },
status: 'ok' status: 'ok',
}; };
// 4. Write artifacts // 4. Write artifacts
writeFileSync(path.join(sessionDir, 'retrieve.json'), JSON.stringify(artifact, null, 2)); writeFileSync(path.join(sessionDir, 'retrieve.json'), JSON.stringify(artifact, null, 2));
writeFileSync(path.join(sessionDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n'); writeFileSync(path.join(sessionDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n');
writeFileSync(path.join(latestDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n'); writeFileSync(path.join(latestDir, 'retrieve.ok'), 'MEMORY_RETRIEVED\n');
writeFileSync(path.join(latestDir, 'memories.md'), retrieved.map(m => `- ${m.content}`).join('\n')); writeFileSync(path.join(latestDir, 'memories.md'), retrieved.map(m => `- ${m.content}`).join('\n'));
// 5. Output success marker to stdout (for scheduler verification) // 5. Output success marker to stdout (for scheduler verification)
console.log('MEMORY_RETRIEVED'); console.log('MEMORY_RETRIEVED');
} catch (error) { } catch (error) {
console.error('Memory retrieval failed:', error); console.error('Memory retrieval failed:', error);
process.exit(1); process.exit(1);
} }
+94 -94
View File
@@ -13,20 +13,20 @@ const runId = process.env.SCHEDULER_RUN_ID ||
`session-${Date.now().toString(36)}`; `session-${Date.now().toString(36)}`;
// Extract prompt intent // Extract prompt intent
let promptIntent = `scheduler memory store`; let promptIntent = 'scheduler memory store';
if (promptPath) { if (promptPath) {
try { try {
const promptRaw = readFileSync(promptPath, 'utf8'); const promptRaw = readFileSync(promptPath, 'utf8');
const promptLines = promptRaw.split(/\r?\n/).map(line => line.trim()).filter(Boolean); const promptLines = promptRaw.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
const intentLine = promptLines.find(line => line.startsWith('#') || line.toLowerCase().startsWith('goal')); const intentLine = promptLines.find(line => line.startsWith('#') || line.toLowerCase().startsWith('goal'));
if (intentLine) { if (intentLine) {
promptIntent = intentLine.replace(/^#+\s*/, ''); promptIntent = intentLine.replace(/^#+\s*/, '');
} else if (promptLines.length > 0) { } else if (promptLines.length > 0) {
promptIntent = promptLines[0]; promptIntent = promptLines[0];
}
} catch (err) {
console.warn(`Could not read prompt file: ${promptPath}`, err.message);
} }
} catch (err) {
console.warn(`Could not read prompt file: ${promptPath}`, err.message);
}
} }
// Check for memory input file // Check for memory input file
@@ -40,103 +40,103 @@ const defaultFile = 'memory-update.md';
const targetFile = explicitFile || (envFile && existsSync(envFile) ? envFile : (existsSync(defaultFile) ? defaultFile : null)); const targetFile = explicitFile || (envFile && existsSync(envFile) ? envFile : (existsSync(defaultFile) ? defaultFile : null));
if (targetFile) { if (targetFile) {
try { try {
if (existsSync(targetFile)) { if (existsSync(targetFile)) {
memoryContent = readFileSync(targetFile, 'utf8').trim(); memoryContent = readFileSync(targetFile, 'utf8').trim();
console.log(`Loaded memory content from ${targetFile}`); console.log(`Loaded memory content from ${targetFile}`);
} else { } else {
console.warn(`Target memory file does not exist: ${targetFile}`); console.warn(`Target memory file does not exist: ${targetFile}`);
}
} catch (err) {
console.warn(`Failed to read memory file ${targetFile}:`, err.message);
} }
} catch (err) {
console.warn(`Failed to read memory file ${targetFile}:`, err.message);
}
} }
const baseTs = Date.now(); const baseTs = Date.now();
let events = []; let events = [];
if (memoryContent) { if (memoryContent) {
events.push({ events.push({
agent_id: agentId, agent_id: agentId,
content: memoryContent, content: memoryContent,
timestamp: baseTs, timestamp: baseTs,
tags: ['scheduler', cadence, 'store', 'insight'], tags: ['scheduler', cadence, 'store', 'insight'],
metadata: { metadata: {
session_id: runId, session_id: runId,
source: 'agent-output', source: 'agent-output',
importance: 0.8, // User-provided memory is important importance: 0.8, // User-provided memory is important
prompt_path: promptPath prompt_path: promptPath,
} },
}); });
} else { } else {
// Fallback if no file provided (preserve existing behavior for compatibility) // Fallback if no file provided (preserve existing behavior for compatibility)
console.warn(`No memory input found (checked --file, env SCHEDULER_MEMORY_FILE=${envFile}, or default ${defaultFile}). using fallback placeholder.`); console.warn(`No memory input found (checked --file, env SCHEDULER_MEMORY_FILE=${envFile}, or default ${defaultFile}). using fallback placeholder.`);
events = [ events = [
{ {
agent_id: agentId, agent_id: agentId,
content: `Store memory event A for ${cadence} :: ${promptIntent}`, content: `Store memory event A for ${cadence} :: ${promptIntent}`,
timestamp: baseTs, timestamp: baseTs,
tags: ['scheduler', cadence, 'store'], tags: ['scheduler', cadence, 'store'],
metadata: { metadata: {
session_id: runId, session_id: runId,
source: 'scheduler-store', source: 'scheduler-store',
importance: 0.55, importance: 0.55,
prompt_path: promptPath prompt_path: promptPath,
} },
}, },
{ {
agent_id: agentId, agent_id: agentId,
content: `Store memory event B for ${cadence} :: ${promptIntent}`, content: `Store memory event B for ${cadence} :: ${promptIntent}`,
timestamp: baseTs + 1, timestamp: baseTs + 1,
tags: ['scheduler', cadence, 'store'], tags: ['scheduler', cadence, 'store'],
metadata: { metadata: {
session_id: runId, session_id: runId,
source: 'scheduler-store', source: 'scheduler-store',
importance: 0.55, importance: 0.55,
prompt_path: promptPath prompt_path: promptPath,
} },
} },
]; ];
} }
try { try {
// 1. Ingest events // 1. Ingest events
const stored = await ingestEvents(events, { agent_id: agentId }); const stored = await ingestEvents(events, { agent_id: agentId });
// 2. Prepare artifacts // 2. Prepare artifacts
const sessionDir = path.join('.scheduler-memory', runId); const sessionDir = path.join('.scheduler-memory', runId);
const latestDir = path.join('.scheduler-memory', 'latest', cadence); const latestDir = path.join('.scheduler-memory', 'latest', cadence);
mkdirSync(sessionDir, { recursive: true }); mkdirSync(sessionDir, { recursive: true });
mkdirSync(latestDir, { recursive: true }); mkdirSync(latestDir, { recursive: true });
const artifact = { const artifact = {
cadence, cadence,
operation: 'store', operation: 'store',
runId, runId,
servicePath: 'src/services/memory/index.js#ingestEvents', servicePath: 'src/services/memory/index.js#ingestEvents',
inputs: { inputs: {
agentId, agentId,
promptPath, promptPath,
promptIntent, promptIntent,
events: events.length, events: events.length,
sourceFile: targetFile || 'fallback' sourceFile: targetFile || 'fallback',
}, },
outputs: { outputs: {
storedCount: stored.length, storedCount: stored.length,
summaries: stored.map(m => m.summary) summaries: stored.map(m => m.summary),
}, },
status: 'ok' status: 'ok',
}; };
// 3. Write artifacts // 3. Write artifacts
writeFileSync(path.join(sessionDir, 'store.json'), JSON.stringify(artifact, null, 2)); writeFileSync(path.join(sessionDir, 'store.json'), JSON.stringify(artifact, null, 2));
writeFileSync(path.join(sessionDir, 'store.ok'), 'MEMORY_STORED\n'); writeFileSync(path.join(sessionDir, 'store.ok'), 'MEMORY_STORED\n');
writeFileSync(path.join(latestDir, 'store.ok'), 'MEMORY_STORED\n'); writeFileSync(path.join(latestDir, 'store.ok'), 'MEMORY_STORED\n');
// 4. Output success marker to stdout (for scheduler verification) // 4. Output success marker to stdout (for scheduler verification)
console.log('MEMORY_STORED'); console.log('MEMORY_STORED');
} catch (error) { } catch (error) {
console.error('Memory storage failed:', error); console.error('Memory storage failed:', error);
process.exit(1); process.exit(1);
} }
+1 -1
View File
@@ -24,7 +24,7 @@ async function runTests(i) {
return new Promise((resolve) => { return new Promise((resolve) => {
// Pass strictly the test args to the child process // Pass strictly the test args to the child process
const child = spawn('node', ['--test', ...testArgs], { const child = spawn('node', ['--test', ...testArgs], {
stdio: ['ignore', 'pipe', 'pipe'] stdio: ['ignore', 'pipe', 'pipe'],
}); });
let stdout = ''; let stdout = '';
@@ -12,7 +12,7 @@ function getFiles(dir, files = []) {
files.push(name); files.push(name);
} }
} }
} catch (_e) { } catch {
// ignore if dir doesn't exist // ignore if dir doesn't exist
} }
return files; return files;
@@ -45,11 +45,11 @@ for (const file of testFiles) {
// Naive check for assertions: look for assert., expect(, t. // Naive check for assertions: look for assert., expect(, t.
// Many tests use 't' context from node:test // Many tests use 't' context from node:test
if (!content.includes('assert.') && !content.includes('expect(') && !content.includes('t.plan') && !content.includes('strictEqual')) { if (!content.includes('assert.') && !content.includes('expect(') && !content.includes('t.plan') && !content.includes('strictEqual')) {
// This is very naive, might false positive on 't.ok' etc. // This is very naive, might false positive on 't.ok' etc.
// Let's look for common assertion keywords. // Let's look for common assertion keywords.
const assertionKeywords = ['assert', 'expect', 'strictEqual', 'deepStrictEqual', 'ok', 'equal']; const assertionKeywords = ['assert', 'expect', 'strictEqual', 'deepStrictEqual', 'ok', 'equal'];
const hasAssertion = assertionKeywords.some(k => content.includes(k)); const hasAssertion = assertionKeywords.some(k => content.includes(k));
if (!hasAssertion) issues.push('No obvious assertions'); if (!hasAssertion) issues.push('No obvious assertions');
} }
if (issues.length > 0) { if (issues.length > 0) {
@@ -70,7 +70,7 @@ const invariants = [
'parseFrontmatterAgent(content) || parseAgentFromFilename(latestFile)', 'parseFrontmatterAgent(content) || parseAgentFromFilename(latestFile)',
'scheduler.firstPromptByCadence?.[cadence]', 'scheduler.firstPromptByCadence?.[cadence]',
'ALL_EXCLUDED_REASON', 'ALL_EXCLUDED_REASON',
"reason: ALL_EXCLUDED_REASON", 'reason: ALL_EXCLUDED_REASON',
], ],
}, },
{ {
@@ -161,7 +161,7 @@ const [meta, flow, scheduler] = await Promise.all([
const errors = []; const errors = [];
for (const invariant of invariants) { for (const invariant of invariants) {
if (invariant.promptSnippets?.meta) { if (invariant.promptSnippets?.meta) {
const missingMeta = collectMissing(meta, invariant.promptSnippets.meta); const missingMeta = collectMissing(meta, invariant.promptSnippets.meta);
for (const snippet of missingMeta) { for (const snippet of missingMeta) {
errors.push(`[${invariant.name}] ${FILES.meta} missing snippet: ${snippet}`); errors.push(`[${invariant.name}] ${FILES.meta} missing snippet: ${snippet}`);
+1 -1
View File
@@ -67,7 +67,7 @@ export async function cmdBackup({ output = null } = {}) {
await fs.writeFile( await fs.writeFile(
path.join(backupDir, 'backup-manifest.json'), path.join(backupDir, 'backup-manifest.json'),
JSON.stringify(manifest, null, 2) JSON.stringify(manifest, null, 2),
); );
console.log(JSON.stringify({ success: true, ...manifest }, null, 2)); console.log(JSON.stringify({ success: true, ...manifest }, null, 2));
+8 -8
View File
@@ -8,14 +8,14 @@ import { todayDateStr as _todayDateStr, nowUnix as _nowUnix } from './utils.mjs'
export async function cmdList(cadence, deps = {}) { export async function cmdList(cadence, deps = {}) {
const { const {
getRelays = _getRelays, getRelays = _getRelays,
getNamespace = _getNamespace, getNamespace = _getNamespace,
getRoster = _getRoster, getRoster = _getRoster,
queryLocks = _queryLocks, queryLocks = _queryLocks,
todayDateStr = _todayDateStr, todayDateStr = _todayDateStr,
nowUnix = _nowUnix, nowUnix = _nowUnix,
log = console.log, log = console.log,
error = console.error, error = console.error,
} = deps; } = deps;
const relays = await getRelays(); const relays = await getRelays();
+25 -25
View File
@@ -4,19 +4,19 @@ import fs from 'node:fs/promises';
export async function cmdProposal(subcommand, args = {}) { export async function cmdProposal(subcommand, args = {}) {
switch (subcommand) { switch (subcommand) {
case 'create': case 'create':
return await handleCreate(args); return handleCreate(args);
case 'list': case 'list':
return await handleList(args); return handleList(args);
case 'apply': case 'apply':
return await handleApply(args); return handleApply(args);
case 'reject': case 'reject':
return await handleReject(args); return handleReject(args);
case 'show': case 'show':
return await handleShow(args); return handleShow(args);
default: default:
console.error(`Unknown proposal subcommand: ${subcommand}`); console.error(`Unknown proposal subcommand: ${subcommand}`);
throw new ExitError(1, 'Unknown subcommand'); throw new ExitError(1, 'Unknown subcommand');
} }
} }
@@ -29,7 +29,7 @@ async function handleCreate({ agent, target, contentFile, reason }) {
let newContent; let newContent;
try { try {
newContent = await fs.readFile(contentFile, 'utf8'); newContent = await fs.readFile(contentFile, 'utf8');
} catch (_e) { } catch {
console.error(`Failed to read content file: ${contentFile}`); console.error(`Failed to read content file: ${contentFile}`);
throw new ExitError(1, 'File read error'); throw new ExitError(1, 'File read error');
} }
@@ -85,15 +85,15 @@ async function handleReject({ id, reason }) {
} }
async function handleShow({ id }) { async function handleShow({ id }) {
if (!id) { if (!id) {
console.error('Usage: torch-lock proposal show --id <proposal-id>'); console.error('Usage: torch-lock proposal show --id <proposal-id>');
throw new ExitError(1, 'Missing id'); throw new ExitError(1, 'Missing id');
} }
try { try {
const proposal = await getProposal(id); const proposal = await getProposal(id);
console.log(JSON.stringify(proposal, null, 2)); console.log(JSON.stringify(proposal, null, 2));
} catch (e) { } catch (e) {
console.error(`Failed to show proposal: ${e.message}`); console.error(`Failed to show proposal: ${e.message}`);
throw new ExitError(1, 'Show failed'); throw new ExitError(1, 'Show failed');
} }
} }
+138 -138
View File
@@ -11,7 +11,7 @@ const SECURITY_HEADERS = {
'Content-Security-Policy': "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss:; object-src 'none'; base-uri 'self';", 'Content-Security-Policy': "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss:; object-src 'none'; base-uri 'self';",
'X-Content-Type-Options': 'nosniff', 'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY', 'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin' 'Referrer-Policy': 'strict-origin-when-cross-origin',
}; };
function timingSafeCompare(a, b) { function timingSafeCompare(a, b) {
@@ -41,160 +41,160 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
const server = http.createServer(async (req, res) => { const server = http.createServer(async (req, res) => {
try { try {
// Basic Auth check // Basic Auth check
if (auth) { if (auth) {
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (!authHeader) { if (!authHeader) {
res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' }); res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.end('Authentication required'); res.end('Authentication required');
return; return;
}
const parts = authHeader.split(' ');
const type = parts[0];
const credentials = parts[1];
let isValid = false;
if (type === 'Basic' && credentials) {
try {
const decoded = Buffer.from(credentials, 'base64').toString();
isValid = timingSafeCompare(decoded, auth);
} catch {
isValid = false;
} }
} const parts = authHeader.split(' ');
const type = parts[0];
if (!isValid) { const credentials = parts[1];
res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' }); let isValid = false;
res.end('Invalid credentials'); if (type === 'Basic' && credentials) {
return;
}
}
// URL parsing
const url = new URL(req.url, `http://${req.headers.host}`);
let pathname = url.pathname;
// Redirect / to /dashboard/
if (pathname === '/' || pathname === '/dashboard') {
res.writeHead(302, { ...SECURITY_HEADERS, 'Location': '/dashboard/' });
res.end();
return;
}
async function statSafe(p) {
try {
return await fsp.stat(p);
} catch {
return null;
}
}
// Special case: /torch-config.json
// Priority: Env Var > User's CWD > Package default
if (pathname === '/torch-config.json') {
let configContent = null;
if (process.env.TORCH_CONFIG_PATH) {
const envPath = path.resolve(process.cwd(), process.env.TORCH_CONFIG_PATH);
try {
configContent = await fsp.readFile(envPath, 'utf8');
} catch {
// Explicitly provided config path not found, do not fall back
}
} else {
const userConfigPath = path.resolve(process.cwd(), 'torch-config.json');
try {
configContent = await fsp.readFile(userConfigPath, 'utf8');
} catch {
// Try package default
const packageConfigPath = path.join(packageRoot, 'torch-config.json');
try { try {
configContent = await fsp.readFile(packageConfigPath, 'utf8'); const decoded = Buffer.from(credentials, 'base64').toString();
isValid = timingSafeCompare(decoded, auth);
} catch { } catch {
// Not found in either location isValid = false;
} }
} }
if (!isValid) {
res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.end('Invalid credentials');
return;
}
} }
if (configContent) { // URL parsing
try { const url = new URL(req.url, `http://${req.headers.host}`);
const rawConfig = JSON.parse(configContent); const pathname = url.pathname;
const safeConfig = parseTorchConfig(rawConfig);
// Security: Whitelist only fields required by the dashboard frontend // Redirect / to /dashboard/
const publicConfig = { if (pathname === '/' || pathname === '/dashboard') {
dashboard: safeConfig.dashboard ? { ...safeConfig.dashboard } : {}, res.writeHead(302, { ...SECURITY_HEADERS, 'Location': '/dashboard/' });
nostrLock: safeConfig.nostrLock res.end();
? { return;
}
async function statSafe(p) {
try {
return await fsp.stat(p);
} catch {
return null;
}
}
// Special case: /torch-config.json
// Priority: Env Var > User's CWD > Package default
if (pathname === '/torch-config.json') {
let configContent = null;
if (process.env.TORCH_CONFIG_PATH) {
const envPath = path.resolve(process.cwd(), process.env.TORCH_CONFIG_PATH);
try {
configContent = await fsp.readFile(envPath, 'utf8');
} catch {
// Explicitly provided config path not found, do not fall back
}
} else {
const userConfigPath = path.resolve(process.cwd(), 'torch-config.json');
try {
configContent = await fsp.readFile(userConfigPath, 'utf8');
} catch {
// Try package default
const packageConfigPath = path.join(packageRoot, 'torch-config.json');
try {
configContent = await fsp.readFile(packageConfigPath, 'utf8');
} catch {
// Not found in either location
}
}
}
if (configContent) {
try {
const rawConfig = JSON.parse(configContent);
const safeConfig = parseTorchConfig(rawConfig);
// Security: Whitelist only fields required by the dashboard frontend
const publicConfig = {
dashboard: safeConfig.dashboard ? { ...safeConfig.dashboard } : {},
nostrLock: safeConfig.nostrLock
? {
namespace: safeConfig.nostrLock.namespace, namespace: safeConfig.nostrLock.namespace,
relays: safeConfig.nostrLock.relays, relays: safeConfig.nostrLock.relays,
} }
: {}, : {},
}; };
// Remove sensitive dashboard auth // Remove sensitive dashboard auth
if (publicConfig.dashboard.auth) { if (publicConfig.dashboard.auth) {
delete publicConfig.dashboard.auth; delete publicConfig.dashboard.auth;
}
res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': 'application/json' });
res.end(JSON.stringify(publicConfig, null, 2));
return;
} catch (err) {
console.error('Error parsing torch-config.json:', err);
res.writeHead(500, SECURITY_HEADERS);
res.end('Internal Server Error');
return;
} }
} else {
res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': 'application/json' }); res.writeHead(404, SECURITY_HEADERS);
res.end(JSON.stringify(publicConfig, null, 2)); res.end('Not Found');
return;
} catch (err) {
console.error('Error parsing torch-config.json:', err);
res.writeHead(500, SECURITY_HEADERS);
res.end('Internal Server Error');
return; return;
} }
} else { }
// Security check: prevent directory traversal
// Resolve path relative to packageRoot.
// We strip the leading slash from pathname (which comes from URL) to treat it as relative.
const relativePath = pathname.replace(/^\//, '');
let filePath = path.resolve(packageRoot, relativePath);
// Security check: restrict access to allowed paths
const allowedPaths = [
path.join(packageRoot, 'dashboard'),
path.join(packageRoot, 'assets'),
path.join(packageRoot, 'src', 'docs'),
path.join(packageRoot, 'src', 'constants.mjs'),
path.join(packageRoot, 'torch-config.json'),
];
const isAllowed = allowedPaths.some(allowedPath => {
const rel = path.relative(allowedPath, filePath);
return !rel.startsWith('..') && !path.isAbsolute(rel);
});
if (!isAllowed) {
res.writeHead(403, SECURITY_HEADERS);
res.end('Forbidden');
return;
}
// If directory, try index.html
let fileStat = await statSafe(filePath);
if (fileStat && fileStat.isDirectory()) {
filePath = path.join(filePath, 'index.html');
fileStat = await statSafe(filePath);
}
// Check if file exists and is a file
if (!fileStat || !fileStat.isFile()) {
res.writeHead(404, SECURITY_HEADERS); res.writeHead(404, SECURITY_HEADERS);
res.end('Not Found'); res.end('Not Found');
return; return;
} }
}
// Security check: prevent directory traversal // MIME types
// Resolve path relative to packageRoot. const extname = path.extname(filePath);
// We strip the leading slash from pathname (which comes from URL) to treat it as relative. let contentType = 'text/plain';
const relativePath = pathname.replace(/^\//, ''); switch (extname) {
let filePath = path.resolve(packageRoot, relativePath);
// Security check: restrict access to allowed paths
const allowedPaths = [
path.join(packageRoot, 'dashboard'),
path.join(packageRoot, 'assets'),
path.join(packageRoot, 'src', 'docs'),
path.join(packageRoot, 'src', 'constants.mjs'),
path.join(packageRoot, 'torch-config.json')
];
const isAllowed = allowedPaths.some(allowedPath => {
const rel = path.relative(allowedPath, filePath);
return !rel.startsWith('..') && !path.isAbsolute(rel);
});
if (!isAllowed) {
res.writeHead(403, SECURITY_HEADERS);
res.end('Forbidden');
return;
}
// If directory, try index.html
let fileStat = await statSafe(filePath);
if (fileStat && fileStat.isDirectory()) {
filePath = path.join(filePath, 'index.html');
fileStat = await statSafe(filePath);
}
// Check if file exists and is a file
if (!fileStat || !fileStat.isFile()) {
res.writeHead(404, SECURITY_HEADERS);
res.end('Not Found');
return;
}
// MIME types
const extname = path.extname(filePath);
let contentType = 'text/plain';
switch (extname) {
case '.html': contentType = 'text/html'; break; case '.html': contentType = 'text/html'; break;
case '.js': contentType = 'text/javascript'; break; case '.js': contentType = 'text/javascript'; break;
case '.mjs': contentType = 'text/javascript'; break; case '.mjs': contentType = 'text/javascript'; break;
@@ -205,10 +205,10 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
case '.svg': contentType = 'image/svg+xml'; break; case '.svg': contentType = 'image/svg+xml'; break;
case '.ico': contentType = 'image/x-icon'; break; case '.ico': contentType = 'image/x-icon'; break;
case '.md': contentType = 'text/markdown'; break; case '.md': contentType = 'text/markdown'; break;
} }
res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': contentType }); res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': contentType });
fs.createReadStream(filePath).pipe(res); fs.createReadStream(filePath).pipe(res);
} catch (err) { } catch (err) {
console.error('Dashboard Server Error:', err); console.error('Dashboard Server Error:', err);
if (!res.headersSent) { if (!res.headersSent) {
+179 -179
View File
@@ -182,7 +182,7 @@ export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {}
raceCheckDelayMs = RACE_CHECK_DELAY_MS, raceCheckDelayMs = RACE_CHECK_DELAY_MS,
getDateStr = todayDateStr, getDateStr = todayDateStr,
log = console.log, log = console.log,
error = console.error error = console.error,
} = deps; } = deps;
const relays = await getRelays(); const relays = await getRelays();
@@ -237,10 +237,10 @@ export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {}
// Check if it is a completed task // Check if it is a completed task
if (earliest.status === 'completed') { if (earliest.status === 'completed') {
error(`LOCK DENIED: Task already completed by event ${earliest.eventId}`); error(`LOCK DENIED: Task already completed by event ${earliest.eventId}`);
log('LOCK_STATUS=denied'); log('LOCK_STATUS=denied');
log('LOCK_REASON=already_completed'); log('LOCK_REASON=already_completed');
throw new ExitError(3, 'Task already completed'); throw new ExitError(3, 'Task already completed');
} }
error( error(
@@ -349,7 +349,7 @@ export async function cmdList(cadence, deps = {}) {
getRoster = _getRoster, getRoster = _getRoster,
getDateStr = todayDateStr, getDateStr = todayDateStr,
log = console.log, log = console.log,
error = console.error error = console.error,
} = deps; } = deps;
const relays = await getRelays(); const relays = await getRelays();
@@ -383,10 +383,10 @@ export async function cmdList(cadence, deps = {}) {
let remainMin = '?'; let remainMin = '?';
if (lock.status === 'completed') { if (lock.status === 'completed') {
remainMin = 'done'; remainMin = 'done';
} else if (lock.expiresAt) { } else if (lock.expiresAt) {
const remaining = lock.expiresAt - nowUnix(); const remaining = lock.expiresAt - nowUnix();
remainMin = Math.round(remaining / 60); remainMin = Math.round(remaining / 60);
} }
log( log(
@@ -443,7 +443,7 @@ export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps
finalizeEvent = _finalizeEvent, finalizeEvent = _finalizeEvent,
getDateStr = todayDateStr, getDateStr = todayDateStr,
log = console.log, log = console.log,
error = console.error error = console.error,
} = deps; } = deps;
const relays = await getRelays(); const relays = await getRelays();
@@ -461,7 +461,7 @@ export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps
if (!myLock) { if (!myLock) {
error(`ERROR: No active lock found for agent "${agent}" on ${dateStr}.`); error(`ERROR: No active lock found for agent "${agent}" on ${dateStr}.`);
error(`Cannot complete a task that is not locked or has already expired.`); error('Cannot complete a task that is not locked or has already expired.');
throw new ExitError(1, 'No active lock found'); throw new ExitError(1, 'No active lock found');
} }
@@ -544,204 +544,204 @@ export async function main(argv) {
} }
switch (args.command) { switch (args.command) {
case 'check': { case 'check': {
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) { if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for check`); console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for check`);
throw new ExitError(1, 'Missing cadence'); throw new ExitError(1, 'Missing cadence');
}
await cmdCheck(args.cadence, {
logDir: args.logDir,
ignoreLogs: args.ignoreLogs,
json: args.json,
jsonFile: args.jsonFile,
quiet: args.quiet,
});
break;
} }
await cmdCheck(args.cadence, {
logDir: args.logDir,
ignoreLogs: args.ignoreLogs,
json: args.json,
jsonFile: args.jsonFile,
quiet: args.quiet,
});
break;
}
case 'lock': { case 'lock': {
if (!args.agent) { if (!args.agent) {
console.error('ERROR: --agent <name> is required for lock'); console.error('ERROR: --agent <name> is required for lock');
throw new ExitError(1, 'Missing agent'); throw new ExitError(1, 'Missing agent');
}
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for lock`);
throw new ExitError(1, 'Missing cadence');
}
await cmdLock(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model
});
break;
} }
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
case 'complete': { console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for lock`);
if (!args.agent) { throw new ExitError(1, 'Missing cadence');
console.error('ERROR: --agent <name> is required for complete');
throw new ExitError(1, 'Missing agent');
}
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for complete`);
throw new ExitError(1, 'Missing cadence');
}
await cmdComplete(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model
});
break;
} }
await cmdLock(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model,
});
break;
}
case 'list': { case 'complete': {
if (args.cadence && !VALID_CADENCES.has(args.cadence)) { if (!args.agent) {
console.error(`ERROR: --cadence must be one of: ${[...VALID_CADENCES].join(', ')}`); console.error('ERROR: --agent <name> is required for complete');
throw new ExitError(1, 'Invalid cadence'); throw new ExitError(1, 'Missing agent');
}
await cmdList(args.cadence || null);
break;
} }
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
case 'health': { console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for complete`);
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) { throw new ExitError(1, 'Missing cadence');
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for health`);
throw new ExitError(1, 'Missing cadence');
}
const result = await runRelayHealthCheck({
cadence: args.cadence,
...(Number.isFinite(args.timeoutMs) ? { timeoutMs: args.timeoutMs } : {}),
...(Number.isFinite(args.allRelaysDownMinutes) ? { allRelaysDownMinutes: args.allRelaysDownMinutes } : {}),
...(Number.isFinite(args.minSuccessRate) ? { minSuccessRate: args.minSuccessRate } : {}),
...(Number.isFinite(args.windowMinutes) ? { windowMinutes: args.windowMinutes } : {}),
});
if (!result.ok) {
result.failureCategory = 'all relays unhealthy';
}
console.log(JSON.stringify(result, null, 2));
if (!result.ok) throw new ExitError(2, 'Relay health check failed');
break;
} }
await cmdComplete(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model,
});
break;
}
case 'dashboard': { case 'list': {
await cmdDashboard(args.port, args.host); if (args.cadence && !VALID_CADENCES.has(args.cadence)) {
break; console.error(`ERROR: --cadence must be one of: ${[...VALID_CADENCES].join(', ')}`);
throw new ExitError(1, 'Invalid cadence');
} }
await cmdList(args.cadence || null);
break;
}
case 'init': { case 'health': {
await cmdInit(args.force); if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
break; console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for health`);
throw new ExitError(1, 'Missing cadence');
} }
const result = await runRelayHealthCheck({
case 'update': { cadence: args.cadence,
await cmdUpdate(args.force); ...(Number.isFinite(args.timeoutMs) ? { timeoutMs: args.timeoutMs } : {}),
break; ...(Number.isFinite(args.allRelaysDownMinutes) ? { allRelaysDownMinutes: args.allRelaysDownMinutes } : {}),
...(Number.isFinite(args.minSuccessRate) ? { minSuccessRate: args.minSuccessRate } : {}),
...(Number.isFinite(args.windowMinutes) ? { windowMinutes: args.windowMinutes } : {}),
});
if (!result.ok) {
result.failureCategory = 'all relays unhealthy';
} }
console.log(JSON.stringify(result, null, 2));
if (!result.ok) throw new ExitError(2, 'Relay health check failed');
break;
}
case 'remove': { case 'dashboard': {
await cmdRemove(args.force); await cmdDashboard(args.port, args.host);
break; break;
} }
case 'list-memories': { case 'init': {
const result = await listMemories({ await cmdInit(args.force);
agent_id: args.agent, break;
type: args.type, }
tags: args.tags,
pinned: args.pinned,
limit: args.limit,
offset: args.offset,
});
if (!args.full && Array.isArray(result)) { case 'update': {
for (const memory of result) { await cmdUpdate(args.force);
if (typeof memory.content === 'string' && memory.content.length > 200) { break;
memory.content = memory.content.slice(0, 200) + '... (truncated, use --full to see all)'; }
}
case 'remove': {
await cmdRemove(args.force);
break;
}
case 'list-memories': {
const result = await listMemories({
agent_id: args.agent,
type: args.type,
tags: args.tags,
pinned: args.pinned,
limit: args.limit,
offset: args.offset,
});
if (!args.full && Array.isArray(result)) {
for (const memory of result) {
if (typeof memory.content === 'string' && memory.content.length > 200) {
memory.content = memory.content.slice(0, 200) + '... (truncated, use --full to see all)';
} }
} }
console.log(JSON.stringify(result, null, 2));
break;
} }
case 'inspect-memory': { console.log(JSON.stringify(result, null, 2));
if (!args.id) { break;
console.error('ERROR: --id <memoryId> is required for inspect-memory'); }
throw new ExitError(1, 'Missing memory id');
}
const result = await inspectMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'pin-memory': { case 'inspect-memory': {
if (!args.id) { if (!args.id) {
console.error('ERROR: --id <memoryId> is required for pin-memory'); console.error('ERROR: --id <memoryId> is required for inspect-memory');
throw new ExitError(1, 'Missing memory id'); throw new ExitError(1, 'Missing memory id');
}
const result = await pinMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
} }
const result = await inspectMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'unpin-memory': { case 'pin-memory': {
if (!args.id) { if (!args.id) {
console.error('ERROR: --id <memoryId> is required for unpin-memory'); console.error('ERROR: --id <memoryId> is required for pin-memory');
throw new ExitError(1, 'Missing memory id'); throw new ExitError(1, 'Missing memory id');
}
const result = await unpinMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
} }
const result = await pinMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'trigger-prune-dry-run': { case 'unpin-memory': {
const result = await triggerPruneDryRun({ retentionMs: args.retentionMs ?? undefined }); if (!args.id) {
console.log(JSON.stringify(result, null, 2)); console.error('ERROR: --id <memoryId> is required for unpin-memory');
break; throw new ExitError(1, 'Missing memory id');
} }
const result = await unpinMemory(args.id);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'memory-stats': { case 'trigger-prune-dry-run': {
const result = await memoryStats({ windowMs: args.windowMs ?? undefined }); const result = await triggerPruneDryRun({ retentionMs: args.retentionMs ?? undefined });
console.log(JSON.stringify(result, null, 2)); console.log(JSON.stringify(result, null, 2));
break; break;
}
case 'memory-stats': {
const result = await memoryStats({ windowMs: args.windowMs ?? undefined });
console.log(JSON.stringify(result, null, 2));
break;
}
case 'proposal': {
if (!args.subcommand) {
console.error('ERROR: Missing subcommand for proposal (create, list, apply, reject, show)');
throw new ExitError(1, 'Missing subcommand');
} }
await cmdProposal(args.subcommand, {
agent: args.agent,
target: args.target,
contentFile: args.content,
reason: args.reason,
id: args.id,
status: args.status,
});
break;
}
case 'proposal': { case 'rollback': {
if (!args.subcommand) { await cmdRollback(args.target, args.strategy, { list: args.list });
console.error('ERROR: Missing subcommand for proposal (create, list, apply, reject, show)'); break;
throw new ExitError(1, 'Missing subcommand'); }
}
await cmdProposal(args.subcommand, { case 'backup': {
agent: args.agent, const { cmdBackup, listBackups } = await import('./cmd-backup.mjs');
target: args.target, if (args.list) {
contentFile: args.content, const backups = await listBackups();
reason: args.reason, console.log(JSON.stringify(backups, null, 2));
id: args.id, } else {
status: args.status await cmdBackup({ output: args.output });
});
break;
} }
break;
}
case 'rollback': { default:
await cmdRollback(args.target, args.strategy, { list: args.list }); console.error(`ERROR: Unknown command: ${args.command}`);
break; usage();
} throw new ExitError(1, 'Unknown command');
case 'backup': {
const { cmdBackup, listBackups } = await import('./cmd-backup.mjs');
if (args.list) {
const backups = await listBackups();
console.log(JSON.stringify(backups, null, 2));
} else {
await cmdBackup({ output: args.output });
}
break;
}
default:
console.error(`ERROR: Unknown command: ${args.command}`);
usage();
throw new ExitError(1, 'Unknown command');
} }
} catch (err) { } catch (err) {
if (err instanceof ExitError) { if (err instanceof ExitError) {
+87 -87
View File
@@ -28,19 +28,19 @@ const APP_DIRS = ['src', 'bin', 'dashboard', 'landing', 'assets', 'scripts'];
const APP_FILES = ['package.json', 'build.mjs', 'README.md', 'torch-config.example.json', 'TORCH.md']; const APP_FILES = ['package.json', 'build.mjs', 'README.md', 'torch-config.example.json', 'TORCH.md'];
function getPaths(root, installDirName) { function getPaths(root, installDirName) {
const torchDir = path.resolve(root, installDirName); const torchDir = path.resolve(root, installDirName);
return { return {
root, root,
torchDir, torchDir,
promptsDir: path.join(torchDir, 'prompts'), promptsDir: path.join(torchDir, 'prompts'),
roster: path.join(torchDir, 'roster.json'), roster: path.join(torchDir, 'roster.json'),
}; };
} }
function copyDir(src, dest) { function copyDir(src, dest) {
if (fs.existsSync(src)) { if (fs.existsSync(src)) {
fs.cpSync(src, dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true });
} }
} }
function transformContent(content, installDirName) { function transformContent(content, installDirName) {
@@ -176,10 +176,10 @@ async function resolveConfiguration(cwd, mockAnswers) {
function ensureInstallDirectory(paths, force, installDir) { function ensureInstallDirectory(paths, force, installDir) {
if (fs.existsSync(paths.torchDir) && !force) { if (fs.existsSync(paths.torchDir) && !force) {
const entries = fs.readdirSync(paths.torchDir); const entries = fs.readdirSync(paths.torchDir);
if (entries.length > 0 && installDir !== '.') { if (entries.length > 0 && installDir !== '.') {
throw new Error(`Directory ${paths.torchDir} already exists and is not empty. Use --force to overwrite.`); throw new Error(`Directory ${paths.torchDir} already exists and is not empty. Use --force to overwrite.`);
} }
} }
ensureDir(paths.torchDir); ensureDir(paths.torchDir);
ensureDir(paths.promptsDir); ensureDir(paths.promptsDir);
@@ -249,17 +249,17 @@ function configureTorch(cwd, paths, installDir, namespace, relays, hashtag) {
// Try to load existing or example // Try to load existing or example
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
try { try {
configData = JSON.parse(fs.readFileSync(configPath, 'utf8')); configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
console.log(`Updating existing ${path.relative(cwd, configPath)}...`); console.log(`Updating existing ${path.relative(cwd, configPath)}...`);
} catch (e) { } catch (e) {
console.warn(`Could not parse existing config: ${e.message}`); console.warn(`Could not parse existing config: ${e.message}`);
} }
} else { } else {
const exampleConfigPath = path.join(PKG_ROOT, 'torch-config.example.json'); const exampleConfigPath = path.join(PKG_ROOT, 'torch-config.example.json');
if (fs.existsSync(exampleConfigPath)) { if (fs.existsSync(exampleConfigPath)) {
configData = JSON.parse(fs.readFileSync(exampleConfigPath, 'utf8')); configData = JSON.parse(fs.readFileSync(exampleConfigPath, 'utf8'));
} }
} }
// Apply user choices // Apply user choices
@@ -286,11 +286,11 @@ function configureTorch(cwd, paths, installDir, namespace, relays, hashtag) {
['daily', 'weekly'].forEach(cadence => { ['daily', 'weekly'].forEach(cadence => {
if (!configData.scheduler.memoryPolicyByCadence[cadence]) { if (!configData.scheduler.memoryPolicyByCadence[cadence]) {
configData.scheduler.memoryPolicyByCadence[cadence] = { configData.scheduler.memoryPolicyByCadence[cadence] = {
mode: "required", mode: 'required',
retrieveSuccessMarkers: ["MEMORY_RETRIEVED"], retrieveSuccessMarkers: ['MEMORY_RETRIEVED'],
storeSuccessMarkers: ["MEMORY_STORED"], storeSuccessMarkers: ['MEMORY_STORED'],
retrieveArtifacts: [`.scheduler-memory/latest/${cadence}/retrieve.ok`], retrieveArtifacts: [`.scheduler-memory/latest/${cadence}/retrieve.ok`],
storeArtifacts: [`.scheduler-memory/latest/${cadence}/store.ok`] storeArtifacts: [`.scheduler-memory/latest/${cadence}/store.ok`],
}; };
} }
@@ -308,7 +308,7 @@ function injectHostScriptsIfNeeded(paths, installDir) {
// 7. Inject Scripts into Host Package.json // 7. Inject Scripts into Host Package.json
// If we are NOT installing to '.', the host package.json is in paths.root // If we are NOT installing to '.', the host package.json is in paths.root
if (installDir !== '.') { if (installDir !== '.') {
injectScriptsIntoHost(paths.root, installDir); injectScriptsIntoHost(paths.root, installDir);
} }
} }
@@ -361,44 +361,44 @@ To change these settings, edit \`torch-config.json\` in your project root.
} }
function injectScriptsIntoHost(hostRoot, installDirName) { function injectScriptsIntoHost(hostRoot, installDirName) {
const hostPkgPath = path.join(hostRoot, 'package.json'); const hostPkgPath = path.join(hostRoot, 'package.json');
if (!fs.existsSync(hostPkgPath)) { if (!fs.existsSync(hostPkgPath)) {
console.warn('No package.json found in host root. Skipping script injection.'); console.warn('No package.json found in host root. Skipping script injection.');
return; return;
}
try {
const pkg = JSON.parse(fs.readFileSync(hostPkgPath, 'utf8'));
if (!pkg.scripts) pkg.scripts = {};
const scriptsToAdd = {
'torch:dashboard': `npm run --prefix ${installDirName} dashboard:serve`,
'torch:check': `npm run --prefix ${installDirName} lock:check:daily`, // Default to daily check
'torch:lock': `npm run --prefix ${installDirName} lock:lock`,
'torch:health': `npm run --prefix ${installDirName} lock:health`,
'torch:memory:list': `node ${installDirName === '.' ? '' : installDirName + '/'}bin/torch-lock.mjs list-memories`,
'torch:memory:inspect': `node ${installDirName === '.' ? '' : installDirName + '/'}bin/torch-lock.mjs inspect-memory`,
};
let modified = false;
for (const [key, cmd] of Object.entries(scriptsToAdd)) {
if (!pkg.scripts[key]) {
pkg.scripts[key] = cmd;
console.log(` Added script: "${key}"`);
modified = true;
} else {
console.log(` Script "${key}" already exists, skipping.`);
}
} }
try { if (modified) {
const pkg = JSON.parse(fs.readFileSync(hostPkgPath, 'utf8')); fs.writeFileSync(hostPkgPath, JSON.stringify(pkg, null, 2), 'utf8');
if (!pkg.scripts) pkg.scripts = {}; console.log('Updated package.json with convenience scripts.');
const scriptsToAdd = {
'torch:dashboard': `npm run --prefix ${installDirName} dashboard:serve`,
'torch:check': `npm run --prefix ${installDirName} lock:check:daily`, // Default to daily check
'torch:lock': `npm run --prefix ${installDirName} lock:lock`,
'torch:health': `npm run --prefix ${installDirName} lock:health`,
'torch:memory:list': `node ${installDirName === '.' ? '' : installDirName + '/'}bin/torch-lock.mjs list-memories`,
'torch:memory:inspect': `node ${installDirName === '.' ? '' : installDirName + '/'}bin/torch-lock.mjs inspect-memory`,
};
let modified = false;
for (const [key, cmd] of Object.entries(scriptsToAdd)) {
if (!pkg.scripts[key]) {
pkg.scripts[key] = cmd;
console.log(` Added script: "${key}"`);
modified = true;
} else {
console.log(` Script "${key}" already exists, skipping.`);
}
}
if (modified) {
fs.writeFileSync(hostPkgPath, JSON.stringify(pkg, null, 2), 'utf8');
console.log('Updated package.json with convenience scripts.');
}
} catch (e) {
console.error(`Failed to inject scripts: ${e.message}`);
} }
} catch (e) {
console.error(`Failed to inject scripts: ${e.message}`);
}
} }
/** /**
@@ -431,7 +431,7 @@ function detectTorchInstallDir(cwd) {
if (pkg.name === 'torch-lock') { if (pkg.name === 'torch-lock') {
return '.'; return '.';
} }
} catch (_e) { } catch {
// Ignore parse errors // Ignore parse errors
} }
} }
@@ -571,7 +571,7 @@ export async function cmdRemove(force = false, cwd = process.cwd(), mockAnswers
if (pkg.scripts) { if (pkg.scripts) {
hasHostScripts = TORCH_HOST_SCRIPTS.some(key => key in pkg.scripts); hasHostScripts = TORCH_HOST_SCRIPTS.some(key => key in pkg.scripts);
} }
} catch (_e) { /* ignore */ } } catch { /* ignore */ }
} }
if (targets.length === 0 && !hasHostScripts) { if (targets.length === 0 && !hasHostScripts) {
@@ -628,7 +628,7 @@ export async function cmdRemove(force = false, cwd = process.cwd(), mockAnswers
fs.rmdirSync(srcDir); fs.rmdirSync(srcDir);
log(' Removed empty src/ directory'); log(' Removed empty src/ directory');
} }
} catch (_e) { /* leave it if not empty or permission error */ } } catch { /* leave it if not empty or permission error */ }
} }
// 3. Remove host package.json scripts // 3. Remove host package.json scripts
@@ -664,15 +664,15 @@ export function cmdUpdate(force = false, cwd = process.cwd()) {
let installDirName = 'torch'; let installDirName = 'torch';
if (!fs.existsSync(path.join(cwd, 'torch')) && fs.existsSync(path.join(cwd, 'package.json'))) { if (!fs.existsSync(path.join(cwd, 'torch')) && fs.existsSync(path.join(cwd, 'package.json'))) {
// Check if current dir is the torch dir // Check if current dir is the torch dir
try { try {
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')); const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
if (pkg.name === 'torch-lock') { if (pkg.name === 'torch-lock') {
installDirName = '.'; installDirName = '.';
}
} catch (_e) {
// Ignore error if package.json is missing or invalid
} }
} catch {
// Ignore error if package.json is missing or invalid
}
} }
const paths = getPaths(cwd, installDirName); const paths = getPaths(cwd, installDirName);
@@ -693,11 +693,11 @@ export function cmdUpdate(force = false, cwd = process.cwd()) {
// We backup EVERYTHING in torchDir except _backups and node_modules // We backup EVERYTHING in torchDir except _backups and node_modules
const entries = fs.readdirSync(paths.torchDir); const entries = fs.readdirSync(paths.torchDir);
for (const entry of entries) { for (const entry of entries) {
if (entry === '_backups' || entry === 'node_modules' || entry === '.git') continue; if (entry === '_backups' || entry === 'node_modules' || entry === '.git') continue;
const srcPath = path.join(paths.torchDir, entry); const srcPath = path.join(paths.torchDir, entry);
const destPath = path.join(thisBackupDir, entry); const destPath = path.join(thisBackupDir, entry);
fs.cpSync(srcPath, destPath, { recursive: true }); fs.cpSync(srcPath, destPath, { recursive: true });
} }
// 2. Update App Directories (Overwrite) // 2. Update App Directories (Overwrite)
@@ -746,15 +746,15 @@ export function cmdUpdate(force = false, cwd = process.cwd()) {
const destFile = path.join(destDir, file); const destFile = path.join(destDir, file);
if (force) { if (force) {
copyFile(srcFile, destFile, false, true, installDirName); copyFile(srcFile, destFile, false, true, installDirName);
updated++; updated++;
} else { } else {
if (!fs.existsSync(destFile)) { if (!fs.existsSync(destFile)) {
copyFile(srcFile, destFile, false, true, installDirName); copyFile(srcFile, destFile, false, true, installDirName);
added++; added++;
} else { } else {
skipped++; skipped++;
} }
} }
} }
console.log(` ${dir}/: ${added} added, ${updated} updated, ${skipped} preserved`); console.log(` ${dir}/: ${added} added, ${updated} updated, ${skipped} preserved`);
+20 -20
View File
@@ -9,9 +9,9 @@ const originalConfig = fs.existsSync(configPath) ? fs.readFileSync(configPath) :
if (!fs.existsSync(configPath)) { if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath, JSON.stringify({ fs.writeFileSync(configPath, JSON.stringify({
nostrLock: { nostrLock: {
relays: ["wss://relay.damus.io"], relays: ['wss://relay.damus.io'],
namespace: "test-ns" namespace: 'test-ns',
} },
}, null, 2)); }, null, 2));
} }
@@ -19,27 +19,27 @@ const ITERATIONS = 1000;
const start = process.hrtime.bigint(); const start = process.hrtime.bigint();
async function run() { async function run() {
for (let i = 0; i < ITERATIONS; i++) { for (let i = 0; i < ITERATIONS; i++) {
_resetTorchConfigCache(); _resetTorchConfigCache();
try { try {
const res = await loadTorchConfig(); const res = await loadTorchConfig();
// Use res to prevent dead code elimination if JIT is smart (though side effects exist) // Use res to prevent dead code elimination if JIT is smart (though side effects exist)
if (!res) throw new Error("Failed"); if (!res) throw new Error('Failed');
} catch (_e) { } catch {
// ignore // ignore
}
} }
}
const end = process.hrtime.bigint(); const end = process.hrtime.bigint();
const duration = Number(end - start) / 1e6; // ms const duration = Number(end - start) / 1e6; // ms
console.log(`[ASYNC] Loaded config ${ITERATIONS} times in ${duration.toFixed(2)}ms`); console.log(`[ASYNC] Loaded config ${ITERATIONS} times in ${duration.toFixed(2)}ms`);
console.log(`[ASYNC] Average: ${(duration / ITERATIONS).toFixed(4)}ms per load`); console.log(`[ASYNC] Average: ${(duration / ITERATIONS).toFixed(4)}ms per load`);
// Cleanup // Cleanup
if (!originalConfig && fs.existsSync(configPath)) { if (!originalConfig && fs.existsSync(configPath)) {
fs.unlinkSync(configPath); fs.unlinkSync(configPath);
} }
} }
run(); run();
+12 -12
View File
@@ -28,9 +28,9 @@ export async function listProposals() {
const meta = JSON.parse(metaContent); const meta = JSON.parse(metaContent);
proposals.push({ proposals.push({
id: entry.name, id: entry.name,
...meta ...meta,
}); });
} catch (_e) { } catch {
// Ignore invalid proposals // Ignore invalid proposals
} }
} }
@@ -59,7 +59,7 @@ export async function getProposal(id) {
meta, meta,
newContent, newContent,
diff, diff,
dir dir,
}; };
} catch (e) { } catch (e) {
throw new Error(`Proposal ${id} not found or invalid: ${e.message}`, { cause: e }); throw new Error(`Proposal ${id} not found or invalid: ${e.message}`, { cause: e });
@@ -103,11 +103,11 @@ export async function createProposal({ agent, target, newContent, reason }) {
if (e.status === 1 && e.stdout) { if (e.status === 1 && e.stdout) {
diff = e.stdout.toString(); diff = e.stdout.toString();
} else if (e.status !== 1) { } else if (e.status !== 1) {
// Some other error // Some other error
console.error('Diff generation failed:', e.message); console.error('Diff generation failed:', e.message);
} }
} }
} catch (_err) { } catch {
diff = '(New File)'; diff = '(New File)';
} }
@@ -119,7 +119,7 @@ export async function createProposal({ agent, target, newContent, reason }) {
target, target,
reason, reason,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
status: 'pending' status: 'pending',
}; };
await fs.writeFile(path.join(dir, 'meta.json'), JSON.stringify(meta, null, 2)); await fs.writeFile(path.join(dir, 'meta.json'), JSON.stringify(meta, null, 2));
@@ -146,7 +146,7 @@ export async function validateProposal(id) {
for (const pattern of requiredPatterns) { for (const pattern of requiredPatterns) {
if (!pattern.test(newContent)) { if (!pattern.test(newContent)) {
return { valid: false, reason: `Missing required header pattern: ${pattern}` }; return { valid: false, reason: `Missing required header pattern: ${pattern}` };
} }
} }
@@ -195,7 +195,7 @@ export async function applyProposal(id) {
archivedAt: new Date().toISOString(), archivedAt: new Date().toISOString(),
}; };
await fs.writeFile(archivePath.replace(/\.md$/, '.meta.json'), JSON.stringify(archiveMeta, null, 2)); await fs.writeFile(archivePath.replace(/\.md$/, '.meta.json'), JSON.stringify(archiveMeta, null, 2));
} catch (_e) { } catch {
// If file didn't exist, nothing to archive // If file didn't exist, nothing to archive
} }
@@ -272,7 +272,7 @@ export async function listPromptVersions(target) {
// Convert back: YYYY-MM-DDTHH-MM-SS-mmmZ -> ISO // Convert back: YYYY-MM-DDTHH-MM-SS-mmmZ -> ISO
archivedAt = tsPart.replace( archivedAt = tsPart.replace(
/^(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, /^(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/,
'$1:$2:$3.$4Z' '$1:$2:$3.$4Z',
); );
if (archivedAt === tsPart) archivedAt = null; // parse failed if (archivedAt === tsPart) archivedAt = null; // parse failed
} else { } else {
@@ -295,7 +295,7 @@ export async function listPromptVersions(target) {
reason: meta?.reason ?? null, reason: meta?.reason ?? null,
}); });
} }
} catch (_e) { } catch {
// archiveDir doesn't exist — no versions yet // archiveDir doesn't exist — no versions yet
} }
@@ -332,7 +332,7 @@ export async function rollbackPrompt(target, hashOrStrategy = 'latest') {
sourceContent = await fs.readFile(path.join(archiveDir, match), 'utf8'); sourceContent = await fs.readFile(path.join(archiveDir, match), 'utf8');
} }
} }
} catch (_e) { } catch {
// Archive lookup failed // Archive lookup failed
} }
+1 -1
View File
@@ -113,7 +113,7 @@ function toContextString(agentContext) {
return agentContext.trim(); return agentContext.trim();
} }
if (agentContext == null) { if (agentContext === null || agentContext === undefined) {
return ''; return '';
} }
+1 -1
View File
@@ -197,7 +197,7 @@ export async function createLifecyclePlan(memories, options) {
const templates = loadMemoryPromptTemplates({ templateDir: options.templateDir }); const templates = loadMemoryPromptTemplates({ templateDir: options.templateDir });
const condensedGroups = await Promise.all( const condensedGroups = await Promise.all(
groups.map((group) => condenseGroup(group, options.generateSummary, templates.condense)) groups.map((group) => condenseGroup(group, options.generateSummary, templates.condense)),
); );
const mergedIntoMap = new Map(); const mergedIntoMap = new Map();
+2 -2
View File
@@ -54,8 +54,8 @@ function textMatchSimilarity(queryTerms, queryRegexes, memory) {
*/ */
function matchesMetadataFilters(memory, params) { function matchesMetadataFilters(memory, params) {
const inTimeframe = !params.timeframe const inTimeframe = !params.timeframe
|| ((params.timeframe.from == null || memory.created_at >= params.timeframe.from) || ((params.timeframe.from === null || memory.created_at >= params.timeframe.from)
&& (params.timeframe.to == null || memory.created_at <= params.timeframe.to)); && (params.timeframe.to === null || memory.created_at <= params.timeframe.to));
if (!inTimeframe) return false; if (!inTimeframe) return false;
+3 -3
View File
@@ -25,7 +25,7 @@ function redactObviousPii(text) {
function renderTemplate(template, replacements) { function renderTemplate(template, replacements) {
return Object.entries(replacements).reduce( return Object.entries(replacements).reduce(
(output, [key, value]) => output.replaceAll(`{{${key}}}`, String(value ?? '')), (output, [key, value]) => output.replaceAll(`{{${key}}}`, String(value ?? '')),
template template,
); );
} }
@@ -40,7 +40,7 @@ export function loadMemoryPromptTemplates(options = {}) {
const filePath = path.join(templateDir, fileName); const filePath = path.join(templateDir, fileName);
const content = readFileSync(filePath, 'utf8').trim(); const content = readFileSync(filePath, 'utf8').trim();
return [key, content]; return [key, content];
}) }),
); );
templateCache.set(templateDir, templates); templateCache.set(templateDir, templates);
@@ -77,7 +77,7 @@ function isLikelyFactual(summary, sourceText) {
sourceText sourceText
.toLowerCase() .toLowerCase()
.split(/[^a-z0-9]+/) .split(/[^a-z0-9]+/)
.filter((token) => token.length > 3) .filter((token) => token.length > 3),
); );
const summaryTokens = summary const summaryTokens = summary
.toLowerCase() .toLowerCase()
+30
View File
@@ -0,0 +1,30 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { estimateTokenCount } from '../src/services/memory/formatter.js';
describe('formatter', () => {
describe('estimateTokenCount', () => {
it('should return 0 for empty string', () => {
const result = estimateTokenCount('');
assert.strictEqual(result, 0);
});
it('should return 0 for null', () => {
const result = estimateTokenCount(null);
assert.strictEqual(result, 0);
});
it('should estimate tokens at approximately 4 chars per token', () => {
const text = 'This is a test string with some content';
const result = estimateTokenCount(text);
// 40 chars / 4 = 10 tokens (approximately)
assert(result > 0);
});
it('should handle long strings', () => {
const text = 'a'.repeat(1000);
const result = estimateTokenCount(text);
assert.strictEqual(result, 250);
});
});
});
+83
View File
@@ -0,0 +1,83 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { parseLockEvent } from '../src/lock-ops.mjs';
describe('parseLockEvent', () => {
it('should parse a valid lock event', () => {
const event = {
id: 'test-event-id',
pubkey: 'test-pubkey',
created_at: Math.floor(Date.now() / 1000),
kind: 30078,
tags: [
['d', 'torch-lock/'],
['t', 'torch-agent-lock'],
],
content: JSON.stringify({
agent: 'test-agent',
cadence: 'daily',
date: '2026-02-20',
git_commit: 'abc123',
prompt_hash: 'def456',
}),
};
const result = parseLockEvent(event);
assert.strictEqual(result.agent, 'test-agent');
assert.strictEqual(result.cadence, 'daily');
assert.strictEqual(result.date, '2026-02-20');
assert.strictEqual(result.eventId, 'test-event-id');
});
it('should handle event with missing content fields', () => {
const event = {
id: 'test-event-id',
pubkey: 'test-pubkey',
created_at: Math.floor(Date.now() / 1000),
kind: 30078,
tags: [['d', 'torch-lock/']],
content: '{}',
};
const result = parseLockEvent(event);
assert.strictEqual(result.agent, null);
assert.strictEqual(result.cadence, null);
assert.strictEqual(result.date, null);
});
it('should parse event with expiration tag', () => {
const event = {
id: 'test-event-id',
pubkey: 'test-pubkey',
created_at: Math.floor(Date.now() / 1000),
kind: 30078,
tags: [
['d', 'torch-lock/'],
['expiration', '1771603837'],
],
content: '{}',
};
const result = parseLockEvent(event);
assert.strictEqual(result.expiresAt, 1771603837);
});
it('should handle malformed JSON content gracefully', () => {
const event = {
id: 'test-event-id',
pubkey: 'test-pubkey',
created_at: Math.floor(Date.now() / 1000),
kind: 30078,
tags: [['d', 'torch-lock/']],
content: 'not valid json',
};
const result = parseLockEvent(event);
assert.strictEqual(result.agent, null);
assert.strictEqual(result.cadence, null);
});
});
+35
View File
@@ -0,0 +1,35 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { todayDateStr, detectPlatform } from '../src/utils.mjs';
describe('utils', () => {
describe('todayDateStr', () => {
it('should return date string in YYYY-MM-DD format', () => {
const result = todayDateStr();
assert.match(result, /^\d{4}-\d{2}-\d{2}$/);
});
});
describe('detectPlatform', () => {
it('should return null when no platform env vars are set', () => {
const result = detectPlatform();
// Should return a string or null depending on environment
assert(result === null || typeof result === 'string');
});
it('should detect qwen platform from environment', () => {
const original = process.env.QWEN_API_KEY;
try {
process.env.QWEN_API_KEY = 'test-key';
const result = detectPlatform();
assert.strictEqual(result, 'qwen');
} finally {
if (original) {
process.env.QWEN_API_KEY = original;
} else {
delete process.env.QWEN_API_KEY;
}
}
});
});
});