screenhand 0.1.1 → 0.3.0
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 +193 -109
- package/bin/darwin-arm64/macos-bridge +0 -0
- package/dist/mcp-desktop.js +5876 -0
- package/dist/scripts/codex-monitor-daemon.js +335 -0
- package/dist/scripts/export-help-center.js +112 -0
- package/dist/scripts/marketing-loop.js +117 -0
- package/dist/scripts/observer-daemon.js +288 -0
- package/dist/scripts/orchestrator-daemon.js +399 -0
- package/dist/scripts/supervisor-daemon.js +272 -0
- package/dist/scripts/threads-campaign.js +208 -0
- package/dist/scripts/worker-daemon.js +228 -0
- package/dist/src/agent/cli.js +82 -0
- package/dist/src/agent/loop.js +274 -0
- package/dist/src/community/fetcher.js +109 -0
- package/dist/src/community/index.js +6 -0
- package/dist/src/community/publisher.js +191 -0
- package/dist/src/community/remote-api.js +121 -0
- package/dist/src/community/types.js +3 -0
- package/dist/src/community/validator.js +95 -0
- package/{src/config.ts → dist/src/config.js} +5 -10
- package/dist/src/context-tracker.js +489 -0
- package/{src/index.ts → dist/src/index.js} +32 -52
- package/dist/src/ingestion/coverage-auditor.js +233 -0
- package/dist/src/ingestion/doc-parser.js +164 -0
- package/dist/src/ingestion/index.js +8 -0
- package/dist/src/ingestion/menu-scanner.js +152 -0
- package/dist/src/ingestion/reference-merger.js +186 -0
- package/dist/src/ingestion/shortcut-extractor.js +180 -0
- package/dist/src/ingestion/tutorial-extractor.js +170 -0
- package/dist/src/ingestion/types.js +3 -0
- package/dist/src/jobs/manager.js +305 -0
- package/dist/src/jobs/runner.js +806 -0
- package/dist/src/jobs/store.js +102 -0
- package/dist/src/jobs/types.js +30 -0
- package/dist/src/jobs/worker.js +97 -0
- package/dist/src/learning/engine.js +356 -0
- package/dist/src/learning/index.js +9 -0
- package/dist/src/learning/locator-policy.js +120 -0
- package/dist/src/learning/pattern-policy.js +89 -0
- package/dist/src/learning/recovery-policy.js +116 -0
- package/dist/src/learning/sensor-policy.js +115 -0
- package/dist/src/learning/timing-model.js +204 -0
- package/dist/src/learning/topology-policy.js +90 -0
- package/dist/src/learning/types.js +9 -0
- package/dist/src/logging/timeline-logger.js +48 -0
- package/dist/src/mcp/mcp-stdio-server.js +464 -0
- package/dist/src/mcp/server.js +363 -0
- package/dist/src/mcp-entry.js +60 -0
- package/dist/src/memory/playbook-seeds.js +200 -0
- package/dist/src/memory/recall.js +222 -0
- package/dist/src/memory/research.js +104 -0
- package/dist/src/memory/seeds.js +101 -0
- package/dist/src/memory/service.js +446 -0
- package/dist/src/memory/session.js +169 -0
- package/dist/src/memory/store.js +451 -0
- package/{src/runtime/locator-cache.ts → dist/src/memory/types.js} +1 -17
- package/dist/src/monitor/codex-monitor.js +382 -0
- package/dist/src/monitor/task-queue.js +97 -0
- package/dist/src/monitor/types.js +62 -0
- package/dist/src/native/bridge-client.js +412 -0
- package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
- package/dist/src/observer/state.js +199 -0
- package/dist/src/observer/types.js +43 -0
- package/dist/src/orchestrator/state.js +68 -0
- package/dist/src/orchestrator/types.js +22 -0
- package/dist/src/perception/ax-source.js +162 -0
- package/dist/src/perception/cdp-source.js +162 -0
- package/dist/src/perception/coordinator.js +771 -0
- package/dist/src/perception/frame-differ.js +287 -0
- package/dist/src/perception/index.js +22 -0
- package/dist/src/perception/manager.js +199 -0
- package/dist/src/perception/types.js +47 -0
- package/dist/src/perception/vision-source.js +399 -0
- package/dist/src/planner/deterministic.js +298 -0
- package/dist/src/planner/executor.js +870 -0
- package/dist/src/planner/goal-store.js +92 -0
- package/dist/src/planner/index.js +21 -0
- package/dist/src/planner/planner.js +520 -0
- package/dist/src/planner/tool-registry.js +71 -0
- package/dist/src/planner/types.js +22 -0
- package/dist/src/platform/explorer.js +213 -0
- package/dist/src/platform/help-center-markdown.js +527 -0
- package/dist/src/platform/learner.js +257 -0
- package/dist/src/playbook/engine.js +486 -0
- package/dist/src/playbook/index.js +20 -0
- package/dist/src/playbook/mcp-recorder.js +204 -0
- package/dist/src/playbook/recorder.js +536 -0
- package/dist/src/playbook/runner.js +408 -0
- package/dist/src/playbook/store.js +312 -0
- package/dist/src/playbook/types.js +17 -0
- package/dist/src/recovery/detectors.js +156 -0
- package/dist/src/recovery/engine.js +327 -0
- package/dist/src/recovery/index.js +20 -0
- package/dist/src/recovery/strategies.js +274 -0
- package/dist/src/recovery/types.js +20 -0
- package/dist/src/runtime/accessibility-adapter.js +430 -0
- package/dist/src/runtime/app-adapter.js +64 -0
- package/dist/src/runtime/applescript-adapter.js +305 -0
- package/dist/src/runtime/ax-role-map.js +96 -0
- package/dist/src/runtime/browser-adapter.js +52 -0
- package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
- package/dist/src/runtime/composite-adapter.js +221 -0
- package/dist/src/runtime/execution-contract.js +159 -0
- package/dist/src/runtime/executor.js +286 -0
- package/dist/src/runtime/locator-cache.js +50 -0
- package/dist/src/runtime/planning-loop.js +63 -0
- package/dist/src/runtime/service.js +432 -0
- package/dist/src/runtime/session-manager.js +63 -0
- package/dist/src/runtime/state-observer.js +121 -0
- package/dist/src/runtime/vision-adapter.js +225 -0
- package/dist/src/state/app-map-types.js +72 -0
- package/dist/src/state/app-map.js +1974 -0
- package/dist/src/state/entity-tracker.js +108 -0
- package/dist/src/state/fusion.js +96 -0
- package/dist/src/state/index.js +21 -0
- package/dist/src/state/ladder-generator.js +236 -0
- package/dist/src/state/persistence.js +156 -0
- package/dist/src/state/types.js +17 -0
- package/dist/src/state/world-model.js +1456 -0
- package/dist/src/supervisor/locks.js +186 -0
- package/dist/src/supervisor/supervisor.js +403 -0
- package/dist/src/supervisor/types.js +30 -0
- package/dist/src/test-mcp-protocol.js +154 -0
- package/dist/src/types.js +17 -0
- package/dist/src/util/atomic-write.js +133 -0
- package/dist/src/util/sanitize.js +146 -0
- package/dist-app-maps/com.figma.Desktop.json +959 -0
- package/dist-app-maps/com.hnc.Discord.json +1146 -0
- package/dist-app-maps/notion.id.json +2831 -0
- package/dist-playbooks/canva-screenhand-carousel.json +445 -0
- package/dist-playbooks/codex-desktop.json +76 -0
- package/dist-playbooks/competitor-research-stack.json +122 -0
- package/dist-playbooks/davinci-color-grade.json +153 -0
- package/dist-playbooks/davinci-edit-timeline.json +162 -0
- package/dist-playbooks/davinci-render.json +114 -0
- package/dist-playbooks/devto.json +52 -0
- package/dist-playbooks/discord.json +41 -0
- package/dist-playbooks/google-flow-create-project.json +59 -0
- package/dist-playbooks/google-flow-edit-image.json +90 -0
- package/dist-playbooks/google-flow-edit-video.json +90 -0
- package/dist-playbooks/google-flow-generate-image.json +68 -0
- package/dist-playbooks/google-flow-generate-video.json +191 -0
- package/dist-playbooks/google-flow-open-project.json +48 -0
- package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
- package/dist-playbooks/google-flow-search-assets.json +64 -0
- package/dist-playbooks/instagram.json +57 -0
- package/dist-playbooks/linkedin.json +52 -0
- package/dist-playbooks/n8n.json +43 -0
- package/dist-playbooks/reddit.json +52 -0
- package/dist-playbooks/threads.json +59 -0
- package/dist-playbooks/x-twitter.json +59 -0
- package/dist-playbooks/youtube.json +59 -0
- package/dist-references/canva.json +646 -0
- package/dist-references/codex-desktop.json +305 -0
- package/dist-references/davinci-resolve-keyboard.json +594 -0
- package/dist-references/davinci-resolve-menu-map.json +1139 -0
- package/dist-references/davinci-resolve-menus-batch1.json +116 -0
- package/dist-references/davinci-resolve-menus-batch2.json +372 -0
- package/dist-references/davinci-resolve-menus-batch3.json +330 -0
- package/dist-references/davinci-resolve-menus-batch4.json +297 -0
- package/dist-references/davinci-resolve-shortcuts.json +333 -0
- package/dist-references/devto.json +317 -0
- package/dist-references/discord.json +549 -0
- package/dist-references/figma.json +1186 -0
- package/dist-references/finder.json +146 -0
- package/dist-references/google-ads-transparency.json +95 -0
- package/dist-references/google-flow.json +649 -0
- package/dist-references/instagram.json +341 -0
- package/dist-references/linkedin.json +324 -0
- package/dist-references/meta-ad-library.json +86 -0
- package/dist-references/n8n.json +387 -0
- package/dist-references/notes.json +27 -0
- package/dist-references/notion.json +163 -0
- package/dist-references/reddit.json +341 -0
- package/dist-references/threads.json +337 -0
- package/dist-references/x-twitter.json +403 -0
- package/dist-references/youtube.json +373 -0
- package/native/macos-bridge/Package.swift +1 -0
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +257 -36
- package/native/macos-bridge/Sources/AppManagement.swift +212 -2
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +348 -53
- package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
- package/native/macos-bridge/Sources/VisionBridge.swift +165 -7
- package/native/macos-bridge/Sources/main.swift +169 -16
- package/native/windows-bridge/Program.cs +5 -0
- package/native/windows-bridge/ScreenCapture.cs +124 -0
- package/package.json +29 -4
- package/scripts/postinstall.cjs +127 -0
- package/.claude/commands/automate.md +0 -28
- package/.claude/commands/debug-ui.md +0 -19
- package/.claude/commands/screenshot.md +0 -15
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.mcp.json +0 -8
- package/DESKTOP_MCP_GUIDE.md +0 -92
- package/SECURITY.md +0 -44
- package/docs/architecture.md +0 -47
- package/install-skills.sh +0 -19
- package/mcp-bridge.ts +0 -271
- package/mcp-desktop.ts +0 -1221
- package/playbooks/instagram.json +0 -41
- package/playbooks/instagram_v2.json +0 -201
- package/playbooks/x_v1.json +0 -211
- package/scripts/devpost-live-loop.mjs +0 -421
- package/src/logging/timeline-logger.ts +0 -55
- package/src/mcp/server.ts +0 -449
- package/src/memory/recall.ts +0 -191
- package/src/memory/research.ts +0 -146
- package/src/memory/seeds.ts +0 -123
- package/src/memory/session.ts +0 -201
- package/src/memory/store.ts +0 -434
- package/src/memory/types.ts +0 -69
- package/src/native/bridge-client.ts +0 -239
- package/src/runtime/accessibility-adapter.ts +0 -487
- package/src/runtime/app-adapter.ts +0 -169
- package/src/runtime/applescript-adapter.ts +0 -376
- package/src/runtime/ax-role-map.ts +0 -102
- package/src/runtime/browser-adapter.ts +0 -129
- package/src/runtime/cdp-chrome-adapter.ts +0 -676
- package/src/runtime/composite-adapter.ts +0 -274
- package/src/runtime/executor.ts +0 -396
- package/src/runtime/planning-loop.ts +0 -81
- package/src/runtime/service.ts +0 -448
- package/src/runtime/session-manager.ts +0 -50
- package/src/runtime/state-observer.ts +0 -136
- package/src/runtime/vision-adapter.ts +0 -297
- package/src/types.ts +0 -297
- package/tests/bridge-client.test.ts +0 -176
- package/tests/browser-stealth.test.ts +0 -210
- package/tests/composite-adapter.test.ts +0 -64
- package/tests/mcp-server.test.ts +0 -151
- package/tests/memory-recall.test.ts +0 -339
- package/tests/memory-research.test.ts +0 -159
- package/tests/memory-seeds.test.ts +0 -120
- package/tests/memory-store.test.ts +0 -392
- package/tests/types.test.ts +0 -92
- package/tsconfig.check.json +0 -17
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
- /package/{playbooks → dist-references}/devpost.json +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Copyright (C) 2025 Clazro Technology Private Limited
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
//
|
|
4
|
+
// This file is part of ScreenHand.
|
|
5
|
+
//
|
|
6
|
+
// ScreenHand is free software: you can redistribute it and/or modify
|
|
7
|
+
// it under the terms of the GNU Affero General Public License as
|
|
8
|
+
// published by the Free Software Foundation, version 3.
|
|
9
|
+
//
|
|
10
|
+
// ScreenHand is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with ScreenHand. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
import { VALID_TRANSITIONS, JOB_STATES } from "./types.js";
|
|
18
|
+
import { JobStore } from "./store.js";
|
|
19
|
+
export class JobManager {
|
|
20
|
+
store;
|
|
21
|
+
memory;
|
|
22
|
+
supervisor;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.store = new JobStore(opts.jobDir);
|
|
25
|
+
this.memory = opts.memory ?? null;
|
|
26
|
+
this.supervisor = opts.supervisor ?? null;
|
|
27
|
+
}
|
|
28
|
+
init() {
|
|
29
|
+
this.store.init();
|
|
30
|
+
}
|
|
31
|
+
// ── Create ──────────────────────────────────────
|
|
32
|
+
create(opts) {
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
const id = "job_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8);
|
|
35
|
+
const steps = (opts.steps ?? []).map((s, i) => {
|
|
36
|
+
const step = { index: i, action: s.action, status: "pending" };
|
|
37
|
+
if (s.target !== undefined)
|
|
38
|
+
step.target = s.target;
|
|
39
|
+
if (s.description !== undefined)
|
|
40
|
+
step.description = s.description;
|
|
41
|
+
if (s.text !== undefined)
|
|
42
|
+
step.text = s.text;
|
|
43
|
+
if (s.keys !== undefined)
|
|
44
|
+
step.keys = s.keys;
|
|
45
|
+
if (s.value !== undefined)
|
|
46
|
+
step.value = s.value;
|
|
47
|
+
return step;
|
|
48
|
+
});
|
|
49
|
+
const job = {
|
|
50
|
+
id,
|
|
51
|
+
task: opts.task,
|
|
52
|
+
state: "queued",
|
|
53
|
+
playbookId: opts.playbookId ?? null,
|
|
54
|
+
sessionId: opts.sessionId ?? null,
|
|
55
|
+
bundleId: opts.bundleId ?? null,
|
|
56
|
+
windowId: opts.windowId ?? null,
|
|
57
|
+
lastStep: -1,
|
|
58
|
+
steps,
|
|
59
|
+
blockReason: null,
|
|
60
|
+
retries: 0,
|
|
61
|
+
maxRetries: opts.maxRetries ?? 3,
|
|
62
|
+
lastError: null,
|
|
63
|
+
tags: opts.tags ?? [],
|
|
64
|
+
priority: opts.priority ?? 10,
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
startedAt: null,
|
|
68
|
+
completedAt: null,
|
|
69
|
+
};
|
|
70
|
+
if (opts.chainId)
|
|
71
|
+
job.chainId = opts.chainId;
|
|
72
|
+
if (opts.dependsOn) {
|
|
73
|
+
// Validate dependency exists to prevent permanently stuck jobs
|
|
74
|
+
const dep = this.store.get(opts.dependsOn);
|
|
75
|
+
if (!dep) {
|
|
76
|
+
throw new Error(`Dependency job ${opts.dependsOn} does not exist`);
|
|
77
|
+
}
|
|
78
|
+
job.dependsOn = opts.dependsOn;
|
|
79
|
+
}
|
|
80
|
+
if (opts.vars)
|
|
81
|
+
job.vars = opts.vars;
|
|
82
|
+
this.store.add(job);
|
|
83
|
+
return job;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a chain of linked jobs. Returns all created jobs.
|
|
87
|
+
* Each job depends on the previous one. Variables from prior job outputs
|
|
88
|
+
* are automatically passed forward using {jobId.outputKey} syntax.
|
|
89
|
+
*/
|
|
90
|
+
createChain(opts) {
|
|
91
|
+
const chainId = opts.chainId ?? "chain_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8);
|
|
92
|
+
const created = [];
|
|
93
|
+
let prevId;
|
|
94
|
+
for (const jobOpts of opts.jobs) {
|
|
95
|
+
const job = this.create({
|
|
96
|
+
...jobOpts,
|
|
97
|
+
chainId,
|
|
98
|
+
...(prevId ? { dependsOn: prevId } : {}),
|
|
99
|
+
});
|
|
100
|
+
created.push(job);
|
|
101
|
+
prevId = job.id;
|
|
102
|
+
}
|
|
103
|
+
return created;
|
|
104
|
+
}
|
|
105
|
+
// ── State transitions ───────────────────────────
|
|
106
|
+
transition(id, to, opts) {
|
|
107
|
+
const job = this.store.get(id);
|
|
108
|
+
if (!job)
|
|
109
|
+
return { error: `Job ${id} not found` };
|
|
110
|
+
const allowed = VALID_TRANSITIONS[job.state];
|
|
111
|
+
if (!allowed.includes(to)) {
|
|
112
|
+
return { error: `Cannot transition from "${job.state}" to "${to}". Allowed: [${allowed.join(", ")}]` };
|
|
113
|
+
}
|
|
114
|
+
const patch = { state: to };
|
|
115
|
+
if (to === "running") {
|
|
116
|
+
if (!job.startedAt)
|
|
117
|
+
patch.startedAt = new Date().toISOString();
|
|
118
|
+
patch.blockReason = null;
|
|
119
|
+
if (opts?.sessionId)
|
|
120
|
+
patch.sessionId = opts.sessionId;
|
|
121
|
+
}
|
|
122
|
+
if (to === "blocked" || to === "waiting_human") {
|
|
123
|
+
patch.blockReason = opts?.blockReason ?? null;
|
|
124
|
+
}
|
|
125
|
+
if (to === "failed") {
|
|
126
|
+
patch.lastError = opts?.error ?? job.lastError;
|
|
127
|
+
this.recordOutcomeToMemory(job, false);
|
|
128
|
+
}
|
|
129
|
+
if (to === "done") {
|
|
130
|
+
patch.completedAt = new Date().toISOString();
|
|
131
|
+
this.recordOutcomeToMemory(job, true);
|
|
132
|
+
}
|
|
133
|
+
// Re-queue bumps retry count
|
|
134
|
+
if (job.state === "failed" && to === "queued") {
|
|
135
|
+
patch.retries = job.retries + 1;
|
|
136
|
+
if (patch.retries > job.maxRetries) {
|
|
137
|
+
return { error: `Job ${id} has exceeded max retries (${job.maxRetries})` };
|
|
138
|
+
}
|
|
139
|
+
patch.lastError = null;
|
|
140
|
+
patch.blockReason = null;
|
|
141
|
+
}
|
|
142
|
+
const updated = this.store.update(id, patch);
|
|
143
|
+
return updated ?? { error: `Failed to update job ${id}` };
|
|
144
|
+
}
|
|
145
|
+
// ── Step tracking ───────────────────────────────
|
|
146
|
+
/** Mark a step as completed and advance lastStep. Optionally capture output. */
|
|
147
|
+
completeStep(jobId, stepIndex, opts) {
|
|
148
|
+
const job = this.store.get(jobId);
|
|
149
|
+
if (!job)
|
|
150
|
+
return { error: `Job ${jobId} not found` };
|
|
151
|
+
if (job.state !== "running")
|
|
152
|
+
return { error: `Job is not running (state=${job.state})` };
|
|
153
|
+
const step = job.steps[stepIndex];
|
|
154
|
+
if (!step)
|
|
155
|
+
return { error: `Step ${stepIndex} does not exist (total: ${job.steps.length})` };
|
|
156
|
+
step.status = "done";
|
|
157
|
+
step.completedAt = new Date().toISOString();
|
|
158
|
+
if (opts?.durationMs !== undefined)
|
|
159
|
+
step.durationMs = opts.durationMs;
|
|
160
|
+
if (opts?.output !== undefined)
|
|
161
|
+
step.output = opts.output;
|
|
162
|
+
// Also store in job-level outputs for cross-job variable passing
|
|
163
|
+
const patch = { lastStep: Math.max(job.lastStep, stepIndex), steps: job.steps };
|
|
164
|
+
if (opts?.output !== undefined) {
|
|
165
|
+
const outputs = job.outputs ?? {};
|
|
166
|
+
outputs[String(stepIndex)] = opts.output;
|
|
167
|
+
// Also store by step description if available (friendlier key)
|
|
168
|
+
// Include step index to prevent collisions from similar descriptions
|
|
169
|
+
if (step.description) {
|
|
170
|
+
const key = step.description.replace(/[^a-zA-Z0-9_]/g, "_").substring(0, 50);
|
|
171
|
+
outputs[`${key}_${stepIndex}`] = opts.output;
|
|
172
|
+
}
|
|
173
|
+
patch.outputs = outputs;
|
|
174
|
+
}
|
|
175
|
+
return this.store.update(jobId, patch) ?? { error: "Update failed" };
|
|
176
|
+
}
|
|
177
|
+
/** Mark a step as failed. Does NOT transition the job — caller decides (retry vs block vs fail). */
|
|
178
|
+
failStep(jobId, stepIndex, error) {
|
|
179
|
+
const job = this.store.get(jobId);
|
|
180
|
+
if (!job)
|
|
181
|
+
return { error: `Job ${jobId} not found` };
|
|
182
|
+
if (job.state !== "running")
|
|
183
|
+
return { error: `Job is not running (state=${job.state})` };
|
|
184
|
+
const step = job.steps[stepIndex];
|
|
185
|
+
if (!step)
|
|
186
|
+
return { error: `Step ${stepIndex} does not exist` };
|
|
187
|
+
step.status = "failed";
|
|
188
|
+
step.error = error;
|
|
189
|
+
return this.store.update(jobId, { steps: job.steps, lastError: error }) ?? { error: "Update failed" };
|
|
190
|
+
}
|
|
191
|
+
/** Skip a step (e.g., optional step or already done). */
|
|
192
|
+
skipStep(jobId, stepIndex) {
|
|
193
|
+
const job = this.store.get(jobId);
|
|
194
|
+
if (!job)
|
|
195
|
+
return { error: `Job ${jobId} not found` };
|
|
196
|
+
if (job.state !== "running")
|
|
197
|
+
return { error: `Job is not running (state=${job.state})` };
|
|
198
|
+
const step = job.steps[stepIndex];
|
|
199
|
+
if (!step)
|
|
200
|
+
return { error: `Step ${stepIndex} does not exist` };
|
|
201
|
+
step.status = "skipped";
|
|
202
|
+
return this.store.update(jobId, { steps: job.steps }) ?? { error: "Update failed" };
|
|
203
|
+
}
|
|
204
|
+
// ── Resume ──────────────────────────────────────
|
|
205
|
+
/** Get the resume point: next pending step after lastStep. */
|
|
206
|
+
getResumePoint(jobId) {
|
|
207
|
+
const job = this.store.get(jobId);
|
|
208
|
+
if (!job)
|
|
209
|
+
return null;
|
|
210
|
+
for (let i = job.lastStep + 1; i < job.steps.length; i++) {
|
|
211
|
+
if (job.steps[i].status === "pending") {
|
|
212
|
+
return { stepIndex: i, step: job.steps[i] };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
// ── Queries ─────────────────────────────────────
|
|
218
|
+
get(id) {
|
|
219
|
+
return this.store.get(id);
|
|
220
|
+
}
|
|
221
|
+
list(state) {
|
|
222
|
+
return this.store.list(state);
|
|
223
|
+
}
|
|
224
|
+
/** Pop the next queued job and transition it to running. Skips jobs whose dependency isn't done yet. */
|
|
225
|
+
dequeue(sessionId) {
|
|
226
|
+
const queued = this.store.list("queued")
|
|
227
|
+
.sort((a, b) => a.priority - b.priority || new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
228
|
+
for (const candidate of queued) {
|
|
229
|
+
// Check dependency — skip if dependent job isn't done
|
|
230
|
+
if (candidate.dependsOn) {
|
|
231
|
+
const dep = this.store.get(candidate.dependsOn);
|
|
232
|
+
if (!dep || dep.state !== "done")
|
|
233
|
+
continue;
|
|
234
|
+
// Resolve variables from dependency outputs
|
|
235
|
+
if (dep.outputs && candidate.vars) {
|
|
236
|
+
for (const [key, val] of Object.entries(candidate.vars)) {
|
|
237
|
+
// {prev.outputKey} → look up from dependency's outputs
|
|
238
|
+
const match = val.match(/^\{prev\.(.+)\}$/);
|
|
239
|
+
if (match?.[1] && dep.outputs[match[1]]) {
|
|
240
|
+
candidate.vars[key] = dep.outputs[match[1]];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.store.update(candidate.id, { vars: candidate.vars });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const opts = {};
|
|
247
|
+
if (sessionId !== undefined)
|
|
248
|
+
opts.sessionId = sessionId;
|
|
249
|
+
const result = this.transition(candidate.id, "running", opts);
|
|
250
|
+
if (!("error" in result))
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
summary() {
|
|
256
|
+
const all = this.store.list();
|
|
257
|
+
const byState = Object.fromEntries(JOB_STATES.map((s) => [s, 0]));
|
|
258
|
+
for (const j of all)
|
|
259
|
+
byState[j.state]++;
|
|
260
|
+
const queued = all
|
|
261
|
+
.filter((j) => j.state === "queued")
|
|
262
|
+
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
263
|
+
return {
|
|
264
|
+
total: all.length,
|
|
265
|
+
byState,
|
|
266
|
+
oldestQueued: queued[0]?.createdAt ?? null,
|
|
267
|
+
runningJobIds: all.filter((j) => j.state === "running").map((j) => j.id),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Remove a job entirely. */
|
|
271
|
+
remove(id) {
|
|
272
|
+
return this.store.remove(id);
|
|
273
|
+
}
|
|
274
|
+
/** Prune old terminal jobs. */
|
|
275
|
+
prune() {
|
|
276
|
+
return this.store.prune();
|
|
277
|
+
}
|
|
278
|
+
// ── Private ─────────────────────────────────────
|
|
279
|
+
recordOutcomeToMemory(job, success) {
|
|
280
|
+
if (!this.memory)
|
|
281
|
+
return;
|
|
282
|
+
try {
|
|
283
|
+
const completedSteps = job.steps.filter((s) => s.status === "done");
|
|
284
|
+
if (completedSteps.length === 0)
|
|
285
|
+
return;
|
|
286
|
+
this.memory.appendStrategy({
|
|
287
|
+
id: "strat_" + job.id,
|
|
288
|
+
task: job.task,
|
|
289
|
+
steps: completedSteps.map((s) => ({
|
|
290
|
+
tool: s.action,
|
|
291
|
+
params: s.target ? { target: s.target } : {},
|
|
292
|
+
})),
|
|
293
|
+
totalDurationMs: completedSteps.reduce((sum, s) => sum + (s.durationMs ?? 0), 0),
|
|
294
|
+
successCount: success ? 1 : 0,
|
|
295
|
+
failCount: success ? 0 : 1,
|
|
296
|
+
lastUsed: new Date().toISOString(),
|
|
297
|
+
tags: job.tags,
|
|
298
|
+
fingerprint: completedSteps.map((s) => s.action).join("→"),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Non-critical — don't let memory failures break job flow
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|