@isaacriehm/cairn 0.1.0 โ 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/backprop/id.d.ts +0 -14
- package/dist/backprop/id.js +0 -40
- package/dist/backprop/id.js.map +0 -1
- package/dist/backprop/index.d.ts +0 -23
- package/dist/backprop/index.js +0 -21
- package/dist/backprop/index.js.map +0 -1
- package/dist/backprop/prompt.d.ts +0 -16
- package/dist/backprop/prompt.js +0 -101
- package/dist/backprop/prompt.js.map +0 -1
- package/dist/backprop/runner.d.ts +0 -18
- package/dist/backprop/runner.js +0 -95
- package/dist/backprop/runner.js.map +0 -1
- package/dist/backprop/schema.d.ts +0 -61
- package/dist/backprop/schema.js +0 -55
- package/dist/backprop/schema.js.map +0 -1
- package/dist/backprop/types.d.ts +0 -101
- package/dist/backprop/types.js +0 -24
- package/dist/backprop/types.js.map +0 -1
- package/dist/backprop/writer.d.ts +0 -27
- package/dist/backprop/writer.js +0 -301
- package/dist/backprop/writer.js.map +0 -1
- package/dist/claude/error.d.ts +0 -33
- package/dist/claude/error.js +0 -58
- package/dist/claude/error.js.map +0 -1
- package/dist/claude/index.d.ts +0 -3
- package/dist/claude/index.js +0 -3
- package/dist/claude/index.js.map +0 -1
- package/dist/claude/runner.d.ts +0 -11
- package/dist/claude/runner.js +0 -132
- package/dist/claude/runner.js.map +0 -1
- package/dist/claude/types.d.ts +0 -52
- package/dist/claude/types.js +0 -14
- package/dist/claude/types.js.map +0 -1
- package/dist/cli/daemon.d.ts +0 -54
- package/dist/cli/daemon.js +0 -351
- package/dist/cli/daemon.js.map +0 -1
- package/dist/cli/install.d.ts +0 -52
- package/dist/cli/install.js +0 -308
- package/dist/cli/install.js.map +0 -1
- package/dist/cli/mirror.d.ts +0 -1
- package/dist/cli/mirror.js +0 -97
- package/dist/cli/mirror.js.map +0 -1
- package/dist/cli/run.d.ts +0 -1
- package/dist/cli/run.js +0 -174
- package/dist/cli/run.js.map +0 -1
- package/dist/cli/task.d.ts +0 -18
- package/dist/cli/task.js +0 -137
- package/dist/cli/task.js.map +0 -1
- package/dist/cli/watch.d.ts +0 -1
- package/dist/cli/watch.js +0 -73
- package/dist/cli/watch.js.map +0 -1
- package/dist/decision-capture/capture.d.ts +0 -57
- package/dist/decision-capture/capture.js +0 -186
- package/dist/decision-capture/capture.js.map +0 -1
- package/dist/decision-capture/extractor.d.ts +0 -20
- package/dist/decision-capture/extractor.js +0 -103
- package/dist/decision-capture/extractor.js.map +0 -1
- package/dist/decision-capture/id.d.ts +0 -14
- package/dist/decision-capture/id.js +0 -44
- package/dist/decision-capture/id.js.map +0 -1
- package/dist/decision-capture/index.d.ts +0 -25
- package/dist/decision-capture/index.js +0 -21
- package/dist/decision-capture/index.js.map +0 -1
- package/dist/decision-capture/prompt.d.ts +0 -15
- package/dist/decision-capture/prompt.js +0 -68
- package/dist/decision-capture/prompt.js.map +0 -1
- package/dist/decision-capture/refinement-prompt.d.ts +0 -25
- package/dist/decision-capture/refinement-prompt.js +0 -146
- package/dist/decision-capture/refinement-prompt.js.map +0 -1
- package/dist/decision-capture/refinement-schema.d.ts +0 -52
- package/dist/decision-capture/refinement-schema.js +0 -61
- package/dist/decision-capture/refinement-schema.js.map +0 -1
- package/dist/decision-capture/refinement.d.ts +0 -60
- package/dist/decision-capture/refinement.js +0 -439
- package/dist/decision-capture/refinement.js.map +0 -1
- package/dist/decision-capture/schema.d.ts +0 -70
- package/dist/decision-capture/schema.js +0 -71
- package/dist/decision-capture/schema.js.map +0 -1
- package/dist/decision-capture/types.d.ts +0 -201
- package/dist/decision-capture/types.js +0 -20
- package/dist/decision-capture/types.js.map +0 -1
- package/dist/decision-capture/writer.d.ts +0 -90
- package/dist/decision-capture/writer.js +0 -267
- package/dist/decision-capture/writer.js.map +0 -1
- package/dist/frontend/discord/acl.d.ts +0 -6
- package/dist/frontend/discord/acl.js +0 -19
- package/dist/frontend/discord/acl.js.map +0 -1
- package/dist/frontend/discord/channels.d.ts +0 -29
- package/dist/frontend/discord/channels.js +0 -58
- package/dist/frontend/discord/channels.js.map +0 -1
- package/dist/frontend/discord/classifier.d.ts +0 -16
- package/dist/frontend/discord/classifier.js +0 -29
- package/dist/frontend/discord/classifier.js.map +0 -1
- package/dist/frontend/discord/index.d.ts +0 -118
- package/dist/frontend/discord/index.js +0 -1104
- package/dist/frontend/discord/index.js.map +0 -1
- package/dist/frontend/discord/slash.d.ts +0 -18
- package/dist/frontend/discord/slash.js +0 -90
- package/dist/frontend/discord/slash.js.map +0 -1
- package/dist/frontend/inbox.d.ts +0 -17
- package/dist/frontend/inbox.js +0 -30
- package/dist/frontend/inbox.js.map +0 -1
- package/dist/frontend/index.d.ts +0 -8
- package/dist/frontend/index.js +0 -6
- package/dist/frontend/index.js.map +0 -1
- package/dist/frontend/stub/index.d.ts +0 -58
- package/dist/frontend/stub/index.js +0 -144
- package/dist/frontend/stub/index.js.map +0 -1
- package/dist/frontend/types.d.ts +0 -247
- package/dist/frontend/types.js +0 -15
- package/dist/frontend/types.js.map +0 -1
- package/dist/gc/apply.d.ts +0 -26
- package/dist/gc/apply.js +0 -48
- package/dist/gc/apply.js.map +0 -1
- package/dist/gc/canary.d.ts +0 -42
- package/dist/gc/canary.js +0 -134
- package/dist/gc/canary.js.map +0 -1
- package/dist/gc/classify.d.ts +0 -25
- package/dist/gc/classify.js +0 -89
- package/dist/gc/classify.js.map +0 -1
- package/dist/gc/doc-gardening.d.ts +0 -29
- package/dist/gc/doc-gardening.js +0 -146
- package/dist/gc/doc-gardening.js.map +0 -1
- package/dist/gc/frontmatter.d.ts +0 -35
- package/dist/gc/frontmatter.js +0 -111
- package/dist/gc/frontmatter.js.map +0 -1
- package/dist/gc/generator-drift.d.ts +0 -28
- package/dist/gc/generator-drift.js +0 -53
- package/dist/gc/generator-drift.js.map +0 -1
- package/dist/gc/index.d.ts +0 -35
- package/dist/gc/index.js +0 -26
- package/dist/gc/index.js.map +0 -1
- package/dist/gc/quality-update.d.ts +0 -23
- package/dist/gc/quality-update.js +0 -69
- package/dist/gc/quality-update.js.map +0 -1
- package/dist/gc/stub-hits.d.ts +0 -31
- package/dist/gc/stub-hits.js +0 -125
- package/dist/gc/stub-hits.js.map +0 -1
- package/dist/gc/sweep.d.ts +0 -56
- package/dist/gc/sweep.js +0 -178
- package/dist/gc/sweep.js.map +0 -1
- package/dist/gc/types.d.ts +0 -129
- package/dist/gc/types.js +0 -26
- package/dist/gc/types.js.map +0 -1
- package/dist/ground/drift.d.ts +0 -8
- package/dist/ground/drift.js +0 -23
- package/dist/ground/drift.js.map +0 -1
- package/dist/ground/frontmatter.d.ts +0 -20
- package/dist/ground/frontmatter.js +0 -49
- package/dist/ground/frontmatter.js.map +0 -1
- package/dist/ground/glob.d.ts +0 -10
- package/dist/ground/glob.js +0 -46
- package/dist/ground/glob.js.map +0 -1
- package/dist/ground/index.d.ts +0 -14
- package/dist/ground/index.js +0 -10
- package/dist/ground/index.js.map +0 -1
- package/dist/ground/ledgers.d.ts +0 -18
- package/dist/ground/ledgers.js +0 -103
- package/dist/ground/ledgers.js.map +0 -1
- package/dist/ground/manifest.d.ts +0 -10
- package/dist/ground/manifest.js +0 -88
- package/dist/ground/manifest.js.map +0 -1
- package/dist/ground/paths.d.ts +0 -20
- package/dist/ground/paths.js +0 -61
- package/dist/ground/paths.js.map +0 -1
- package/dist/ground/quality-grades.d.ts +0 -11
- package/dist/ground/quality-grades.js +0 -98
- package/dist/ground/quality-grades.js.map +0 -1
- package/dist/ground/schemas.d.ts +0 -306
- package/dist/ground/schemas.js +0 -188
- package/dist/ground/schemas.js.map +0 -1
- package/dist/ground/walk.d.ts +0 -7
- package/dist/ground/walk.js +0 -53
- package/dist/ground/walk.js.map +0 -1
- package/dist/init/detect.d.ts +0 -25
- package/dist/init/detect.js +0 -336
- package/dist/init/detect.js.map +0 -1
- package/dist/init/index.d.ts +0 -14
- package/dist/init/index.js +0 -9
- package/dist/init/index.js.map +0 -1
- package/dist/init/init.d.ts +0 -68
- package/dist/init/init.js +0 -673
- package/dist/init/init.js.map +0 -1
- package/dist/init/mapper.d.ts +0 -160
- package/dist/init/mapper.js +0 -248
- package/dist/init/mapper.js.map +0 -1
- package/dist/init/prompts.d.ts +0 -70
- package/dist/init/prompts.js +0 -80
- package/dist/init/prompts.js.map +0 -1
- package/dist/init/secrets.d.ts +0 -18
- package/dist/init/secrets.js +0 -76
- package/dist/init/secrets.js.map +0 -1
- package/dist/init/seed.d.ts +0 -21
- package/dist/init/seed.js +0 -75
- package/dist/init/seed.js.map +0 -1
- package/dist/init/setup-runners.d.ts +0 -17
- package/dist/init/setup-runners.js +0 -70
- package/dist/init/setup-runners.js.map +0 -1
- package/dist/init/types.d.ts +0 -59
- package/dist/init/types.js +0 -10
- package/dist/init/types.js.map +0 -1
- package/dist/init/walker.d.ts +0 -53
- package/dist/init/walker.js +0 -460
- package/dist/init/walker.js.map +0 -1
- package/dist/init/workflow-block.d.ts +0 -34
- package/dist/init/workflow-block.js +0 -110
- package/dist/init/workflow-block.js.map +0 -1
- package/dist/logger.d.ts +0 -3
- package/dist/logger.js +0 -23
- package/dist/logger.js.map +0 -1
- package/dist/mcp/context.d.ts +0 -16
- package/dist/mcp/context.js +0 -8
- package/dist/mcp/context.js.map +0 -1
- package/dist/mcp/errors.d.ts +0 -17
- package/dist/mcp/errors.js +0 -23
- package/dist/mcp/errors.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -10
- package/dist/mcp/index.js +0 -7
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/path-allowlist.d.ts +0 -25
- package/dist/mcp/path-allowlist.js +0 -66
- package/dist/mcp/path-allowlist.js.map +0 -1
- package/dist/mcp/result.d.ts +0 -8
- package/dist/mcp/result.js +0 -18
- package/dist/mcp/result.js.map +0 -1
- package/dist/mcp/schemas.d.ts +0 -153
- package/dist/mcp/schemas.js +0 -135
- package/dist/mcp/schemas.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -11
- package/dist/mcp/server.js +0 -58
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/telemetry.d.ts +0 -15
- package/dist/mcp/telemetry.js +0 -13
- package/dist/mcp/telemetry.js.map +0 -1
- package/dist/mcp/tools/append.d.ts +0 -8
- package/dist/mcp/tools/append.js +0 -33
- package/dist/mcp/tools/append.js.map +0 -1
- package/dist/mcp/tools/archive.d.ts +0 -8
- package/dist/mcp/tools/archive.js +0 -49
- package/dist/mcp/tools/archive.js.map +0 -1
- package/dist/mcp/tools/ask-operator.d.ts +0 -34
- package/dist/mcp/tools/ask-operator.js +0 -93
- package/dist/mcp/tools/ask-operator.js.map +0 -1
- package/dist/mcp/tools/canonical-for-topic.d.ts +0 -6
- package/dist/mcp/tools/canonical-for-topic.js +0 -40
- package/dist/mcp/tools/canonical-for-topic.js.map +0 -1
- package/dist/mcp/tools/decision-get.d.ts +0 -6
- package/dist/mcp/tools/decision-get.js +0 -49
- package/dist/mcp/tools/decision-get.js.map +0 -1
- package/dist/mcp/tools/decisions-for-symbol.d.ts +0 -7
- package/dist/mcp/tools/decisions-for-symbol.js +0 -42
- package/dist/mcp/tools/decisions-for-symbol.js.map +0 -1
- package/dist/mcp/tools/decisions-in-scope.d.ts +0 -7
- package/dist/mcp/tools/decisions-in-scope.js +0 -47
- package/dist/mcp/tools/decisions-in-scope.js.map +0 -1
- package/dist/mcp/tools/drop-task.d.ts +0 -12
- package/dist/mcp/tools/drop-task.js +0 -47
- package/dist/mcp/tools/drop-task.js.map +0 -1
- package/dist/mcp/tools/get-full.d.ts +0 -7
- package/dist/mcp/tools/get-full.js +0 -46
- package/dist/mcp/tools/get-full.js.map +0 -1
- package/dist/mcp/tools/ground-get.d.ts +0 -7
- package/dist/mcp/tools/ground-get.js +0 -80
- package/dist/mcp/tools/ground-get.js.map +0 -1
- package/dist/mcp/tools/index.d.ts +0 -3
- package/dist/mcp/tools/index.js +0 -44
- package/dist/mcp/tools/index.js.map +0 -1
- package/dist/mcp/tools/invariant-get.d.ts +0 -6
- package/dist/mcp/tools/invariant-get.js +0 -49
- package/dist/mcp/tools/invariant-get.js.map +0 -1
- package/dist/mcp/tools/invariants-in-scope.d.ts +0 -7
- package/dist/mcp/tools/invariants-in-scope.js +0 -65
- package/dist/mcp/tools/invariants-in-scope.js.map +0 -1
- package/dist/mcp/tools/query-history.d.ts +0 -9
- package/dist/mcp/tools/query-history.js +0 -33
- package/dist/mcp/tools/query-history.js.map +0 -1
- package/dist/mcp/tools/record-decision.d.ts +0 -14
- package/dist/mcp/tools/record-decision.js +0 -101
- package/dist/mcp/tools/record-decision.js.map +0 -1
- package/dist/mcp/tools/record-run-event.d.ts +0 -10
- package/dist/mcp/tools/record-run-event.js +0 -28
- package/dist/mcp/tools/record-run-event.js.map +0 -1
- package/dist/mcp/tools/search.d.ts +0 -9
- package/dist/mcp/tools/search.js +0 -165
- package/dist/mcp/tools/search.js.map +0 -1
- package/dist/mcp/tools/supersedes-chain.d.ts +0 -6
- package/dist/mcp/tools/supersedes-chain.js +0 -66
- package/dist/mcp/tools/supersedes-chain.js.map +0 -1
- package/dist/mcp/tools/timeline.d.ts +0 -9
- package/dist/mcp/tools/timeline.js +0 -65
- package/dist/mcp/tools/timeline.js.map +0 -1
- package/dist/mcp/tools/types.d.ts +0 -9
- package/dist/mcp/tools/types.js +0 -2
- package/dist/mcp/tools/types.js.map +0 -1
- package/dist/mirror/clone.d.ts +0 -6
- package/dist/mirror/clone.js +0 -48
- package/dist/mirror/clone.js.map +0 -1
- package/dist/mirror/dirty-overlap.d.ts +0 -13
- package/dist/mirror/dirty-overlap.js +0 -77
- package/dist/mirror/dirty-overlap.js.map +0 -1
- package/dist/mirror/index.d.ts +0 -7
- package/dist/mirror/index.js +0 -7
- package/dist/mirror/index.js.map +0 -1
- package/dist/mirror/paths.d.ts +0 -18
- package/dist/mirror/paths.js +0 -45
- package/dist/mirror/paths.js.map +0 -1
- package/dist/mirror/push.d.ts +0 -9
- package/dist/mirror/push.js +0 -27
- package/dist/mirror/push.js.map +0 -1
- package/dist/mirror/state.d.ts +0 -4
- package/dist/mirror/state.js +0 -36
- package/dist/mirror/state.js.map +0 -1
- package/dist/mirror/sync.d.ts +0 -9
- package/dist/mirror/sync.js +0 -33
- package/dist/mirror/sync.js.map +0 -1
- package/dist/mirror/types.d.ts +0 -77
- package/dist/mirror/types.js +0 -2
- package/dist/mirror/types.js.map +0 -1
- package/dist/orchestrator/activity-summarizer.d.ts +0 -33
- package/dist/orchestrator/activity-summarizer.js +0 -120
- package/dist/orchestrator/activity-summarizer.js.map +0 -1
- package/dist/orchestrator/inbox.d.ts +0 -78
- package/dist/orchestrator/inbox.js +0 -115
- package/dist/orchestrator/inbox.js.map +0 -1
- package/dist/orchestrator/index.d.ts +0 -9
- package/dist/orchestrator/index.js +0 -7
- package/dist/orchestrator/index.js.map +0 -1
- package/dist/orchestrator/orchestrator.d.ts +0 -154
- package/dist/orchestrator/orchestrator.js +0 -2437
- package/dist/orchestrator/orchestrator.js.map +0 -1
- package/dist/orchestrator/prompt.d.ts +0 -19
- package/dist/orchestrator/prompt.js +0 -50
- package/dist/orchestrator/prompt.js.map +0 -1
- package/dist/orchestrator/queue.d.ts +0 -21
- package/dist/orchestrator/queue.js +0 -80
- package/dist/orchestrator/queue.js.map +0 -1
- package/dist/orchestrator/run-log.d.ts +0 -53
- package/dist/orchestrator/run-log.js +0 -92
- package/dist/orchestrator/run-log.js.map +0 -1
- package/dist/orchestrator/runner.d.ts +0 -56
- package/dist/orchestrator/runner.js +0 -172
- package/dist/orchestrator/runner.js.map +0 -1
- package/dist/orchestrator/tool-digest.d.ts +0 -35
- package/dist/orchestrator/tool-digest.js +0 -116
- package/dist/orchestrator/tool-digest.js.map +0 -1
- package/dist/orchestrator/types.d.ts +0 -263
- package/dist/orchestrator/types.js +0 -2
- package/dist/orchestrator/types.js.map +0 -1
- package/dist/orchestrator/workspace.d.ts +0 -21
- package/dist/orchestrator/workspace.js +0 -31
- package/dist/orchestrator/workspace.js.map +0 -1
- package/dist/profiles/index.d.ts +0 -3
- package/dist/profiles/index.js +0 -3
- package/dist/profiles/index.js.map +0 -1
- package/dist/profiles/registry.d.ts +0 -5
- package/dist/profiles/registry.js +0 -31
- package/dist/profiles/registry.js.map +0 -1
- package/dist/profiles/types.d.ts +0 -48
- package/dist/profiles/types.js +0 -11
- package/dist/profiles/types.js.map +0 -1
- package/dist/profiles/unknown.d.ts +0 -9
- package/dist/profiles/unknown.js +0 -17
- package/dist/profiles/unknown.js.map +0 -1
- package/dist/reviewer/index.d.ts +0 -6
- package/dist/reviewer/index.js +0 -5
- package/dist/reviewer/index.js.map +0 -1
- package/dist/reviewer/prompt.d.ts +0 -11
- package/dist/reviewer/prompt.js +0 -132
- package/dist/reviewer/prompt.js.map +0 -1
- package/dist/reviewer/remediation.d.ts +0 -15
- package/dist/reviewer/remediation.js +0 -61
- package/dist/reviewer/remediation.js.map +0 -1
- package/dist/reviewer/reviewer.d.ts +0 -9
- package/dist/reviewer/reviewer.js +0 -89
- package/dist/reviewer/reviewer.js.map +0 -1
- package/dist/reviewer/schema.d.ts +0 -45
- package/dist/reviewer/schema.js +0 -43
- package/dist/reviewer/schema.js.map +0 -1
- package/dist/reviewer/types.d.ts +0 -74
- package/dist/reviewer/types.js +0 -14
- package/dist/reviewer/types.js.map +0 -1
- package/dist/sensors/attestation.d.ts +0 -44
- package/dist/sensors/attestation.js +0 -262
- package/dist/sensors/attestation.js.map +0 -1
- package/dist/sensors/catalog.d.ts +0 -41
- package/dist/sensors/catalog.js +0 -123
- package/dist/sensors/catalog.js.map +0 -1
- package/dist/sensors/decisions.d.ts +0 -30
- package/dist/sensors/decisions.js +0 -393
- package/dist/sensors/decisions.js.map +0 -1
- package/dist/sensors/diff.d.ts +0 -27
- package/dist/sensors/diff.js +0 -148
- package/dist/sensors/diff.js.map +0 -1
- package/dist/sensors/index.d.ts +0 -13
- package/dist/sensors/index.js +0 -9
- package/dist/sensors/index.js.map +0 -1
- package/dist/sensors/remediation.d.ts +0 -20
- package/dist/sensors/remediation.js +0 -65
- package/dist/sensors/remediation.js.map +0 -1
- package/dist/sensors/runner.d.ts +0 -44
- package/dist/sensors/runner.js +0 -95
- package/dist/sensors/runner.js.map +0 -1
- package/dist/sensors/structural.d.ts +0 -30
- package/dist/sensors/structural.js +0 -204
- package/dist/sensors/structural.js.map +0 -1
- package/dist/sensors/stub-catalog.d.ts +0 -39
- package/dist/sensors/stub-catalog.js +0 -115
- package/dist/sensors/stub-catalog.js.map +0 -1
- package/dist/sensors/types.d.ts +0 -135
- package/dist/sensors/types.js +0 -14
- package/dist/sensors/types.js.map +0 -1
- package/dist/tier0/classify.d.ts +0 -5
- package/dist/tier0/classify.js +0 -91
- package/dist/tier0/classify.js.map +0 -1
- package/dist/tier0/index.d.ts +0 -3
- package/dist/tier0/index.js +0 -3
- package/dist/tier0/index.js.map +0 -1
- package/dist/tier0/ollama.d.ts +0 -22
- package/dist/tier0/ollama.js +0 -63
- package/dist/tier0/ollama.js.map +0 -1
- package/dist/tier0/types.d.ts +0 -24
- package/dist/tier0/types.js +0 -7
- package/dist/tier0/types.js.map +0 -1
- package/dist/tightener/index.d.ts +0 -4
- package/dist/tightener/index.js +0 -4
- package/dist/tightener/index.js.map +0 -1
- package/dist/tightener/prompt.d.ts +0 -3
- package/dist/tightener/prompt.js +0 -67
- package/dist/tightener/prompt.js.map +0 -1
- package/dist/tightener/schema.d.ts +0 -68
- package/dist/tightener/schema.js +0 -44
- package/dist/tightener/schema.js.map +0 -1
- package/dist/tightener/tighten.d.ts +0 -2
- package/dist/tightener/tighten.js +0 -66
- package/dist/tightener/tighten.js.map +0 -1
- package/dist/tightener/types.d.ts +0 -74
- package/dist/tightener/types.js +0 -6
- package/dist/tightener/types.js.map +0 -1
- package/dist/uat/bundle.d.ts +0 -68
- package/dist/uat/bundle.js +0 -168
- package/dist/uat/bundle.js.map +0 -1
- package/dist/uat/index.d.ts +0 -15
- package/dist/uat/index.js +0 -10
- package/dist/uat/index.js.map +0 -1
- package/dist/uat/persistent.d.ts +0 -64
- package/dist/uat/persistent.js +0 -206
- package/dist/uat/persistent.js.map +0 -1
- package/dist/uat/probes/cli.d.ts +0 -11
- package/dist/uat/probes/cli.js +0 -107
- package/dist/uat/probes/cli.js.map +0 -1
- package/dist/uat/probes/http.d.ts +0 -12
- package/dist/uat/probes/http.js +0 -139
- package/dist/uat/probes/http.js.map +0 -1
- package/dist/uat/probes/index.d.ts +0 -21
- package/dist/uat/probes/index.js +0 -30
- package/dist/uat/probes/index.js.map +0 -1
- package/dist/uat/probes/integration.d.ts +0 -18
- package/dist/uat/probes/integration.js +0 -188
- package/dist/uat/probes/integration.js.map +0 -1
- package/dist/uat/probes/sql/config.d.ts +0 -14
- package/dist/uat/probes/sql/config.js +0 -57
- package/dist/uat/probes/sql/config.js.map +0 -1
- package/dist/uat/probes/sql/index.d.ts +0 -29
- package/dist/uat/probes/sql/index.js +0 -43
- package/dist/uat/probes/sql/index.js.map +0 -1
- package/dist/uat/probes/sql/mysql.d.ts +0 -12
- package/dist/uat/probes/sql/mysql.js +0 -96
- package/dist/uat/probes/sql/mysql.js.map +0 -1
- package/dist/uat/probes/sql/pg.d.ts +0 -20
- package/dist/uat/probes/sql/pg.js +0 -102
- package/dist/uat/probes/sql/pg.js.map +0 -1
- package/dist/uat/probes/sql/sqlite.d.ts +0 -9
- package/dist/uat/probes/sql/sqlite.js +0 -58
- package/dist/uat/probes/sql/sqlite.js.map +0 -1
- package/dist/uat/probes/sql/types.d.ts +0 -46
- package/dist/uat/probes/sql/types.js +0 -10
- package/dist/uat/probes/sql/types.js.map +0 -1
- package/dist/uat/probes/sql.d.ts +0 -9
- package/dist/uat/probes/sql.js +0 -119
- package/dist/uat/probes/sql.js.map +0 -1
- package/dist/uat/probes/ui.d.ts +0 -19
- package/dist/uat/probes/ui.js +0 -244
- package/dist/uat/probes/ui.js.map +0 -1
- package/dist/uat/prompt.d.ts +0 -10
- package/dist/uat/prompt.js +0 -85
- package/dist/uat/prompt.js.map +0 -1
- package/dist/uat/question.d.ts +0 -50
- package/dist/uat/question.js +0 -139
- package/dist/uat/question.js.map +0 -1
- package/dist/uat/rejection.d.ts +0 -58
- package/dist/uat/rejection.js +0 -163
- package/dist/uat/rejection.js.map +0 -1
- package/dist/uat/runner.d.ts +0 -6
- package/dist/uat/runner.js +0 -96
- package/dist/uat/runner.js.map +0 -1
- package/dist/uat/schema.d.ts +0 -322
- package/dist/uat/schema.js +0 -189
- package/dist/uat/schema.js.map +0 -1
- package/dist/uat/types.d.ts +0 -268
- package/dist/uat/types.js +0 -18
- package/dist/uat/types.js.map +0 -1
- package/dist/uat/uat.d.ts +0 -89
- package/dist/uat/uat.js +0 -256
- package/dist/uat/uat.js.map +0 -1
- package/dist/voice/index.d.ts +0 -4
- package/dist/voice/index.js +0 -4
- package/dist/voice/index.js.map +0 -1
- package/dist/voice/model.d.ts +0 -23
- package/dist/voice/model.js +0 -46
- package/dist/voice/model.js.map +0 -1
- package/dist/voice/pipe.d.ts +0 -9
- package/dist/voice/pipe.js +0 -47
- package/dist/voice/pipe.js.map +0 -1
- package/dist/voice/transcribe.d.ts +0 -3
- package/dist/voice/transcribe.js +0 -43
- package/dist/voice/transcribe.js.map +0 -1
- package/dist/voice/types.d.ts +0 -26
- package/dist/voice/types.js +0 -9
- package/dist/voice/types.js.map +0 -1
- package/dist/watch/daemon.d.ts +0 -21
- package/dist/watch/daemon.js +0 -143
- package/dist/watch/daemon.js.map +0 -1
- package/dist/watch/index.d.ts +0 -4
- package/dist/watch/index.js +0 -3
- package/dist/watch/index.js.map +0 -1
- package/dist/watch/regenerate.d.ts +0 -25
- package/dist/watch/regenerate.js +0 -51
- package/dist/watch/regenerate.js.map +0 -1
|
@@ -1,1104 +0,0 @@
|
|
|
1
|
-
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, EmbedBuilder, Events, GatewayIntentBits, Partials, } from "discord.js";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { logger } from "../../logger.js";
|
|
4
|
-
import { writeInboxRow } from "../inbox.js";
|
|
5
|
-
import { classifyTier0 } from "../../tier0/index.js";
|
|
6
|
-
import { transcribeUrl, whisperModelExists } from "../../voice/index.js";
|
|
7
|
-
import { isOwner, parseOwnerIds } from "./acl.js";
|
|
8
|
-
import { CATEGORY_NAMES, createTaskChannel, ensureCategories, moveChannelToCategory, slugifyForChannel, } from "./channels.js";
|
|
9
|
-
import { registerSlashCommands, SLASH_COMMAND_NAMES } from "./slash.js";
|
|
10
|
-
const log = logger("frontend.discord");
|
|
11
|
-
const VOICE_MIME_PREFIXES = ["audio/", "video/ogg"];
|
|
12
|
-
/**
|
|
13
|
-
* Real Discord adapter. Implements `FrontendAdapter`. Phase 5 wires:
|
|
14
|
-
* - slash command surface per `WORKFLOW_GUIDE.md` ยง3
|
|
15
|
-
* - channel-per-task lifecycle in ๐/๐ข/๐ฆ categories
|
|
16
|
-
* - ACL on `DISCORD_OWNER_USER_IDS`
|
|
17
|
-
* - free-text โ regex Tier-0 stub (real Ollama in Phase 6)
|
|
18
|
-
* - voice attachments โ inbox row only (Whisper transcription Phase 6)
|
|
19
|
-
* - button interactions for approval / dialog round-trips
|
|
20
|
-
*
|
|
21
|
-
* Inbox rows are the only output Phase 5 produces โ the orchestrator (Phase
|
|
22
|
-
* 8) is the consumer. No code dispatch lives here.
|
|
23
|
-
*/
|
|
24
|
-
export class DiscordFrontendAdapter {
|
|
25
|
-
name = "discord";
|
|
26
|
-
opts;
|
|
27
|
-
ownerIds;
|
|
28
|
-
client;
|
|
29
|
-
confidenceFloor;
|
|
30
|
-
tier0Opts;
|
|
31
|
-
taskHandler;
|
|
32
|
-
voiceHandler;
|
|
33
|
-
slashHandler;
|
|
34
|
-
freeTextHandler;
|
|
35
|
-
interactionHandler;
|
|
36
|
-
pendingApprovals = new Map();
|
|
37
|
-
pendingDialogs = new Map();
|
|
38
|
-
/**
|
|
39
|
-
* ยง3.4 win 1 โ bundleId โ messageId for in-place dialog edit. When a
|
|
40
|
-
* follow-up DialogSpec arrives with `replaceBundleId` set, the adapter
|
|
41
|
-
* fetches that message and edits it (new prompt + new buttons) instead
|
|
42
|
-
* of posting a fresh one. Single edited message replaces the N+1
|
|
43
|
-
* scroll explosion.
|
|
44
|
-
*/
|
|
45
|
-
dialogMessageIds = new Map();
|
|
46
|
-
/**
|
|
47
|
-
* In-memory map of taskId โ live status messageId. The live status
|
|
48
|
-
* message is one embed per task that gets edited in place on each
|
|
49
|
-
* postTaskUpdate call (instead of spamming the channel with a new
|
|
50
|
-
* message per phase). Restart loses the mapping; a fresh run posts a
|
|
51
|
-
* new live status message.
|
|
52
|
-
*/
|
|
53
|
-
liveStatusMessages = new Map();
|
|
54
|
-
/**
|
|
55
|
-
* Channels that have responded with Unknown Channel (Discord error
|
|
56
|
-
* 10003). Once flagged dead, subsequent postTaskUpdate / sendTyping
|
|
57
|
-
* / requestDialog calls bail without hitting Discord. This stops the
|
|
58
|
-
* 8s typing heartbeat from spamming logs after the operator deletes
|
|
59
|
-
* a per-task channel mid-run.
|
|
60
|
-
*/
|
|
61
|
-
deadChannels = new Set();
|
|
62
|
-
started = false;
|
|
63
|
-
constructor(opts) {
|
|
64
|
-
this.opts = opts;
|
|
65
|
-
this.confidenceFloor = opts.confidenceFloor ?? 0.85;
|
|
66
|
-
this.tier0Opts = opts.tier0 ?? {};
|
|
67
|
-
this.ownerIds = parseOwnerIds(opts.ownerUserIdsEnv);
|
|
68
|
-
if (this.ownerIds.size === 0) {
|
|
69
|
-
log.warn("DISCORD_OWNER_USER_IDS empty โ no commands will be accepted; configure before live use");
|
|
70
|
-
}
|
|
71
|
-
this.client = new Client({
|
|
72
|
-
intents: [
|
|
73
|
-
GatewayIntentBits.Guilds,
|
|
74
|
-
GatewayIntentBits.GuildMessages,
|
|
75
|
-
GatewayIntentBits.MessageContent,
|
|
76
|
-
GatewayIntentBits.GuildMessageReactions,
|
|
77
|
-
GatewayIntentBits.DirectMessages,
|
|
78
|
-
GatewayIntentBits.DirectMessageReactions,
|
|
79
|
-
],
|
|
80
|
-
partials: [Partials.Channel, Partials.Message, Partials.Reaction],
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
async start() {
|
|
84
|
-
if (this.started)
|
|
85
|
-
return;
|
|
86
|
-
this.client.on(Events.InteractionCreate, (i) => {
|
|
87
|
-
void this.handleInteraction(i);
|
|
88
|
-
});
|
|
89
|
-
this.client.on(Events.MessageCreate, (m) => {
|
|
90
|
-
void this.handleMessage(m);
|
|
91
|
-
});
|
|
92
|
-
this.client.once(Events.ClientReady, (c) => {
|
|
93
|
-
log.info({ user: c.user.tag, guildId: this.opts.guildId }, "discord client ready");
|
|
94
|
-
});
|
|
95
|
-
await this.client.login(this.opts.token);
|
|
96
|
-
if (!this.opts.skipSlashRegistration) {
|
|
97
|
-
const appId = this.opts.applicationId ?? this.client.application?.id;
|
|
98
|
-
if (!appId) {
|
|
99
|
-
await this.client.destroy();
|
|
100
|
-
throw new Error("could not resolve application id for slash registration");
|
|
101
|
-
}
|
|
102
|
-
const registered = await registerSlashCommands({
|
|
103
|
-
token: this.opts.token,
|
|
104
|
-
appId,
|
|
105
|
-
guildId: this.opts.guildId,
|
|
106
|
-
});
|
|
107
|
-
log.info({ count: registered.length }, "slash commands registered");
|
|
108
|
-
}
|
|
109
|
-
const guild = await this.requireGuild();
|
|
110
|
-
await ensureCategories(guild);
|
|
111
|
-
log.info({ categories: Object.values(CATEGORY_NAMES) }, "categories ensured");
|
|
112
|
-
this.started = true;
|
|
113
|
-
}
|
|
114
|
-
async stop() {
|
|
115
|
-
if (!this.started)
|
|
116
|
-
return;
|
|
117
|
-
for (const pending of this.pendingApprovals.values()) {
|
|
118
|
-
clearTimeout(pending.timeoutHandle);
|
|
119
|
-
pending.resolve({
|
|
120
|
-
bundleId: pending.bundleId,
|
|
121
|
-
decision: "ask",
|
|
122
|
-
timedOut: true,
|
|
123
|
-
reason: "adapter stopped",
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
this.pendingApprovals.clear();
|
|
127
|
-
for (const pending of this.pendingDialogs.values()) {
|
|
128
|
-
clearTimeout(pending.timeoutHandle);
|
|
129
|
-
pending.resolve({
|
|
130
|
-
bundleId: pending.bundleId,
|
|
131
|
-
choiceId: "e_other",
|
|
132
|
-
timedOut: true,
|
|
133
|
-
freeText: "(adapter stopped before reply)",
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
this.pendingDialogs.clear();
|
|
137
|
-
this.dialogMessageIds.clear();
|
|
138
|
-
await this.client.destroy();
|
|
139
|
-
this.started = false;
|
|
140
|
-
log.info("discord adapter stopped");
|
|
141
|
-
}
|
|
142
|
-
onTask(handler) {
|
|
143
|
-
this.taskHandler = handler;
|
|
144
|
-
}
|
|
145
|
-
onVoice(handler) {
|
|
146
|
-
this.voiceHandler = handler;
|
|
147
|
-
}
|
|
148
|
-
onSlash(handler) {
|
|
149
|
-
this.slashHandler = handler;
|
|
150
|
-
}
|
|
151
|
-
onFreeText(handler) {
|
|
152
|
-
this.freeTextHandler = handler;
|
|
153
|
-
}
|
|
154
|
-
onInteraction(handler) {
|
|
155
|
-
this.interactionHandler = handler;
|
|
156
|
-
}
|
|
157
|
-
async postTaskUpdate(update) {
|
|
158
|
-
const channelId = update.channelId;
|
|
159
|
-
if (!channelId) {
|
|
160
|
-
log.warn({ taskId: update.taskId }, "postTaskUpdate without channelId; dropping");
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (this.isChannelDead(channelId))
|
|
164
|
-
return;
|
|
165
|
-
let channel;
|
|
166
|
-
try {
|
|
167
|
-
channel = await this.client.channels.fetch(channelId);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
if (isUnknownChannelError(err)) {
|
|
171
|
-
this.markChannelDead(channelId);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
throw err;
|
|
175
|
-
}
|
|
176
|
-
if (!channel || !channel.isTextBased() || !("send" in channel)) {
|
|
177
|
-
log.warn({ channelId }, "postTaskUpdate target channel not text-based");
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const text = channel;
|
|
181
|
-
const embed = buildPhaseEmbed(update);
|
|
182
|
-
// ยง3.3 win 3 โ single edited live-status embed; no secondary content
|
|
183
|
-
// post. body / activity / tools / recentEvents all render inside this
|
|
184
|
-
// one embed via fields. Long bodies are truncated to 1024 chars + a
|
|
185
|
-
// pointer to log.jsonl (operator can grep the full content there).
|
|
186
|
-
const existingMsgId = this.liveStatusMessages.get(update.taskId);
|
|
187
|
-
if (existingMsgId !== undefined) {
|
|
188
|
-
try {
|
|
189
|
-
const msg = await text.messages.fetch(existingMsgId);
|
|
190
|
-
await msg.edit({ embeds: [embed] });
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
log.warn({ err: String(err), taskId: update.taskId }, "live status edit failed; recreating");
|
|
195
|
-
this.liveStatusMessages.delete(update.taskId);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
const sent = await text.send({ embeds: [embed] });
|
|
200
|
-
this.liveStatusMessages.set(update.taskId, sent.id);
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
log.warn({ err: String(err), taskId: update.taskId }, "live status send failed");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
async requestApproval(bundle) {
|
|
207
|
-
const channelId = bundle.channelId;
|
|
208
|
-
if (!channelId) {
|
|
209
|
-
throw new Error("requestApproval requires bundle.channelId");
|
|
210
|
-
}
|
|
211
|
-
if (this.isChannelDead(channelId)) {
|
|
212
|
-
// Same dead-channel sentinel as requestDialog โ orchestrator
|
|
213
|
-
// treats timeout as ask/abandon.
|
|
214
|
-
return { bundleId: bundle.bundleId, decision: "ask", timedOut: true };
|
|
215
|
-
}
|
|
216
|
-
let channel;
|
|
217
|
-
try {
|
|
218
|
-
channel = await this.client.channels.fetch(channelId);
|
|
219
|
-
}
|
|
220
|
-
catch (err) {
|
|
221
|
-
if (isUnknownChannelError(err)) {
|
|
222
|
-
this.markChannelDead(channelId);
|
|
223
|
-
return { bundleId: bundle.bundleId, decision: "ask", timedOut: true };
|
|
224
|
-
}
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
if (!channel || !("send" in channel) || !channel.isTextBased()) {
|
|
228
|
-
throw new Error(`approval target channel not text-based: ${channelId}`);
|
|
229
|
-
}
|
|
230
|
-
const buttonIds = {
|
|
231
|
-
approve: `harness:approve:${bundle.bundleId}`,
|
|
232
|
-
reject: `harness:reject:${bundle.bundleId}`,
|
|
233
|
-
ask: `harness:ask:${bundle.bundleId}`,
|
|
234
|
-
};
|
|
235
|
-
const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
|
|
236
|
-
.setCustomId(buttonIds.approve)
|
|
237
|
-
.setStyle(ButtonStyle.Success)
|
|
238
|
-
.setLabel("Approve & Push")
|
|
239
|
-
.setEmoji("๐ข"), new ButtonBuilder()
|
|
240
|
-
.setCustomId(buttonIds.reject)
|
|
241
|
-
.setStyle(ButtonStyle.Danger)
|
|
242
|
-
.setLabel("Reject + tell me why")
|
|
243
|
-
.setEmoji("๐ด"), new ButtonBuilder()
|
|
244
|
-
.setCustomId(buttonIds.ask)
|
|
245
|
-
.setStyle(ButtonStyle.Secondary)
|
|
246
|
-
.setLabel("Ask follow-up")
|
|
247
|
-
.setEmoji("โ"));
|
|
248
|
-
const embed = new EmbedBuilder()
|
|
249
|
-
.setTitle(`๐ฌ UAT โ ${bundle.runId}`)
|
|
250
|
-
.setColor(0x1abc9c)
|
|
251
|
-
.setDescription(bundle.goal)
|
|
252
|
-
.setTimestamp(new Date());
|
|
253
|
-
if (bundle.diffSummary) {
|
|
254
|
-
embed.addFields({ name: "diff", value: bundle.diffSummary.slice(0, 1024) });
|
|
255
|
-
}
|
|
256
|
-
if (bundle.acceptance && bundle.acceptance.length > 0) {
|
|
257
|
-
const acText = bundle.acceptance
|
|
258
|
-
.map((ac) => {
|
|
259
|
-
const mark = ac.status === "pass" ? "โ" : ac.status === "fail" ? "โ" : "โ";
|
|
260
|
-
return `${mark} ${ac.id}${ac.note ? ` โ ${ac.note}` : ""}`;
|
|
261
|
-
})
|
|
262
|
-
.join("\n");
|
|
263
|
-
embed.addFields({ name: "acceptance", value: acText.slice(0, 1024) });
|
|
264
|
-
}
|
|
265
|
-
const sentApproval = await channel.send({
|
|
266
|
-
embeds: [embed],
|
|
267
|
-
components: [row],
|
|
268
|
-
});
|
|
269
|
-
try {
|
|
270
|
-
await sentApproval.react("๐");
|
|
271
|
-
}
|
|
272
|
-
catch (err) {
|
|
273
|
-
log.warn({ err: String(err) }, "approval ๐ react failed");
|
|
274
|
-
}
|
|
275
|
-
const timeoutMs = bundle.timeoutMs ?? 24 * 60 * 60 * 1000;
|
|
276
|
-
return new Promise((resolve) => {
|
|
277
|
-
const timeoutHandle = setTimeout(() => {
|
|
278
|
-
this.pendingApprovals.delete(bundle.bundleId);
|
|
279
|
-
resolve({ bundleId: bundle.bundleId, decision: "ask", timedOut: true });
|
|
280
|
-
}, timeoutMs);
|
|
281
|
-
this.pendingApprovals.set(bundle.bundleId, {
|
|
282
|
-
bundleId: bundle.bundleId,
|
|
283
|
-
resolve,
|
|
284
|
-
timeoutHandle,
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
async requestDialog(spec) {
|
|
289
|
-
const channelId = spec.channelId;
|
|
290
|
-
if (!channelId) {
|
|
291
|
-
throw new Error("requestDialog requires spec.channelId");
|
|
292
|
-
}
|
|
293
|
-
if (this.isChannelDead(channelId)) {
|
|
294
|
-
// Operator can't see the prompt โ return a dead-channel sentinel
|
|
295
|
-
// (timed-out so the orchestrator's existing timeout-handling
|
|
296
|
-
// branch fires).
|
|
297
|
-
return { bundleId: spec.bundleId, choiceId: "e_other", timedOut: true };
|
|
298
|
-
}
|
|
299
|
-
let channel;
|
|
300
|
-
try {
|
|
301
|
-
channel = await this.client.channels.fetch(channelId);
|
|
302
|
-
}
|
|
303
|
-
catch (err) {
|
|
304
|
-
if (isUnknownChannelError(err)) {
|
|
305
|
-
this.markChannelDead(channelId);
|
|
306
|
-
return { bundleId: spec.bundleId, choiceId: "e_other", timedOut: true };
|
|
307
|
-
}
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
if (!channel || !("send" in channel) || !channel.isTextBased()) {
|
|
311
|
-
throw new Error(`dialog target channel not text-based: ${channelId}`);
|
|
312
|
-
}
|
|
313
|
-
const choiceMap = new Map();
|
|
314
|
-
const choiceLabels = new Map();
|
|
315
|
-
const builders = [];
|
|
316
|
-
for (const choice of spec.choices.slice(0, 5)) {
|
|
317
|
-
const buttonId = `harness:dialog:${spec.bundleId}:${choice.id}`;
|
|
318
|
-
choiceMap.set(buttonId, choice.id);
|
|
319
|
-
choiceLabels.set(choice.id, choice.label);
|
|
320
|
-
builders.push(new ButtonBuilder()
|
|
321
|
-
.setCustomId(buttonId)
|
|
322
|
-
.setStyle(ButtonStyle.Secondary)
|
|
323
|
-
.setLabel(choice.label.slice(0, 80)));
|
|
324
|
-
}
|
|
325
|
-
const row = new ActionRowBuilder().addComponents(...builders);
|
|
326
|
-
// Plain content has a 2000-char cap, but operator-walked dialogs
|
|
327
|
-
// can include 4 long ambiguity candidates that blow past it.
|
|
328
|
-
// Discord embeds get 4096 chars in the description, so longer
|
|
329
|
-
// prompts go there. Short prompts stay as content for the
|
|
330
|
-
// visually-cleaner default rendering.
|
|
331
|
-
const pingPrefix = spec.pingOperators === true && this.ownerIds.size > 0
|
|
332
|
-
? Array.from(this.ownerIds)
|
|
333
|
-
.map((id) => `<@${id}>`)
|
|
334
|
-
.join(" ") + " "
|
|
335
|
-
: "";
|
|
336
|
-
const sendOpts = { components: [row] };
|
|
337
|
-
const useEmbed = spec.prompt.length > 1800;
|
|
338
|
-
if (useEmbed) {
|
|
339
|
-
sendOpts.embeds = [
|
|
340
|
-
new EmbedBuilder()
|
|
341
|
-
.setDescription(spec.prompt.slice(0, 4096))
|
|
342
|
-
.setColor(0x3498db),
|
|
343
|
-
];
|
|
344
|
-
// Pings only render in `content`, not embed description, so
|
|
345
|
-
// when we use an embed for body, ping prefix lives on content.
|
|
346
|
-
if (pingPrefix.length > 0)
|
|
347
|
-
sendOpts.content = pingPrefix.trim();
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
sendOpts.content = pingPrefix + spec.prompt;
|
|
351
|
-
}
|
|
352
|
-
// ยง3.4 win 1 โ try to edit the previous walk step's message in place.
|
|
353
|
-
let messageId;
|
|
354
|
-
if (spec.replaceBundleId !== undefined) {
|
|
355
|
-
const prevMsgId = this.dialogMessageIds.get(spec.replaceBundleId);
|
|
356
|
-
if (prevMsgId !== undefined) {
|
|
357
|
-
try {
|
|
358
|
-
const prevMsg = await channel.messages.fetch(prevMsgId);
|
|
359
|
-
await prevMsg.edit({
|
|
360
|
-
content: sendOpts.content ?? "",
|
|
361
|
-
embeds: sendOpts.embeds ?? [],
|
|
362
|
-
components: sendOpts.components,
|
|
363
|
-
});
|
|
364
|
-
messageId = prevMsg.id;
|
|
365
|
-
}
|
|
366
|
-
catch (err) {
|
|
367
|
-
log.warn({ err: String(err), prev: spec.replaceBundleId }, "dialog edit-in-place failed; falling back to fresh send");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (messageId === undefined) {
|
|
372
|
-
const sentMessage = await channel.send(sendOpts);
|
|
373
|
-
messageId = sentMessage.id;
|
|
374
|
-
try {
|
|
375
|
-
await sentMessage.react("๐");
|
|
376
|
-
}
|
|
377
|
-
catch (err) {
|
|
378
|
-
log.warn({ err: String(err), bundleId: spec.bundleId }, "dialog ๐ react failed");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
this.dialogMessageIds.set(spec.bundleId, messageId);
|
|
382
|
-
if (spec.replaceBundleId !== undefined &&
|
|
383
|
-
spec.replaceBundleId !== spec.bundleId) {
|
|
384
|
-
this.dialogMessageIds.delete(spec.replaceBundleId);
|
|
385
|
-
}
|
|
386
|
-
const timeoutMs = spec.timeoutMs ?? 5 * 60 * 1000;
|
|
387
|
-
const compactOnAnswer = spec.compactOnAnswer !== false;
|
|
388
|
-
return new Promise((resolve) => {
|
|
389
|
-
const timeoutHandle = setTimeout(() => {
|
|
390
|
-
this.pendingDialogs.delete(spec.bundleId);
|
|
391
|
-
this.dialogMessageIds.delete(spec.bundleId);
|
|
392
|
-
resolve({ bundleId: spec.bundleId, choiceId: "e_other", timedOut: true });
|
|
393
|
-
}, timeoutMs);
|
|
394
|
-
this.pendingDialogs.set(spec.bundleId, {
|
|
395
|
-
bundleId: spec.bundleId,
|
|
396
|
-
resolve,
|
|
397
|
-
choiceMap,
|
|
398
|
-
choiceLabels,
|
|
399
|
-
timeoutHandle,
|
|
400
|
-
compactOnAnswer,
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
async isChannelAlive(channelId) {
|
|
405
|
-
if (this.isChannelDead(channelId))
|
|
406
|
-
return false;
|
|
407
|
-
try {
|
|
408
|
-
const ch = await this.client.channels.fetch(channelId);
|
|
409
|
-
return ch !== null;
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
if (isUnknownChannelError(err)) {
|
|
413
|
-
this.markChannelDead(channelId);
|
|
414
|
-
}
|
|
415
|
-
return false;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
startTyping(channelId) {
|
|
419
|
-
let stopped = false;
|
|
420
|
-
const ping = async () => {
|
|
421
|
-
if (stopped)
|
|
422
|
-
return;
|
|
423
|
-
if (this.isChannelDead(channelId)) {
|
|
424
|
-
stopped = true;
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
try {
|
|
428
|
-
const channel = await this.client.channels.fetch(channelId);
|
|
429
|
-
if (channel && channel.isTextBased() && "sendTyping" in channel) {
|
|
430
|
-
await channel.sendTyping();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
if (isUnknownChannelError(err)) {
|
|
435
|
-
this.markChannelDead(channelId);
|
|
436
|
-
stopped = true;
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
log.warn({ err: String(err), channelId }, "sendTyping failed");
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
void ping();
|
|
443
|
-
// Discord's typing indicator decays after ~10s; refresh every 8s.
|
|
444
|
-
const handle = setInterval(() => void ping(), 8_000);
|
|
445
|
-
return () => {
|
|
446
|
-
stopped = true;
|
|
447
|
-
clearInterval(handle);
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
async notify(level, message) {
|
|
451
|
-
log[level === "warn" ? "warn" : level === "error" ? "error" : "info"]({ message }, "frontend notify");
|
|
452
|
-
// No default channel for ad-hoc notifications. Discord-side surfacing
|
|
453
|
-
// belongs to per-run channels (postTaskUpdate). This is a logging
|
|
454
|
-
// fallback โ Phase 5 has no system channel concept.
|
|
455
|
-
}
|
|
456
|
-
// โโ private โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
457
|
-
/**
|
|
458
|
-
* Discord error 10003 = Unknown Channel. Operator deleted the
|
|
459
|
-
* per-task channel after the harness recorded its id. Once flagged,
|
|
460
|
-
* silently no-op on every subsequent send/edit/typing call so we
|
|
461
|
-
* don't spam logs while the orchestrator winds the run down.
|
|
462
|
-
*/
|
|
463
|
-
isChannelDead(channelId) {
|
|
464
|
-
return this.deadChannels.has(channelId);
|
|
465
|
-
}
|
|
466
|
-
markChannelDead(channelId) {
|
|
467
|
-
if (!this.deadChannels.has(channelId)) {
|
|
468
|
-
this.deadChannels.add(channelId);
|
|
469
|
-
log.warn({ channelId }, "channel marked dead โ operator likely deleted it; further posts/typing/dialogs will no-op");
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
async requireGuild() {
|
|
473
|
-
const guild = await this.client.guilds.fetch(this.opts.guildId);
|
|
474
|
-
if (!guild) {
|
|
475
|
-
throw new Error(`guild not accessible: ${this.opts.guildId}`);
|
|
476
|
-
}
|
|
477
|
-
return guild;
|
|
478
|
-
}
|
|
479
|
-
async handleInteraction(interaction) {
|
|
480
|
-
try {
|
|
481
|
-
if (interaction.isChatInputCommand()) {
|
|
482
|
-
await this.handleSlash(interaction);
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
if (interaction.isButton()) {
|
|
486
|
-
await this.handleButton(interaction);
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
log.error({ err: String(err) }, "interaction handler threw");
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
async handleSlash(interaction) {
|
|
495
|
-
const command = interaction.commandName;
|
|
496
|
-
const userId = interaction.user.id;
|
|
497
|
-
if (!isOwner(this.ownerIds, userId)) {
|
|
498
|
-
await interaction.reply({
|
|
499
|
-
content: "Not authorized.",
|
|
500
|
-
ephemeral: true,
|
|
501
|
-
});
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (!SLASH_COMMAND_NAMES.includes(command)) {
|
|
505
|
-
await interaction.reply({ content: `Unknown command: ${command}`, ephemeral: true });
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
const options = {};
|
|
509
|
-
for (const opt of interaction.options.data) {
|
|
510
|
-
if (opt.value !== undefined && opt.value !== null)
|
|
511
|
-
options[opt.name] = opt.value;
|
|
512
|
-
}
|
|
513
|
-
const slashEvent = {
|
|
514
|
-
source: this.name,
|
|
515
|
-
command,
|
|
516
|
-
options,
|
|
517
|
-
authorId: userId,
|
|
518
|
-
receivedAt: new Date().toISOString(),
|
|
519
|
-
...(interaction.channelId ? { channelId: interaction.channelId } : {}),
|
|
520
|
-
...(interaction.guildId ? { guildId: interaction.guildId } : {}),
|
|
521
|
-
messageId: interaction.id,
|
|
522
|
-
};
|
|
523
|
-
if (command === "task") {
|
|
524
|
-
await this.handleTaskCommand(interaction, slashEvent);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
await writeInboxRow({
|
|
528
|
-
repoRoot: this.opts.repoRoot,
|
|
529
|
-
source: this.name,
|
|
530
|
-
kind: "slash",
|
|
531
|
-
payload: { slash: slashEvent },
|
|
532
|
-
});
|
|
533
|
-
if (this.slashHandler)
|
|
534
|
-
await this.slashHandler(slashEvent);
|
|
535
|
-
await interaction.reply({
|
|
536
|
-
content: `Queued \`/${command}\` (run-id pending).`,
|
|
537
|
-
ephemeral: true,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
async handleTaskCommand(interaction, slashEvent) {
|
|
541
|
-
const body = typeof slashEvent.options["body"] === "string" ? slashEvent.options["body"] : "";
|
|
542
|
-
const taskId = newTaskId();
|
|
543
|
-
const bodySlug = slugifyForChannel(body);
|
|
544
|
-
let createdChannelId;
|
|
545
|
-
if (interaction.guild) {
|
|
546
|
-
try {
|
|
547
|
-
const categories = await ensureCategories(interaction.guild);
|
|
548
|
-
const channel = await createTaskChannel({
|
|
549
|
-
guild: interaction.guild,
|
|
550
|
-
category: categories.active,
|
|
551
|
-
taskId,
|
|
552
|
-
bodySlug,
|
|
553
|
-
});
|
|
554
|
-
createdChannelId = channel.id;
|
|
555
|
-
// ยง3.4 win โ no standalone "๐ Task" drop card. The orchestrator
|
|
556
|
-
// surfaces the body inside the live-status embed (PostUpdate.taskBody
|
|
557
|
-
// field) so the channel shows a single self-updating message per task
|
|
558
|
-
// instead of two.
|
|
559
|
-
}
|
|
560
|
-
catch (err) {
|
|
561
|
-
log.error({ err: String(err) }, "failed to create task channel");
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
const task = {
|
|
565
|
-
source: this.name,
|
|
566
|
-
intent: "code_task",
|
|
567
|
-
rawText: body,
|
|
568
|
-
authorId: slashEvent.authorId,
|
|
569
|
-
receivedAt: slashEvent.receivedAt,
|
|
570
|
-
...(createdChannelId ? { channelId: createdChannelId } : {}),
|
|
571
|
-
...(slashEvent.guildId ? { guildId: slashEvent.guildId } : {}),
|
|
572
|
-
messageId: slashEvent.messageId ?? "",
|
|
573
|
-
};
|
|
574
|
-
await writeInboxRow({
|
|
575
|
-
repoRoot: this.opts.repoRoot,
|
|
576
|
-
source: this.name,
|
|
577
|
-
kind: "task",
|
|
578
|
-
payload: { task, slash: slashEvent, task_id: taskId },
|
|
579
|
-
});
|
|
580
|
-
if (this.taskHandler)
|
|
581
|
-
await this.taskHandler(task);
|
|
582
|
-
const replyLines = [`๐ Task ${taskId} queued.`];
|
|
583
|
-
if (createdChannelId)
|
|
584
|
-
replyLines.push(`Channel: <#${createdChannelId}>`);
|
|
585
|
-
await interaction.reply({ content: replyLines.join("\n"), ephemeral: true });
|
|
586
|
-
}
|
|
587
|
-
async handleButton(interaction) {
|
|
588
|
-
const userId = interaction.user.id;
|
|
589
|
-
if (!isOwner(this.ownerIds, userId)) {
|
|
590
|
-
await interaction.reply({ content: "Not authorized.", ephemeral: true });
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const customId = interaction.customId;
|
|
594
|
-
const parts = customId.split(":");
|
|
595
|
-
const namespace = parts[0];
|
|
596
|
-
const kind = parts[1];
|
|
597
|
-
if (namespace !== "harness") {
|
|
598
|
-
await interaction.reply({ content: "Unknown button.", ephemeral: true });
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
if (kind === "approve" || kind === "reject" || kind === "ask") {
|
|
602
|
-
const bundleId = parts.slice(2).join(":");
|
|
603
|
-
const pending = this.pendingApprovals.get(bundleId);
|
|
604
|
-
if (pending) {
|
|
605
|
-
clearTimeout(pending.timeoutHandle);
|
|
606
|
-
this.pendingApprovals.delete(bundleId);
|
|
607
|
-
pending.resolve({ bundleId, decision: kind });
|
|
608
|
-
}
|
|
609
|
-
const interactionEvent = {
|
|
610
|
-
source: this.name,
|
|
611
|
-
bundleId,
|
|
612
|
-
choiceId: kind,
|
|
613
|
-
authorId: userId,
|
|
614
|
-
receivedAt: new Date().toISOString(),
|
|
615
|
-
...(interaction.channelId ? { channelId: interaction.channelId } : {}),
|
|
616
|
-
...(interaction.guildId ? { guildId: interaction.guildId } : {}),
|
|
617
|
-
messageId: interaction.id,
|
|
618
|
-
};
|
|
619
|
-
await writeInboxRow({
|
|
620
|
-
repoRoot: this.opts.repoRoot,
|
|
621
|
-
source: this.name,
|
|
622
|
-
kind: "interaction",
|
|
623
|
-
payload: { interaction: interactionEvent },
|
|
624
|
-
});
|
|
625
|
-
if (this.interactionHandler)
|
|
626
|
-
await this.interactionHandler(interactionEvent);
|
|
627
|
-
await interaction.deferUpdate();
|
|
628
|
-
await this.ackReact(interaction, kind);
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (kind === "voice-confirm") {
|
|
632
|
-
const transcriptId = parts[2] ?? "";
|
|
633
|
-
const choiceId = parts[3] ?? "";
|
|
634
|
-
const interactionEvent = {
|
|
635
|
-
source: this.name,
|
|
636
|
-
bundleId: transcriptId,
|
|
637
|
-
choiceId,
|
|
638
|
-
authorId: userId,
|
|
639
|
-
receivedAt: new Date().toISOString(),
|
|
640
|
-
...(interaction.channelId ? { channelId: interaction.channelId } : {}),
|
|
641
|
-
...(interaction.guildId ? { guildId: interaction.guildId } : {}),
|
|
642
|
-
messageId: interaction.id,
|
|
643
|
-
};
|
|
644
|
-
await writeInboxRow({
|
|
645
|
-
repoRoot: this.opts.repoRoot,
|
|
646
|
-
source: this.name,
|
|
647
|
-
kind: "interaction",
|
|
648
|
-
payload: { interaction: interactionEvent, voice_transcript_id: transcriptId },
|
|
649
|
-
});
|
|
650
|
-
if (this.interactionHandler)
|
|
651
|
-
await this.interactionHandler(interactionEvent);
|
|
652
|
-
await interaction.deferUpdate();
|
|
653
|
-
await this.ackReact(interaction, choiceId);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
if (kind === "dialog") {
|
|
657
|
-
// bundleId may contain colons (e.g. `TSK-XXX:Q1` from the
|
|
658
|
-
// tightener per-question walk), so the choiceId is always the
|
|
659
|
-
// LAST segment and bundleId is everything between `dialog:` and
|
|
660
|
-
// the last `:`.
|
|
661
|
-
const choiceId = parts[parts.length - 1] ?? "";
|
|
662
|
-
const bundleId = parts.slice(2, -1).join(":");
|
|
663
|
-
const pending = this.pendingDialogs.get(bundleId);
|
|
664
|
-
const compact = pending?.compactOnAnswer ?? true;
|
|
665
|
-
const choiceLabel = pending?.choiceLabels.get(choiceId) ?? choiceId.toUpperCase();
|
|
666
|
-
if (pending) {
|
|
667
|
-
clearTimeout(pending.timeoutHandle);
|
|
668
|
-
this.pendingDialogs.delete(bundleId);
|
|
669
|
-
// Don't drop the messageId here โ the next walk step's
|
|
670
|
-
// requestDialog (with replaceBundleId=this) needs to fetch + edit
|
|
671
|
-
// it. The next requestDialog deletes the old entry once it
|
|
672
|
-
// claims the message.
|
|
673
|
-
pending.resolve({ bundleId, choiceId });
|
|
674
|
-
}
|
|
675
|
-
const interactionEvent = {
|
|
676
|
-
source: this.name,
|
|
677
|
-
bundleId,
|
|
678
|
-
choiceId,
|
|
679
|
-
authorId: userId,
|
|
680
|
-
receivedAt: new Date().toISOString(),
|
|
681
|
-
...(interaction.channelId ? { channelId: interaction.channelId } : {}),
|
|
682
|
-
...(interaction.guildId ? { guildId: interaction.guildId } : {}),
|
|
683
|
-
messageId: interaction.id,
|
|
684
|
-
};
|
|
685
|
-
await writeInboxRow({
|
|
686
|
-
repoRoot: this.opts.repoRoot,
|
|
687
|
-
source: this.name,
|
|
688
|
-
kind: "interaction",
|
|
689
|
-
payload: { interaction: interactionEvent },
|
|
690
|
-
});
|
|
691
|
-
if (this.interactionHandler)
|
|
692
|
-
await this.interactionHandler(interactionEvent);
|
|
693
|
-
// Walk steps (compactOnAnswer=false) defer to the next dialog's
|
|
694
|
-
// edit-in-place; touching the message here would race the next
|
|
695
|
-
// step and either drop its content or its buttons. Only terminal
|
|
696
|
-
// dialogs (confirm, UAT, ad-hoc operator question) compact.
|
|
697
|
-
if (compact) {
|
|
698
|
-
try {
|
|
699
|
-
const original = interaction.message;
|
|
700
|
-
const annotated = (original.content ?? "").trim() +
|
|
701
|
-
`\n\n> **Answered:** ${choiceId.toUpperCase()} โ ${choiceLabel}`;
|
|
702
|
-
await interaction.update({
|
|
703
|
-
content: annotated.slice(0, 2000),
|
|
704
|
-
components: [],
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
catch (err) {
|
|
708
|
-
log.warn({ err: String(err), bundleId, choiceId }, "dialog message compact-update failed");
|
|
709
|
-
try {
|
|
710
|
-
await interaction.deferUpdate();
|
|
711
|
-
}
|
|
712
|
-
catch {
|
|
713
|
-
/* noop */
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
try {
|
|
719
|
-
await interaction.deferUpdate();
|
|
720
|
-
}
|
|
721
|
-
catch (err) {
|
|
722
|
-
log.warn({ err: String(err), bundleId, choiceId }, "dialog deferUpdate failed");
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
await this.ackReact(interaction, choiceId);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
await interaction.deferUpdate();
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Stamp the bot's processing on the message: success โ โ
, decline โ
|
|
732
|
-
* โ, ask โ โ. Best-effort; reaction failures don't bubble.
|
|
733
|
-
*/
|
|
734
|
-
async ackReact(interaction, decision) {
|
|
735
|
-
let emoji = "โ
";
|
|
736
|
-
if (decision === "reject" ||
|
|
737
|
-
decision === "cancel" ||
|
|
738
|
-
decision === "no" ||
|
|
739
|
-
decision === "edit") {
|
|
740
|
-
emoji = "โ";
|
|
741
|
-
}
|
|
742
|
-
else if (decision === "ask" || decision === "ship_anyway") {
|
|
743
|
-
emoji = "โก";
|
|
744
|
-
}
|
|
745
|
-
try {
|
|
746
|
-
await interaction.message.react(emoji);
|
|
747
|
-
}
|
|
748
|
-
catch (err) {
|
|
749
|
-
log.warn({ err: String(err), choice: decision }, "ackReact failed");
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
async handleMessage(message) {
|
|
753
|
-
if (message.author.bot)
|
|
754
|
-
return;
|
|
755
|
-
const userId = message.author.id;
|
|
756
|
-
if (!isOwner(this.ownerIds, userId))
|
|
757
|
-
return;
|
|
758
|
-
// Voice attachments take precedence โ bypass text classification path.
|
|
759
|
-
const voiceAttachments = message.attachments.filter((a) => {
|
|
760
|
-
const mime = a.contentType ?? "";
|
|
761
|
-
return VOICE_MIME_PREFIXES.some((p) => mime.startsWith(p));
|
|
762
|
-
});
|
|
763
|
-
if (voiceAttachments.size > 0) {
|
|
764
|
-
for (const attachment of voiceAttachments.values()) {
|
|
765
|
-
await this.handleVoiceAttachment(message, userId, attachment);
|
|
766
|
-
}
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const text = message.content?.trim() ?? "";
|
|
770
|
-
if (text.length === 0)
|
|
771
|
-
return;
|
|
772
|
-
const classification = await classifyTier0(text, this.tier0Opts);
|
|
773
|
-
const event = {
|
|
774
|
-
source: this.name,
|
|
775
|
-
intent: classification.intent,
|
|
776
|
-
rawText: text,
|
|
777
|
-
authorId: userId,
|
|
778
|
-
receivedAt: new Date().toISOString(),
|
|
779
|
-
channelId: message.channelId,
|
|
780
|
-
messageId: message.id,
|
|
781
|
-
...(message.guildId ? { guildId: message.guildId } : {}),
|
|
782
|
-
};
|
|
783
|
-
if (classification.intent === "code_task") {
|
|
784
|
-
const taskId = newTaskId();
|
|
785
|
-
const task = {
|
|
786
|
-
source: this.name,
|
|
787
|
-
intent: "code_task",
|
|
788
|
-
rawText: text,
|
|
789
|
-
authorId: userId,
|
|
790
|
-
receivedAt: event.receivedAt,
|
|
791
|
-
channelId: message.channelId,
|
|
792
|
-
messageId: message.id,
|
|
793
|
-
...(message.guildId ? { guildId: message.guildId } : {}),
|
|
794
|
-
};
|
|
795
|
-
await writeInboxRow({
|
|
796
|
-
repoRoot: this.opts.repoRoot,
|
|
797
|
-
source: this.name,
|
|
798
|
-
kind: "task",
|
|
799
|
-
payload: { task, free_text: event, task_id: taskId, classification },
|
|
800
|
-
});
|
|
801
|
-
if (this.taskHandler)
|
|
802
|
-
await this.taskHandler(task);
|
|
803
|
-
try {
|
|
804
|
-
await message.react("๐");
|
|
805
|
-
}
|
|
806
|
-
catch {
|
|
807
|
-
// permission optional
|
|
808
|
-
}
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
await writeInboxRow({
|
|
812
|
-
repoRoot: this.opts.repoRoot,
|
|
813
|
-
source: this.name,
|
|
814
|
-
kind: "free_text",
|
|
815
|
-
payload: { free_text: event, classification },
|
|
816
|
-
});
|
|
817
|
-
if (this.freeTextHandler)
|
|
818
|
-
await this.freeTextHandler(event);
|
|
819
|
-
try {
|
|
820
|
-
await message.react(classification.intent === "unknown" ? "โ" : "โ
");
|
|
821
|
-
}
|
|
822
|
-
catch {
|
|
823
|
-
// optional
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
async handleVoiceAttachment(message, userId, attachment) {
|
|
827
|
-
const transcriptId = `VTX-${Date.now().toString(36)}-${randomBytes(2).toString("hex")}`;
|
|
828
|
-
let transcript = null;
|
|
829
|
-
let transcribeError = null;
|
|
830
|
-
if (!whisperModelExists()) {
|
|
831
|
-
transcribeError = "whisper model not installed (Phase 16 init or manual download)";
|
|
832
|
-
}
|
|
833
|
-
else {
|
|
834
|
-
try {
|
|
835
|
-
transcript = await transcribeUrl(attachment.url, { language: "en" });
|
|
836
|
-
}
|
|
837
|
-
catch (err) {
|
|
838
|
-
transcribeError = err instanceof Error ? err.message : String(err);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
let classification = null;
|
|
842
|
-
if (transcript && transcript.text.length > 0) {
|
|
843
|
-
try {
|
|
844
|
-
classification = await classifyTier0(transcript.text, this.tier0Opts);
|
|
845
|
-
}
|
|
846
|
-
catch (err) {
|
|
847
|
-
log.warn({ err: String(err) }, "tier0 classify on transcript failed");
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
const voice = {
|
|
851
|
-
source: this.name,
|
|
852
|
-
attachmentUrl: attachment.url,
|
|
853
|
-
authorId: userId,
|
|
854
|
-
channelId: message.channelId,
|
|
855
|
-
messageId: message.id,
|
|
856
|
-
receivedAt: new Date().toISOString(),
|
|
857
|
-
...(attachment.contentType ? { mime: attachment.contentType } : {}),
|
|
858
|
-
...(message.guildId ? { guildId: message.guildId } : {}),
|
|
859
|
-
};
|
|
860
|
-
const belowFloor = transcript !== null && transcript.avgLogprob < this.confidenceFloor;
|
|
861
|
-
const transcriptPayload = transcript
|
|
862
|
-
? {
|
|
863
|
-
id: transcriptId,
|
|
864
|
-
text: transcript.text,
|
|
865
|
-
avg_logprob: transcript.avgLogprob,
|
|
866
|
-
language: transcript.language,
|
|
867
|
-
duration_ms: transcript.durationMs,
|
|
868
|
-
segments: transcript.segments.length,
|
|
869
|
-
confidence_floor: this.confidenceFloor,
|
|
870
|
-
below_floor: belowFloor,
|
|
871
|
-
}
|
|
872
|
-
: null;
|
|
873
|
-
await writeInboxRow({
|
|
874
|
-
repoRoot: this.opts.repoRoot,
|
|
875
|
-
source: this.name,
|
|
876
|
-
kind: "voice",
|
|
877
|
-
payload: {
|
|
878
|
-
voice,
|
|
879
|
-
transcript: transcriptPayload,
|
|
880
|
-
transcribe_error: transcribeError,
|
|
881
|
-
classification,
|
|
882
|
-
},
|
|
883
|
-
});
|
|
884
|
-
if (this.voiceHandler)
|
|
885
|
-
await this.voiceHandler(voice);
|
|
886
|
-
if (transcribeError !== null) {
|
|
887
|
-
try {
|
|
888
|
-
await message.react("โ ๏ธ");
|
|
889
|
-
}
|
|
890
|
-
catch {
|
|
891
|
-
// optional
|
|
892
|
-
}
|
|
893
|
-
try {
|
|
894
|
-
await message.reply(`Transcription failed: \`${transcribeError.slice(0, 200)}\`. Inbox row dropped without transcript.`);
|
|
895
|
-
}
|
|
896
|
-
catch {
|
|
897
|
-
// optional
|
|
898
|
-
}
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
if (transcript && belowFloor) {
|
|
902
|
-
try {
|
|
903
|
-
const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
|
|
904
|
-
.setCustomId(`harness:voice-confirm:${transcriptId}:yes`)
|
|
905
|
-
.setStyle(ButtonStyle.Success)
|
|
906
|
-
.setLabel("Confirm")
|
|
907
|
-
.setEmoji("๐ข"), new ButtonBuilder()
|
|
908
|
-
.setCustomId(`harness:voice-confirm:${transcriptId}:no`)
|
|
909
|
-
.setStyle(ButtonStyle.Danger)
|
|
910
|
-
.setLabel("Re-record")
|
|
911
|
-
.setEmoji("๐ด"));
|
|
912
|
-
const pct = (transcript.avgLogprob * 100).toFixed(0);
|
|
913
|
-
const summary = transcript.text.length > 220
|
|
914
|
-
? `${transcript.text.slice(0, 217)}...`
|
|
915
|
-
: transcript.text;
|
|
916
|
-
await message.reply({
|
|
917
|
-
content: `Heard: "${summary}" (confidence ${pct}%) โ confirm?`,
|
|
918
|
-
components: [row],
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
catch (err) {
|
|
922
|
-
log.warn({ err: String(err) }, "voice confirm prompt failed");
|
|
923
|
-
}
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
try {
|
|
927
|
-
await message.react("๐");
|
|
928
|
-
}
|
|
929
|
-
catch {
|
|
930
|
-
// optional
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
function newTaskId() {
|
|
935
|
-
return `TSK-${Date.now().toString(36)}-${randomBytes(2).toString("hex")}`;
|
|
936
|
-
}
|
|
937
|
-
/**
|
|
938
|
-
* True when the error is a discord.js DiscordAPIError with code 10003
|
|
939
|
-
* (Unknown Channel). The harness flags the channel dead and silently
|
|
940
|
-
* no-ops every subsequent post against it.
|
|
941
|
-
*/
|
|
942
|
-
function isUnknownChannelError(err) {
|
|
943
|
-
if (typeof err !== "object" || err === null)
|
|
944
|
-
return false;
|
|
945
|
-
const e = err;
|
|
946
|
-
return e["code"] === 10003;
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Map orchestrator phase names โ embed colors. Mirrors the harness
|
|
950
|
-
* pipeline: tighten โ prep โ run โ sense โ review โ uat โ done.
|
|
951
|
-
*
|
|
952
|
-
* `blocked` is cyan (waiting-on-operator), distinct from failure
|
|
953
|
-
* orange โ operator should read the color as "your move", not "broken".
|
|
954
|
-
*/
|
|
955
|
-
const PHASE_COLOR = {
|
|
956
|
-
queued: 0x607d8b,
|
|
957
|
-
tightening: 0x3498db,
|
|
958
|
-
blocked: 0x4a90e2,
|
|
959
|
-
prepping: 0x95a5a6,
|
|
960
|
-
running: 0xf1c40f,
|
|
961
|
-
sensing: 0xe67e22,
|
|
962
|
-
reviewing: 0x9b59b6,
|
|
963
|
-
uat: 0x1abc9c,
|
|
964
|
-
backpropping: 0x6f42c1,
|
|
965
|
-
succeeded: 0x2ecc71,
|
|
966
|
-
failed: 0xe74c3c,
|
|
967
|
-
};
|
|
968
|
-
const PHASE_EMOJI = {
|
|
969
|
-
queued: "๐ฆ",
|
|
970
|
-
tightening: "๐ช",
|
|
971
|
-
blocked: "โณ",
|
|
972
|
-
prepping: "๐งฐ",
|
|
973
|
-
running: "๐ก",
|
|
974
|
-
sensing: "๐",
|
|
975
|
-
reviewing: "๐ฌ",
|
|
976
|
-
uat: "๐งช",
|
|
977
|
-
backpropping: "๐",
|
|
978
|
-
succeeded: "๐ข",
|
|
979
|
-
failed: "๐ด",
|
|
980
|
-
};
|
|
981
|
-
const PHASE_LABEL = {
|
|
982
|
-
queued: "queued",
|
|
983
|
-
tightening: "tightening spec",
|
|
984
|
-
blocked: "awaiting you",
|
|
985
|
-
prepping: "prepping workspace",
|
|
986
|
-
running: "agent running",
|
|
987
|
-
sensing: "sensors sweeping",
|
|
988
|
-
reviewing: "reviewer checking",
|
|
989
|
-
uat: "UAT in flight",
|
|
990
|
-
backpropping: "backprop",
|
|
991
|
-
succeeded: "shipped",
|
|
992
|
-
failed: "failed",
|
|
993
|
-
};
|
|
994
|
-
/** ยง3.4 win 3 โ class-specific failure color so operator routes per class. */
|
|
995
|
-
const FAILURE_COLOR = {
|
|
996
|
-
sensor: 0xff8c00, // orange โ mechanical / structural fail; usually fixable
|
|
997
|
-
reviewer: 0x9b59b6, // purple โ inferential reject; needs spec or code thinking
|
|
998
|
-
uat: 0x3498db, // blue โ operator rejected the bundle
|
|
999
|
-
hard: 0xff0000, // red โ process / infra error
|
|
1000
|
-
halt: 0x2c2c2c, // dark grey โ operator pulled the cord
|
|
1001
|
-
};
|
|
1002
|
-
const FAILURE_EMOJI = {
|
|
1003
|
-
sensor: "๐ง",
|
|
1004
|
-
reviewer: "๐ช",
|
|
1005
|
-
uat: "๐ฆ",
|
|
1006
|
-
hard: "๐ฅ",
|
|
1007
|
-
halt: "โซ",
|
|
1008
|
-
};
|
|
1009
|
-
function buildPhaseEmbed(update) {
|
|
1010
|
-
const isFailure = update.status === "failed" && update.failureClass !== undefined;
|
|
1011
|
-
const color = isFailure
|
|
1012
|
-
? FAILURE_COLOR[update.failureClass] ?? 0xff0000
|
|
1013
|
-
: PHASE_COLOR[update.status] ?? 0x808080;
|
|
1014
|
-
const emoji = isFailure
|
|
1015
|
-
? FAILURE_EMOJI[update.failureClass] ?? "๐ด"
|
|
1016
|
-
: PHASE_EMOJI[update.status] ?? "โข";
|
|
1017
|
-
const titleSuffix = isFailure ? ` โ failed: ${update.failureClass}` : "";
|
|
1018
|
-
const phaseLabel = PHASE_LABEL[update.status] ?? update.status;
|
|
1019
|
-
const descLines = [`**${phaseLabel}**`];
|
|
1020
|
-
if (update.taskBody !== undefined && update.taskBody.length > 0) {
|
|
1021
|
-
descLines.push("");
|
|
1022
|
-
const trimmed = update.taskBody.trim();
|
|
1023
|
-
const truncated = trimmed.length > 1024
|
|
1024
|
-
? `${trimmed.slice(0, 1010)}โฆ`
|
|
1025
|
-
: trimmed;
|
|
1026
|
-
// Quote-block so the spec stays visually distinct from status.
|
|
1027
|
-
descLines.push(...truncated.split("\n").map((line) => `> ${line}`));
|
|
1028
|
-
}
|
|
1029
|
-
if (update.recentEvents !== undefined && update.recentEvents.length > 0) {
|
|
1030
|
-
descLines.push("");
|
|
1031
|
-
descLines.push("**recent**");
|
|
1032
|
-
for (const ev of update.recentEvents)
|
|
1033
|
-
descLines.push(ev);
|
|
1034
|
-
}
|
|
1035
|
-
const description = descLines.join("\n").slice(0, 4000);
|
|
1036
|
-
const embed = new EmbedBuilder()
|
|
1037
|
-
.setTitle(`${emoji} ${update.taskId}${titleSuffix}`)
|
|
1038
|
-
.setColor(color)
|
|
1039
|
-
.setDescription(description);
|
|
1040
|
-
if (update.runId) {
|
|
1041
|
-
embed.addFields({ name: "run", value: `\`${update.runId}\``, inline: true });
|
|
1042
|
-
}
|
|
1043
|
-
if (update.activity !== undefined && update.activity.length > 0) {
|
|
1044
|
-
embed.addFields({
|
|
1045
|
-
name: "activity",
|
|
1046
|
-
value: update.activity.slice(0, 1024),
|
|
1047
|
-
inline: false,
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
if (update.tools?.files !== undefined &&
|
|
1051
|
-
update.tools.files.length > 0) {
|
|
1052
|
-
embed.addFields({
|
|
1053
|
-
name: "files",
|
|
1054
|
-
value: capFieldValue(update.tools.files.map((f) => `\`${f}\``).join("\n")),
|
|
1055
|
-
inline: false,
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
if (update.tools?.bash !== undefined && update.tools.bash.length > 0) {
|
|
1059
|
-
embed.addFields({
|
|
1060
|
-
name: "shell",
|
|
1061
|
-
value: capFieldValue(update.tools.bash.map((c) => `\`${c}\``).join("\n")),
|
|
1062
|
-
inline: false,
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
if (update.tools?.searches !== undefined &&
|
|
1066
|
-
update.tools.searches.length > 0) {
|
|
1067
|
-
embed.addFields({
|
|
1068
|
-
name: "searches",
|
|
1069
|
-
value: capFieldValue(update.tools.searches.map((s) => `\`${s}\``).join("\n")),
|
|
1070
|
-
inline: false,
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
if (update.body !== undefined && update.body.length > 0) {
|
|
1074
|
-
let value = update.body;
|
|
1075
|
-
if (value.length > 1024) {
|
|
1076
|
-
value = `${value.slice(0, 1010)}โฆ\n_+${update.body.length - 1010} chars in log.jsonl_`;
|
|
1077
|
-
}
|
|
1078
|
-
embed.addFields({ name: "details", value, inline: false });
|
|
1079
|
-
}
|
|
1080
|
-
if (update.remediation !== undefined) {
|
|
1081
|
-
const lines = [
|
|
1082
|
-
`**reason:** ${update.remediation.reason.slice(0, 400)}`,
|
|
1083
|
-
];
|
|
1084
|
-
if (update.remediation.suggestedActions.length > 0) {
|
|
1085
|
-
lines.push("**next:**");
|
|
1086
|
-
for (const a of update.remediation.suggestedActions.slice(0, 5)) {
|
|
1087
|
-
lines.push(`โข ${a}`);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
embed.addFields({
|
|
1091
|
-
name: "remediation",
|
|
1092
|
-
value: capFieldValue(lines.join("\n")),
|
|
1093
|
-
inline: false,
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
embed.setTimestamp(new Date());
|
|
1097
|
-
return embed;
|
|
1098
|
-
}
|
|
1099
|
-
function capFieldValue(text) {
|
|
1100
|
-
return text.length > 1024 ? `${text.slice(0, 1018)}\nโฆ` : text;
|
|
1101
|
-
}
|
|
1102
|
-
// re-export utilities for callers (orchestrator, smoke)
|
|
1103
|
-
export { CATEGORY_NAMES, ensureCategories, moveChannelToCategory, slugifyForChannel, };
|
|
1104
|
-
//# sourceMappingURL=index.js.map
|