@evomap/evolver 1.90.0 → 2.0.0-beta.1
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/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3670
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -602
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -361
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/solo/breaker.js +0 -25
- package/src/solo/gitGuard.js +0 -65
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- package/src/webui/server/routes.js +0 -41
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// evolver-task-recall.js
|
|
3
|
-
// UserPromptSubmit hook: on each user prompt, find GEP assets (Hub genes +
|
|
4
|
-
// local genes) that match the task and inject a distilled hint so a GENERAL
|
|
5
|
-
// agent benefits from prior distilled capabilities without manually calling
|
|
6
|
-
// MCP tools. This is the per-harness shell around src/gep/recallInject.js.
|
|
7
|
-
//
|
|
8
|
-
// Input (stdin JSON, Claude Code UserPromptSubmit):
|
|
9
|
-
// { "prompt": "...", "session_id", "cwd", "transcript_path", ... }
|
|
10
|
-
// Output (stdout JSON, exit 0 ALWAYS):
|
|
11
|
-
// enforce + match -> { agent_message, additionalContext,
|
|
12
|
-
// hookSpecificOutput: { hookEventName, additionalContext } }
|
|
13
|
-
// everything else -> {} (injects nothing)
|
|
14
|
-
//
|
|
15
|
-
// DESIGN CONTRACT (the fail-open core — see also recallInject.js invariants):
|
|
16
|
-
// - DEFAULT off. off finishes {} WITHOUT parsing the prompt body.
|
|
17
|
-
// - shadow computes + logs what WOULD inject but injects nothing.
|
|
18
|
-
// - enforce injects the distilled hint.
|
|
19
|
-
// - FAIL-OPEN: any error/timeout/empty -> exactly one finish({}). The hook
|
|
20
|
-
// blocks the user's prompt, so it must NEVER hang or crash the session.
|
|
21
|
-
// Claude Code's timeout fail-open behaviour is UNDOCUMENTED, so we own the
|
|
22
|
-
// deadline ourselves with a single watchdog + latch (never rely on the
|
|
23
|
-
// host to kill us gracefully).
|
|
24
|
-
// - STDOUT is a single JSON object. Any stray console.log() from a
|
|
25
|
-
// transitively-required module (e.g. signals._mergeSignals, hubSearch
|
|
26
|
-
// fetch-cost log, assetStore seeding) would corrupt it — so we redirect
|
|
27
|
-
// console.* to stderr before requiring anything heavy.
|
|
28
|
-
|
|
29
|
-
'use strict';
|
|
30
|
-
|
|
31
|
-
// --- stdout-poison defense: route all console.* to stderr ------------------
|
|
32
|
-
// Modules we require (hubSearch, signals, assetStore, …) call console.log,
|
|
33
|
-
// which writes to stdout. The hook contract is ONE JSON object on stdout, so
|
|
34
|
-
// we redirect every console method to stderr. stderr on exit 0 is not fed to
|
|
35
|
-
// the model for UserPromptSubmit; on non-2 exit only its first line shows in
|
|
36
|
-
// the transcript as a hook-error notice — acceptable and we exit 0 anyway.
|
|
37
|
-
for (const m of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
38
|
-
try { console[m] = function () { try { process.stderr.write(''); } catch (_) {} }; } catch (_) {}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const path = require('path');
|
|
42
|
-
|
|
43
|
-
// ---- Timing budget, coherent with the host kill ---------------------------
|
|
44
|
-
// The host (Claude Code) kills this process at the hook's `timeout` (5s in
|
|
45
|
-
// buildClaudeHooks -> 5000ms). Our OWN absolute watchdog MUST fire comfortably
|
|
46
|
-
// BEFORE that, or the host could kill us mid-write and break the fail-open
|
|
47
|
-
// stdout contract. So:
|
|
48
|
-
// ABSOLUTE_DEADLINE_MS (3300) < host 5000ms -> ~1.7s headroom for finish().
|
|
49
|
-
// EVOLVER_RECALL_TIMEOUT_MS is the Hub SEARCH budget only (clamped well under
|
|
50
|
-
// the absolute deadline). The actual budget handed to the search is computed
|
|
51
|
-
// DYNAMICALLY at search-start as (deadline - already-elapsed - safety), so
|
|
52
|
-
// slow stdin/require can never let the search run past the watchdog (which
|
|
53
|
-
// would otherwise spuriously return {} — Bugbot #183 medium findings).
|
|
54
|
-
const T0 = Date.now();
|
|
55
|
-
const ABSOLUTE_DEADLINE_MS = 3300; // watchdog; strictly < host timeout (5000ms)
|
|
56
|
-
const SEARCH_SAFETY_MS = 250; // leave room for finish() after the search
|
|
57
|
-
const MIN_SEARCH_MS = 300; // below this, skip the search (finish {})
|
|
58
|
-
|
|
59
|
-
function getSearchBudgetMs() {
|
|
60
|
-
const n = parseInt(process.env.EVOLVER_RECALL_TIMEOUT_MS, 10);
|
|
61
|
-
// Hard cap at 2800 so even a max-budget search starts and ends before the
|
|
62
|
-
// 3300ms watchdog under any realistic startup cost.
|
|
63
|
-
return Number.isFinite(n) && n >= 500 && n <= 2800 ? n : 2000;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function getMode() {
|
|
67
|
-
const v = String(process.env.EVOLVER_RECALL_MODE || '').toLowerCase().trim();
|
|
68
|
-
return v === 'shadow' || v === 'enforce' ? v : 'off';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let handled = false;
|
|
72
|
-
let watchdog = null;
|
|
73
|
-
|
|
74
|
-
// Single-writer latch: exactly one stdout write, exactly one exit. Mirrors the
|
|
75
|
-
// proven pattern in evolver-signal-detect.js / evolver-session-end.js.
|
|
76
|
-
function finish(obj) {
|
|
77
|
-
if (handled) return;
|
|
78
|
-
handled = true;
|
|
79
|
-
if (watchdog) { try { clearTimeout(watchdog); } catch (_) {} }
|
|
80
|
-
try { process.stdout.write(JSON.stringify(obj || {})); } catch (_) {}
|
|
81
|
-
process.exit(0);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function main() {
|
|
85
|
-
const mode = getMode();
|
|
86
|
-
|
|
87
|
-
// Absolute watchdog, armed at process entry and INDEPENDENT of the search
|
|
88
|
-
// budget. Fires at ABSOLUTE_DEADLINE_MS (3300ms) — strictly under the host's
|
|
89
|
-
// timeout (5000ms) so the host can never kill us mid-write. If stdin never
|
|
90
|
-
// closes OR anything hangs, we emit {} and exit cleanly first.
|
|
91
|
-
watchdog = setTimeout(() => finish({}), ABSOLUTE_DEADLINE_MS);
|
|
92
|
-
|
|
93
|
-
let buf = '';
|
|
94
|
-
try {
|
|
95
|
-
process.stdin.setEncoding('utf8');
|
|
96
|
-
} catch (_) { /* some hosts pass no stdin */ }
|
|
97
|
-
process.stdin.on('data', (c) => { buf += c; });
|
|
98
|
-
process.stdin.on('error', () => finish({}));
|
|
99
|
-
process.stdin.on('end', () => {
|
|
100
|
-
if (handled) return;
|
|
101
|
-
|
|
102
|
-
// off: do NOT even parse the prompt body (privacy: nothing read/sent).
|
|
103
|
-
if (mode === 'off') return finish({});
|
|
104
|
-
|
|
105
|
-
let prompt = '';
|
|
106
|
-
let sessionId = '';
|
|
107
|
-
try {
|
|
108
|
-
const input = buf.trim() ? JSON.parse(buf) : {};
|
|
109
|
-
prompt = String(input.prompt || '').trim();
|
|
110
|
-
sessionId = String(input.session_id || input.sessionId || '').trim();
|
|
111
|
-
} catch (_) {
|
|
112
|
-
return finish({});
|
|
113
|
-
}
|
|
114
|
-
if (prompt.length < 8) return finish({}); // trivial prompt -> skip
|
|
115
|
-
|
|
116
|
-
// Heavy require INSIDE try/catch: a broken require graph must fail open,
|
|
117
|
-
// not crash the user's prompt (this is also why the e2e test runs the
|
|
118
|
-
// copied hook with mode=off and asserts exit 0 + parseable stdout).
|
|
119
|
-
let core;
|
|
120
|
-
try {
|
|
121
|
-
const { findEvolverRoot } = require('./_runtimePaths');
|
|
122
|
-
const root = findEvolverRoot();
|
|
123
|
-
if (!root) return finish({});
|
|
124
|
-
core = require(path.join(root, 'src', 'gep', 'recallInject.js'));
|
|
125
|
-
} catch (_) {
|
|
126
|
-
return finish({});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Pass the ABSOLUTE deadline (T0 + watchdog window) plus the configured
|
|
130
|
-
// search cap. The core bounds the Hub call by the time REMAINING to that
|
|
131
|
-
// deadline (minus its own post-await safety margin) and runs local-gene
|
|
132
|
-
// disk I/O BEFORE the Hub await — so neither the Hub search nor the
|
|
133
|
-
// post-processing can overrun the watchdog (Bugbot #183: slow startup or a
|
|
134
|
-
// budget-eating Hub call must not let the timer fire mid-work). If too
|
|
135
|
-
// little time remains even before starting, skip and fail open.
|
|
136
|
-
const elapsed = Date.now() - T0;
|
|
137
|
-
const remaining = ABSOLUTE_DEADLINE_MS - elapsed - SEARCH_SAFETY_MS;
|
|
138
|
-
if (remaining < MIN_SEARCH_MS) return finish({});
|
|
139
|
-
const deadlineMs = T0 + ABSOLUTE_DEADLINE_MS;
|
|
140
|
-
|
|
141
|
-
Promise.resolve()
|
|
142
|
-
.then(() => core.recallForTask({ prompt, mode, sessionId, timeoutMs: getSearchBudgetMs(), deadlineMs }))
|
|
143
|
-
.then((r) => {
|
|
144
|
-
if (r && r.inject && r.text) {
|
|
145
|
-
// Emit BOTH shapes:
|
|
146
|
-
// - nested hookSpecificOutput.additionalContext is the DOCUMENTED
|
|
147
|
-
// canonical UserPromptSubmit injection shape (system-reminder,
|
|
148
|
-
// no transcript noise).
|
|
149
|
-
// - flat additionalContext / agent_message match the in-repo
|
|
150
|
-
// precedent (session-start.js) for hosts that read the flat key.
|
|
151
|
-
// Extra keys are tolerated/ignored by hosts; whichever wins, the
|
|
152
|
-
// other is harmless.
|
|
153
|
-
return finish({
|
|
154
|
-
agent_message: r.text,
|
|
155
|
-
additionalContext: r.text,
|
|
156
|
-
hookSpecificOutput: {
|
|
157
|
-
hookEventName: 'UserPromptSubmit',
|
|
158
|
-
additionalContext: r.text,
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
// shadow (logged inside the core) and no-match both inject nothing.
|
|
163
|
-
return finish({});
|
|
164
|
-
})
|
|
165
|
-
.catch(() => finish({}));
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (require.main === module) {
|
|
170
|
-
main();
|
|
171
|
-
} else {
|
|
172
|
-
module.exports = { getMode, getSearchBudgetMs };
|
|
173
|
-
}
|
package/src/atp/atpExecute.js
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
// ATP end-to-end task completer.
|
|
2
|
-
//
|
|
3
|
-
// Invoked from the `atp-complete` subcommand (index.js). A spawned Cursor
|
|
4
|
-
// sub-session answers an ATP task, writes the answer to disk, then runs:
|
|
5
|
-
//
|
|
6
|
-
// node index.js atp-complete \
|
|
7
|
-
// --task-id=<tid> --order-id=<oid> --answer-file=<path> [--summary="..."]
|
|
8
|
-
//
|
|
9
|
-
// This module takes that answer and drives the full settlement path:
|
|
10
|
-
// 1. Synthesize a minimal Gene + Capsule bundle wrapping the answer.
|
|
11
|
-
// 2. POST /a2a/publish to register the Capsule asset on the Hub (signed).
|
|
12
|
-
// 3. POST /a2a/task/complete to bind the resultAssetId to the claimed task.
|
|
13
|
-
// 4. POST /a2a/atp/deliver to submit the proof_payload (asset_id + result).
|
|
14
|
-
//
|
|
15
|
-
// Autoverify (verifyMode=auto) on the Hub treats `payload.asset_id` and
|
|
16
|
-
// `payload.result` as has_result=true with pass_rate=1.0, so a valid answer
|
|
17
|
-
// immediately progresses the DeliveryProof from pending -> verified -> settled.
|
|
18
|
-
//
|
|
19
|
-
// Failures are returned as { ok:false, stage:..., error:... } so the caller can
|
|
20
|
-
// retry per-stage without duplicating upstream effects (Gene/Capsule asset_ids
|
|
21
|
-
// are deterministic content-hashes, so republish of the same bundle is
|
|
22
|
-
// idempotent server-side).
|
|
23
|
-
|
|
24
|
-
const fs = require('fs');
|
|
25
|
-
const crypto = require('crypto');
|
|
26
|
-
|
|
27
|
-
const { computeAssetId } = require('../gep/contentHash');
|
|
28
|
-
const { hubFetch } = require('../gep/hubFetch');
|
|
29
|
-
const {
|
|
30
|
-
getNodeId,
|
|
31
|
-
getHubUrl,
|
|
32
|
-
getHubNodeSecret,
|
|
33
|
-
buildHubHeaders,
|
|
34
|
-
buildNodeScopedHubHeaders,
|
|
35
|
-
sendHelloToHub,
|
|
36
|
-
} = require('../gep/a2aProtocol');
|
|
37
|
-
const { submitDelivery } = require('./hubClient');
|
|
38
|
-
|
|
39
|
-
const MAX_ANSWER_CHARS = 32000; // cap capsule.content to protect Hub payload limits
|
|
40
|
-
const PUBLISH_TIMEOUT_MS = 15000;
|
|
41
|
-
|
|
42
|
-
function _readAnswer(answerFile) {
|
|
43
|
-
const raw = fs.readFileSync(answerFile, 'utf8');
|
|
44
|
-
const trimmed = String(raw || '').trim();
|
|
45
|
-
if (!trimmed) throw new Error('answer file is empty');
|
|
46
|
-
if (trimmed.length > MAX_ANSWER_CHARS) {
|
|
47
|
-
return trimmed.slice(0, MAX_ANSWER_CHARS - 40) + '\n...[TRUNCATED]...';
|
|
48
|
-
}
|
|
49
|
-
return trimmed;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function _buildGene(capabilities, signals) {
|
|
53
|
-
const caps = Array.isArray(capabilities) && capabilities.length > 0
|
|
54
|
-
? capabilities.slice(0, 8)
|
|
55
|
-
: ['general'];
|
|
56
|
-
const sig = Array.isArray(signals) && signals.length > 0
|
|
57
|
-
? signals.slice(0, 8)
|
|
58
|
-
: ['atp_task'];
|
|
59
|
-
const gene = {
|
|
60
|
-
type: 'Gene',
|
|
61
|
-
schema_version: '1.0.0',
|
|
62
|
-
id: 'gene_atp_answer_' + caps.sort().join('_').slice(0, 40),
|
|
63
|
-
summary: 'Deliver an ATP task answer for capabilities: ' + caps.join(', '),
|
|
64
|
-
signals_match: sig,
|
|
65
|
-
category: 'innovate',
|
|
66
|
-
strategy: [
|
|
67
|
-
'Read the buyer question carefully and identify the requested capability.',
|
|
68
|
-
'Produce a concrete, actionable answer addressing the question directly.',
|
|
69
|
-
'Return the answer as Capsule content for verifiable delivery.',
|
|
70
|
-
],
|
|
71
|
-
// gep-sdk Gene schema requires `constraints`; an ATP answer edits no
|
|
72
|
-
// files, so the blast radius is empty rather than left unbounded.
|
|
73
|
-
constraints: { max_files: 0, forbidden_paths: [] },
|
|
74
|
-
validation: [
|
|
75
|
-
'Answer is non-empty and directly addresses the buyer question.',
|
|
76
|
-
'Answer references the requested capabilities where relevant.',
|
|
77
|
-
],
|
|
78
|
-
};
|
|
79
|
-
gene.asset_id = computeAssetId(gene);
|
|
80
|
-
return gene;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function _buildCapsule({ gene, answer, summary, orderId, taskId, capabilities, signals }) {
|
|
84
|
-
const caps = Array.isArray(capabilities) ? capabilities.slice(0, 8) : [];
|
|
85
|
-
const sig = Array.isArray(signals) && signals.length > 0 ? signals.slice(0, 8) : ['atp_task'];
|
|
86
|
-
const confidence = 0.9; // merchant self-attested; buyer verify may override
|
|
87
|
-
const capsuleSummary = String(summary || '').trim()
|
|
88
|
-
|| 'ATP merchant delivery for order ' + String(orderId || '').slice(0, 24);
|
|
89
|
-
const capsule = {
|
|
90
|
-
type: 'Capsule',
|
|
91
|
-
schema_version: '1.0.0',
|
|
92
|
-
id: 'capsule_atp_' + String(orderId || taskId || Date.now()).replace(/[^a-zA-Z0-9_\-]/g, '_').slice(0, 40),
|
|
93
|
-
trigger: sig,
|
|
94
|
-
gene: gene.id,
|
|
95
|
-
summary: capsuleSummary.slice(0, 200),
|
|
96
|
-
confidence,
|
|
97
|
-
blast_radius: { files: 0, lines: Math.min(1000, answer.split('\n').length) },
|
|
98
|
-
outcome: { status: 'success', score: confidence },
|
|
99
|
-
env_fingerprint: { platform: process.platform, arch: process.arch, runtime: 'evolver-atp' },
|
|
100
|
-
content: answer,
|
|
101
|
-
// 'generated' is the gep-sdk source_type enum value for a freshly
|
|
102
|
-
// produced asset; the ATP-specific provenance rides in `a2a.atp` below.
|
|
103
|
-
source_type: 'generated',
|
|
104
|
-
// The order/task association MUST live under `a2a`, not as a top-level
|
|
105
|
-
// `atp` key: the Hub's payload sanitizer allow-lists `a2a` but not `atp`
|
|
106
|
-
// (CAPSULE_ALLOWED_FIELDS), so a top-level `atp` was being silently
|
|
107
|
-
// stripped on publish and the association never reached the Hub. `a2a`
|
|
108
|
-
// is also an open object in gep-sdk's Capsule schema, so this keeps the
|
|
109
|
-
// bundle GEP-valid.
|
|
110
|
-
a2a: {
|
|
111
|
-
atp: {
|
|
112
|
-
order_id: orderId || null,
|
|
113
|
-
task_id: taskId || null,
|
|
114
|
-
capabilities: caps,
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
capsule.asset_id = computeAssetId(capsule);
|
|
119
|
-
return capsule;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function _publishUrl() {
|
|
123
|
-
const base = String(getHubUrl() || '').replace(/\/+$/, '');
|
|
124
|
-
if (!base) throw new Error('hub url not configured');
|
|
125
|
-
return base + '/a2a/publish';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function _postJson(urlStr, body, timeoutMs) {
|
|
129
|
-
const payload = JSON.stringify(body || {});
|
|
130
|
-
const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
|
|
131
|
-
const headers = Object.assign(
|
|
132
|
-
{ 'Content-Type': 'application/json' },
|
|
133
|
-
buildHeaders() || {},
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const res = await hubFetch(urlStr, {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
headers: headers,
|
|
140
|
-
body: payload,
|
|
141
|
-
signal: AbortSignal.timeout(timeoutMs || PUBLISH_TIMEOUT_MS),
|
|
142
|
-
});
|
|
143
|
-
const text = await res.text();
|
|
144
|
-
let data = null;
|
|
145
|
-
try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
|
|
146
|
-
if (res.status >= 200 && res.status < 300) {
|
|
147
|
-
return { ok: true, status: res.status, data };
|
|
148
|
-
}
|
|
149
|
-
return { ok: false, status: res.status, data, error: 'http_' + res.status };
|
|
150
|
-
} catch (err) {
|
|
151
|
-
const msg = (err && err.message) || String(err);
|
|
152
|
-
if (msg.indexOf('[hubFetch]') !== -1) {
|
|
153
|
-
if (/not a valid URL/i.test(msg)) return { ok: false, error: 'invalid_url: ' + msg };
|
|
154
|
-
return { ok: false, error: 'tls_refused: ' + msg };
|
|
155
|
-
}
|
|
156
|
-
return { ok: false, error: msg };
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function _ensureNodeSecret() {
|
|
161
|
-
if (getHubNodeSecret()) return true;
|
|
162
|
-
try {
|
|
163
|
-
const hello = await sendHelloToHub();
|
|
164
|
-
return !!(hello && hello.ok);
|
|
165
|
-
} catch (e) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function _publishBundle(gene, capsule) {
|
|
171
|
-
const nodeSecret = getHubNodeSecret();
|
|
172
|
-
if (!nodeSecret) return { ok: false, error: 'missing_node_secret_after_hello' };
|
|
173
|
-
const signatureInput = [gene.asset_id, capsule.asset_id].sort().join('|');
|
|
174
|
-
const signature = crypto.createHmac('sha256', nodeSecret).update(signatureInput).digest('hex');
|
|
175
|
-
const msg = {
|
|
176
|
-
protocol: 'gep-a2a',
|
|
177
|
-
protocol_version: '1.0.0',
|
|
178
|
-
message_type: 'publish',
|
|
179
|
-
message_id: 'msg_atp_' + crypto.randomBytes(8).toString('hex'),
|
|
180
|
-
timestamp: new Date().toISOString(),
|
|
181
|
-
sender_id: getNodeId(),
|
|
182
|
-
payload: { assets: [gene, capsule], signature: signature },
|
|
183
|
-
};
|
|
184
|
-
return _postJson(_publishUrl(), msg, PUBLISH_TIMEOUT_MS);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function _completeTaskOnHub(taskId, assetId) {
|
|
188
|
-
const base = String(getHubUrl() || '').replace(/\/+$/, '');
|
|
189
|
-
if (!base) return { ok: false, error: 'hub_url_missing' };
|
|
190
|
-
return _postJson(base + '/a2a/task/complete', {
|
|
191
|
-
task_id: taskId,
|
|
192
|
-
asset_id: assetId,
|
|
193
|
-
node_id: getNodeId(),
|
|
194
|
-
}, PUBLISH_TIMEOUT_MS);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* End-to-end ATP task completion driver.
|
|
199
|
-
*
|
|
200
|
-
* @param {object} opts
|
|
201
|
-
* @param {string} opts.taskId - Hub task row id (required)
|
|
202
|
-
* @param {string} opts.orderId - ATP DeliveryProof id (required)
|
|
203
|
-
* @param {string} opts.answerFile - Path to file holding the merchant answer (required)
|
|
204
|
-
* @param {string} [opts.summary] - Short summary for capsule.summary
|
|
205
|
-
* @param {string[]} [opts.capabilities] - Listing capabilities (metadata only)
|
|
206
|
-
* @param {string[]} [opts.signals] - Task signals (metadata only)
|
|
207
|
-
* @returns {Promise<{ok:boolean, stage?:string, error?:string, assetId?:string}>}
|
|
208
|
-
*/
|
|
209
|
-
async function completeAtpTask(opts) {
|
|
210
|
-
const taskId = opts && opts.taskId;
|
|
211
|
-
const orderId = opts && opts.orderId;
|
|
212
|
-
const answerFile = opts && opts.answerFile;
|
|
213
|
-
if (!taskId || !orderId || !answerFile) {
|
|
214
|
-
return { ok: false, stage: 'input', error: 'taskId, orderId, answerFile are required' };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let answer;
|
|
218
|
-
try {
|
|
219
|
-
answer = _readAnswer(answerFile);
|
|
220
|
-
} catch (e) {
|
|
221
|
-
return { ok: false, stage: 'read_answer', error: e && e.message };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const handshakeOk = await _ensureNodeSecret();
|
|
225
|
-
if (!handshakeOk) {
|
|
226
|
-
return { ok: false, stage: 'hello', error: 'failed to register with hub; node_secret missing' };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const gene = _buildGene(opts.capabilities, opts.signals);
|
|
230
|
-
const capsule = _buildCapsule({
|
|
231
|
-
gene,
|
|
232
|
-
answer,
|
|
233
|
-
summary: opts.summary,
|
|
234
|
-
orderId,
|
|
235
|
-
taskId,
|
|
236
|
-
capabilities: opts.capabilities,
|
|
237
|
-
signals: opts.signals,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const pub = await _publishBundle(gene, capsule);
|
|
241
|
-
if (!pub.ok) {
|
|
242
|
-
return { ok: false, stage: 'publish', error: pub.error || 'publish_failed', details: pub };
|
|
243
|
-
}
|
|
244
|
-
const decision = pub.data && pub.data.payload && pub.data.payload.decision;
|
|
245
|
-
if (decision && decision !== 'accept') {
|
|
246
|
-
const reason = pub.data.payload.reason || 'unknown';
|
|
247
|
-
return { ok: false, stage: 'publish', error: 'publish_rejected: ' + reason, details: pub.data };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const complete = await _completeTaskOnHub(taskId, capsule.asset_id);
|
|
251
|
-
if (!complete.ok) {
|
|
252
|
-
return { ok: false, stage: 'complete', error: complete.error || 'complete_failed', details: complete };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const proofPayload = {
|
|
256
|
-
asset_id: capsule.asset_id,
|
|
257
|
-
result: capsule.summary,
|
|
258
|
-
content_hash: capsule.asset_id,
|
|
259
|
-
pass_rate: 1.0,
|
|
260
|
-
delivered_by: getNodeId(),
|
|
261
|
-
task_id: taskId,
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const delivery = await submitDelivery(orderId, proofPayload);
|
|
265
|
-
if (!delivery || !delivery.ok) {
|
|
266
|
-
return {
|
|
267
|
-
ok: false,
|
|
268
|
-
stage: 'deliver',
|
|
269
|
-
error: (delivery && delivery.error) || 'deliver_failed',
|
|
270
|
-
assetId: capsule.asset_id,
|
|
271
|
-
details: delivery,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return { ok: true, assetId: capsule.asset_id, deliveryId: delivery.data && delivery.data.proof_id };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
module.exports = {
|
|
279
|
-
completeAtpTask,
|
|
280
|
-
// exported for tests
|
|
281
|
-
_buildGene,
|
|
282
|
-
_buildCapsule,
|
|
283
|
-
};
|
package/src/atp/atpTaskPickup.js
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
// ATP Task Pickup (merchant-side)
|
|
2
|
-
//
|
|
3
|
-
// When a buyer places an ATP order, the Hub creates a Task row in status
|
|
4
|
-
// "claimed" already bound to a target merchant node (see orderRouterService).
|
|
5
|
-
// That Task never appears in /a2a/fetch (which only returns status="open"
|
|
6
|
-
// tasks), so without this module the merchant's Evolver runtime never knows
|
|
7
|
-
// it has work to do, no resultAssetId is ever written, autoDeliver never
|
|
8
|
-
// runs, and the DeliveryProof expires after 7 days.
|
|
9
|
-
//
|
|
10
|
-
// This module bridges the gap by:
|
|
11
|
-
// 1. Polling /a2a/task/my for tasks with atp_order_id set and no
|
|
12
|
-
// result_asset_id yet (the "merchant owes work" shape).
|
|
13
|
-
// 2. Producing a renderable sessions_spawn(...) prompt that the main loop
|
|
14
|
-
// can emit to stdout. The Evolver wrapper (Cursor/Claude Code hook)
|
|
15
|
-
// picks that up and launches a sub-session that answers the question
|
|
16
|
-
// and runs `node index.js atp-complete` to settle the order.
|
|
17
|
-
// 3. Deduping via a local ledger so the same task is never spawned twice,
|
|
18
|
-
// even across restarts.
|
|
19
|
-
//
|
|
20
|
-
// The module never *itself* prints sessions_spawn. It only PROVIDES the
|
|
21
|
-
// spawn string to whoever orchestrates stdout (evolve.js main loop), so the
|
|
22
|
-
// existing "one sessions_spawn per cycle" contract with the wrapper is
|
|
23
|
-
// preserved and evolve's normal bridge is not interfered with.
|
|
24
|
-
|
|
25
|
-
const fs = require('fs');
|
|
26
|
-
const path = require('path');
|
|
27
|
-
|
|
28
|
-
const { getMemoryDir } = require('../gep/paths');
|
|
29
|
-
const { renderSessionsSpawnCall } = require('../gep/bridge');
|
|
30
|
-
const hubClient = require('./hubClient');
|
|
31
|
-
|
|
32
|
-
const LEDGER_FILENAME = 'atp-pickup-ledger.json';
|
|
33
|
-
const LEDGER_MAX_ENTRIES = 500;
|
|
34
|
-
const SPAWN_COOLDOWN_MS = 5 * 60 * 1000; // do not respawn the same task within 5 min
|
|
35
|
-
const MAX_ANSWER_PROMPT_CHARS = 12000;
|
|
36
|
-
|
|
37
|
-
function _isEnabled() {
|
|
38
|
-
const raw = (process.env.EVOLVER_ATP_PICKUP || 'on').toLowerCase().trim();
|
|
39
|
-
return raw !== 'off' && raw !== '0' && raw !== 'false';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function _ledgerPath() {
|
|
43
|
-
return path.join(getMemoryDir(), LEDGER_FILENAME);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function _emptyLedger() {
|
|
47
|
-
return { version: 1, spawned: {} };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function _readLedger() {
|
|
51
|
-
try {
|
|
52
|
-
const p = _ledgerPath();
|
|
53
|
-
if (!fs.existsSync(p)) return _emptyLedger();
|
|
54
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
55
|
-
const parsed = JSON.parse(raw);
|
|
56
|
-
if (!parsed || typeof parsed !== 'object' || !parsed.spawned) return _emptyLedger();
|
|
57
|
-
return parsed;
|
|
58
|
-
} catch (_) {
|
|
59
|
-
return _emptyLedger();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function _writeLedger(ledger) {
|
|
64
|
-
try {
|
|
65
|
-
const dir = getMemoryDir();
|
|
66
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
67
|
-
const entries = Object.entries(ledger.spawned || {});
|
|
68
|
-
if (entries.length > LEDGER_MAX_ENTRIES) {
|
|
69
|
-
ledger.spawned = Object.fromEntries(entries.slice(-LEDGER_MAX_ENTRIES));
|
|
70
|
-
}
|
|
71
|
-
const tmp = _ledgerPath() + '.tmp';
|
|
72
|
-
fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
|
|
73
|
-
fs.renameSync(tmp, _ledgerPath());
|
|
74
|
-
} catch (_) {
|
|
75
|
-
// Non-fatal: next tick will re-read Hub state. Stale ledger at worst
|
|
76
|
-
// causes a duplicate spawn, which the Hub will 409 on already-completed
|
|
77
|
-
// tasks without side effects.
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function _isEligible(task) {
|
|
82
|
-
if (!task || typeof task !== 'object') return false;
|
|
83
|
-
if (!task.atp_order_id) return false;
|
|
84
|
-
if (task.result_asset_id) return false;
|
|
85
|
-
if (task.status && task.status !== 'claimed' && task.status !== 'open') return false;
|
|
86
|
-
if (!task.id) return false;
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function _recentlySpawned(ledger, taskId) {
|
|
91
|
-
const entry = ledger.spawned && ledger.spawned[taskId];
|
|
92
|
-
if (!entry || typeof entry !== 'object') return false;
|
|
93
|
-
const ts = Number(entry.at) || 0;
|
|
94
|
-
return Date.now() - ts < SPAWN_COOLDOWN_MS;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function _clipQuestion(q) {
|
|
98
|
-
const s = String(q || '').trim();
|
|
99
|
-
if (!s) return '(buyer did not provide a question body)';
|
|
100
|
-
if (s.length <= MAX_ANSWER_PROMPT_CHARS) return s;
|
|
101
|
-
return s.slice(0, MAX_ANSWER_PROMPT_CHARS - 40) + '\n...[TRUNCATED]...';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function _answerFilePath(taskId) {
|
|
105
|
-
const safe = String(taskId || 'task').replace(/[^a-zA-Z0-9_\-]/g, '_').slice(0, 64);
|
|
106
|
-
return path.join(getMemoryDir(), 'atp_answer_' + safe + '.md');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function _buildSpawnTask(task, opts) {
|
|
110
|
-
const capabilities = Array.isArray(task.capabilities) ? task.capabilities.slice(0, 8) : [];
|
|
111
|
-
const signalsCsv = task.signals ? String(task.signals) : '';
|
|
112
|
-
const answerPath = _answerFilePath(task.id);
|
|
113
|
-
const question = _clipQuestion(task.user_question_body || task.description || task.title);
|
|
114
|
-
|
|
115
|
-
const evolverExec = opts && opts.evolverExec ? opts.evolverExec : 'node index.js';
|
|
116
|
-
|
|
117
|
-
const lines = [
|
|
118
|
-
'You are an ATP merchant sub-agent. A buyer has paid credits for your node to answer their request.',
|
|
119
|
-
'',
|
|
120
|
-
'# Task',
|
|
121
|
-
'- Task ID: ' + task.id,
|
|
122
|
-
'- ATP Order ID: ' + task.atp_order_id,
|
|
123
|
-
'- Title: ' + String(task.title || '(no title)').slice(0, 200),
|
|
124
|
-
'- Capabilities requested: ' + (capabilities.length ? capabilities.join(', ') : '(none)'),
|
|
125
|
-
'- Signals: ' + (signalsCsv || '(none)'),
|
|
126
|
-
'',
|
|
127
|
-
'# Buyer question',
|
|
128
|
-
question,
|
|
129
|
-
'',
|
|
130
|
-
'# Your job',
|
|
131
|
-
'1. Produce a concrete, useful answer to the buyer question above.',
|
|
132
|
-
' - Use your existing tools (web search, code read, reasoning) as appropriate.',
|
|
133
|
-
' - Keep the answer focused and actionable; do not invent facts.',
|
|
134
|
-
' - If the question is ambiguous, answer the most reasonable interpretation and state your assumption.',
|
|
135
|
-
'2. Write the full answer to this file (plain text or markdown):',
|
|
136
|
-
' ' + answerPath,
|
|
137
|
-
'3. Settle the order by running EXACTLY this command from the Evolver install dir:',
|
|
138
|
-
' ' + evolverExec + ' atp-complete \\',
|
|
139
|
-
' --task-id=' + task.id + ' \\',
|
|
140
|
-
' --order-id=' + task.atp_order_id + ' \\',
|
|
141
|
-
' --answer-file=' + answerPath +
|
|
142
|
-
(capabilities.length ? ' \\\n --capabilities=' + capabilities.join(',') : '') +
|
|
143
|
-
(signalsCsv ? ' \\\n --signals=' + signalsCsv : ''),
|
|
144
|
-
'4. If atp-complete prints "[ATP-Complete] OK asset_id=...", you are done.',
|
|
145
|
-
' If it prints "FAILED", read the stage= field. Safe to retry the same command.',
|
|
146
|
-
'',
|
|
147
|
-
'# Hard rules',
|
|
148
|
-
'- Do NOT commit or push any repo changes -- this is a per-order side task, not a code evolution.',
|
|
149
|
-
'- Do NOT run `node index.js solidify` or `node index.js run`.',
|
|
150
|
-
'- Do NOT fabricate the answer; if you cannot answer, still run atp-complete with a short',
|
|
151
|
-
' honest explanation so the buyer is not left waiting for 7 days.',
|
|
152
|
-
'- Keep the answer under 12k characters.',
|
|
153
|
-
];
|
|
154
|
-
return lines.join('\n');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Fetch a pickup action if one is due. Idempotent -- safe to call from the
|
|
159
|
-
* main loop every cycle.
|
|
160
|
-
*
|
|
161
|
-
* @param {object} [opts]
|
|
162
|
-
* @param {number} [opts.limit=5] -- how many Hub tasks to consider per call
|
|
163
|
-
* @param {string} [opts.evolverExec] -- how the wrapper should invoke Evolver
|
|
164
|
-
* @returns {Promise<null | { spawnCall: string, task: object }>}
|
|
165
|
-
* null when there is nothing to do; otherwise a sessions_spawn() string
|
|
166
|
-
* the caller SHOULD print to stdout on its next cycle output and the task
|
|
167
|
-
* we picked so the caller can log it.
|
|
168
|
-
*/
|
|
169
|
-
async function pickOne(opts) {
|
|
170
|
-
if (!_isEnabled()) return null;
|
|
171
|
-
const limit = Math.max(1, Math.min(20, Number(opts && opts.limit) || 5));
|
|
172
|
-
|
|
173
|
-
let listResult;
|
|
174
|
-
try {
|
|
175
|
-
listResult = await hubClient.listMyTasks(limit);
|
|
176
|
-
} catch (_) {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
if (!listResult || !listResult.ok) return null;
|
|
180
|
-
|
|
181
|
-
const tasks = (listResult.data && Array.isArray(listResult.data.tasks))
|
|
182
|
-
? listResult.data.tasks
|
|
183
|
-
: (Array.isArray(listResult.data) ? listResult.data : []);
|
|
184
|
-
if (!tasks.length) return null;
|
|
185
|
-
|
|
186
|
-
const ledger = _readLedger();
|
|
187
|
-
let picked = null;
|
|
188
|
-
for (const t of tasks) {
|
|
189
|
-
if (!_isEligible(t)) continue;
|
|
190
|
-
if (_recentlySpawned(ledger, t.id)) continue;
|
|
191
|
-
picked = t;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
if (!picked) return null;
|
|
195
|
-
|
|
196
|
-
const spawnTask = _buildSpawnTask(picked, opts);
|
|
197
|
-
const spawnCall = renderSessionsSpawnCall({
|
|
198
|
-
task: spawnTask,
|
|
199
|
-
agentId: 'atp_pickup',
|
|
200
|
-
cleanup: 'delete',
|
|
201
|
-
label: 'atp_pickup_' + String(picked.id).slice(0, 32),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
ledger.spawned = ledger.spawned || {};
|
|
205
|
-
ledger.spawned[picked.id] = { at: Date.now(), order_id: picked.atp_order_id };
|
|
206
|
-
_writeLedger(ledger);
|
|
207
|
-
|
|
208
|
-
return { spawnCall, task: picked };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Forget a previously-spawned task so the main loop will retry it next cycle.
|
|
213
|
-
* Called by callers that detected the spawn channel was unavailable (e.g.
|
|
214
|
-
* wrapper not attached) so we do not burn the cooldown on a no-op spawn.
|
|
215
|
-
*/
|
|
216
|
-
function forget(taskId) {
|
|
217
|
-
if (!taskId) return;
|
|
218
|
-
const ledger = _readLedger();
|
|
219
|
-
if (ledger.spawned && ledger.spawned[taskId]) {
|
|
220
|
-
delete ledger.spawned[taskId];
|
|
221
|
-
_writeLedger(ledger);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
module.exports = {
|
|
226
|
-
pickOne,
|
|
227
|
-
forget,
|
|
228
|
-
_isEnabled,
|
|
229
|
-
_isEligible,
|
|
230
|
-
_buildSpawnTask,
|
|
231
|
-
_recentlySpawned,
|
|
232
|
-
_answerFilePath,
|
|
233
|
-
};
|