@galda/cli 0.10.13 → 0.10.15
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/app/index.html +19 -15
- package/engine/server.mjs +31 -56
- package/package.json +1 -1
- package/examples/task-001/runs/2026-07-03T06-23-57-441Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T06-23-57-441Z/report.json +0 -20
- package/examples/task-001/runs/2026-07-03T06-29-54-517Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T06-29-54-517Z/report.json +0 -20
- package/examples/task-001/runs/2026-07-03T06-40-23-231Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T06-40-23-231Z/report.json +0 -20
- package/examples/task-001/runs/2026-07-03T07-34-58-109Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T07-34-58-109Z/report.json +0 -21
- package/examples/task-001/runs/2026-07-03T07-53-44-639Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T07-53-44-639Z/report.json +0 -21
- package/examples/task-001/runs/2026-07-03T08-02-12-117Z/attempt-1-proof.png +0 -0
- package/examples/task-001/runs/2026-07-03T08-02-12-117Z/report.json +0 -21
package/app/index.html
CHANGED
|
@@ -5792,7 +5792,7 @@ function bootDisconnected(info){
|
|
|
5792
5792
|
+ '<p>Galda runs on your own machine. Run this where you use Claude Code or Codex — this board comes alive the moment it connects, no refresh needed:</p>'
|
|
5793
5793
|
+ '<div class="cg-cmd"><code>npx @galda/cli</code><button class="cg-copy">Copy</button></div>'
|
|
5794
5794
|
+ '<p class="cg-who">Signed in as <b></b></p>'
|
|
5795
|
-
+ '<p class="cg-switch">
|
|
5795
|
+
+ '<p class="cg-switch">Already running Galda under a different account? Run <code>npx @galda/cli --signin</code> and pick this one. Or <a href="/logout">switch this browser →</a></p>'
|
|
5796
5796
|
+ '<div class="cg-wait"><i></i>WAITING FOR THIS DEVICE</div></div>';
|
|
5797
5797
|
document.body.appendChild(ov);
|
|
5798
5798
|
ov.querySelector('.cg-who b').textContent = email || 'your account';
|
|
@@ -5814,20 +5814,24 @@ function bootDisconnected(info){
|
|
|
5814
5814
|
const cw = document.getElementById('composerWrap');
|
|
5815
5815
|
if (cw) cw.addEventListener('mousedown', (e) => { if (e.target.closest('#appsel, #modelsel, #effortsel')) return; gate(e); });
|
|
5816
5816
|
|
|
5817
|
-
// Come alive automatically
|
|
5818
|
-
//
|
|
5819
|
-
//
|
|
5820
|
-
//
|
|
5821
|
-
//
|
|
5822
|
-
//
|
|
5823
|
-
//
|
|
5824
|
-
//
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
.
|
|
5830
|
-
|
|
5817
|
+
// Come alive automatically — WITHOUT polling-and-reloading (Masa 2026-07-16).
|
|
5818
|
+
// The relay pushes a 'connected' event over SSE the instant THIS account's agent
|
|
5819
|
+
// is STABLY online (debounced on the relay side), and we reload exactly once on
|
|
5820
|
+
// that edge. Event-driven + debounced makes a reload loop structurally
|
|
5821
|
+
// impossible: a flapping agent never reaches the stable-connect edge, so it
|
|
5822
|
+
// shows "reconnecting" instead of triggering reloads. This replaces the old
|
|
5823
|
+
// `setInterval` + `if (status !== 503) reload` poll, which amplified any agent
|
|
5824
|
+
// flap into an infinite reload (the ~10-failure "無限ループ").
|
|
5825
|
+
const cgWait = ov.querySelector('.cg-wait');
|
|
5826
|
+
try {
|
|
5827
|
+
const es = new EventSource('/presence');
|
|
5828
|
+
es.addEventListener('presence', (e) => {
|
|
5829
|
+
let d; try { d = JSON.parse(e.data); } catch { return; }
|
|
5830
|
+
if (d.state === 'connected') { es.close(); location.reload(); } // one reload, only on a stable connect
|
|
5831
|
+
else if (cgWait) cgWait.innerHTML = '<i></i>WAITING FOR THIS DEVICE';
|
|
5832
|
+
});
|
|
5833
|
+
window.addEventListener('beforeunload', () => { try { es.close(); } catch {} });
|
|
5834
|
+
} catch { /* no EventSource (ancient browser) → stay on the gate; never loops */ }
|
|
5831
5835
|
}
|
|
5832
5836
|
|
|
5833
5837
|
async function boot(){
|
package/engine/server.mjs
CHANGED
|
@@ -20,8 +20,8 @@ import { resolve, dirname, join, basename } from 'node:path';
|
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { pathToFileURL } from 'node:url';
|
|
23
|
-
import { needsProjectFolder, folderLabel, buildFolderQuestion, resolveFolderAnswer
|
|
24
|
-
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd,
|
|
23
|
+
import { needsProjectFolder, folderLabel, buildFolderQuestion, resolveFolderAnswer } from './lib.mjs';
|
|
24
|
+
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable } from './lib.mjs';
|
|
25
25
|
import { openPR } from './pr.mjs';
|
|
26
26
|
import { runVerification, exerciseUi } from './verify.mjs';
|
|
27
27
|
|
|
@@ -2160,11 +2160,6 @@ async function finishGoalIfComplete(goal, project, activityTask = null) {
|
|
|
2160
2160
|
// point (all its tasks are done), so its conflictsWith is accurate even if
|
|
2161
2161
|
// PR creation itself fails (e.g. no `gh` auth) or is still no-op'd (no code).
|
|
2162
2162
|
updateGoalConflicts(goal);
|
|
2163
|
-
// Computed BEFORE createGoalPR (not inside verifyGate, which used to run
|
|
2164
|
-
// after) so the PR body's "確認ポイント" headline is available the first
|
|
2165
|
-
// time the PR is opened, not only on the review card (Masa 2026-07-16).
|
|
2166
|
-
goalAct('Writing the review summary from the worker report and diff.');
|
|
2167
|
-
goal.reviewSummary = await summarizeForReview(goal, siblings, wdir, reviewDefinition);
|
|
2168
2163
|
goalAct('Checking changed files and preparing the PR branch if code changed.');
|
|
2169
2164
|
await createGoalPR(goal, prProject, siblings, reviewDefinition, activityTask);
|
|
2170
2165
|
// goal 427 §4: classify the change's risk from the SAME changed-files/diff
|
|
@@ -2233,13 +2228,8 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
2233
2228
|
goal.testResult = { ran: false, skipped: true, skippedReason: changedFiles.length ? 'documentation-only change' : 'no changed files' };
|
|
2234
2229
|
goalAct(`Skipped project tests: ${goal.testResult.skippedReason}.`);
|
|
2235
2230
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
// standalone /reverify path or any older goal that never got one.
|
|
2239
|
-
if (!goal.reviewSummary) {
|
|
2240
|
-
goalAct('Writing the review summary from the worker report and diff.');
|
|
2241
|
-
goal.reviewSummary = await summarizeForReview(goal, siblings, wdir, reviewDefinition);
|
|
2242
|
-
}
|
|
2231
|
+
goalAct('Writing the review summary from the worker report and diff.');
|
|
2232
|
+
goal.reviewSummary = await summarizeForReview(goal, siblings, wdir, reviewDefinition);
|
|
2243
2233
|
const uiTasks = siblings.filter((t) => isUiChange(t.changedFiles));
|
|
2244
2234
|
// Only require/capture proof for projects we can actually boot & screenshot
|
|
2245
2235
|
// (Manager-shaped). A generic web repo that edits a stylesheet can't be
|
|
@@ -2381,12 +2371,26 @@ async function createGoalPR(goal, project, goalTasks, reviewDefinition = DEFAULT
|
|
|
2381
2371
|
// (Masa: 「ユーザーの言語で」).
|
|
2382
2372
|
const lang = reviewDefinition.language === 'auto' ? detectRequestLanguage(goal.text) : (reviewDefinition.language || 'ja');
|
|
2383
2373
|
const L = (en, ja) => (lang === 'en' ? en : ja);
|
|
2384
|
-
const bodyFor = (branch) =>
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2374
|
+
const bodyFor = (branch) => [
|
|
2375
|
+
...(proofItems.length && owner ? [
|
|
2376
|
+
L('## Proof — one tap, plays in place', '## 動作確認 — 1タップでその場で再生'),
|
|
2377
|
+
'',
|
|
2378
|
+
`https://github.com/${owner}/blob/${branch}/${proofRel}/PROOF.md`,
|
|
2379
|
+
'',
|
|
2380
|
+
] : []),
|
|
2381
|
+
L('## Goal', '## ゴール'),
|
|
2382
|
+
'',
|
|
2383
|
+
goal.text.slice(0, 1500),
|
|
2384
|
+
'',
|
|
2385
|
+
...buildReviewRuleSection(reviewDefinition.description),
|
|
2386
|
+
L(`Implemented by Manager for AI: the goal was decomposed into ${goalTasks.length} task(s), each executed by a Claude Code worker (flat-rate, no extra API), tests written and run per task.${proofItems.length ? ' Tasks with a pass condition were verified independently in a headless browser.' : ''}`,
|
|
2387
|
+
`Manager for AI が実装: ゴールを${goalTasks.length}個のタスクに分解し、Claude Code worker が順に実行(既存サブスク・追加API無し・タスク毎にテストを作成し実行)。${proofItems.length ? '成功条件つきタスクは headless ブラウザで独立検証済み。' : ''}`),
|
|
2388
|
+
'',
|
|
2389
|
+
L('## Tasks and worker reports', '## タスクとworker報告'),
|
|
2390
|
+
...goalTasks.map((t) => `\n### ${t.num} ${t.title} — ${t.status} (${t.secs}s)\n${(t.result ?? '').slice(0, 800)}`),
|
|
2391
|
+
'',
|
|
2392
|
+
L(`Changed files: ${files.map((f) => `\`${f}\``).join(' ')}`, `変更ファイル: ${files.map((f) => `\`${f}\``).join(' ')}`),
|
|
2393
|
+
].join('\n');
|
|
2390
2394
|
const title = `Manager for AI: ${isRework ? L('rework — ', '再作業 — ') : ''}${goal.plan?.[0] ?? goal.text.slice(0, 60)}${goal.plan?.length > 1 ? L(` +${goal.plan.length - 1} more`, ` 他${goal.plan.length - 1}件`) : ''}`;
|
|
2391
2395
|
const { url, branch } = openPR({
|
|
2392
2396
|
repoDir: project.dir, slug: `goal-${goal.id}`,
|
|
@@ -2397,7 +2401,7 @@ async function createGoalPR(goal, project, goalTasks, reviewDefinition = DEFAULT
|
|
|
2397
2401
|
extraCopies,
|
|
2398
2402
|
extraWrites: proofItems.length ? [{
|
|
2399
2403
|
destRel: `${proofRel}/PROOF.md`,
|
|
2400
|
-
content: buildGoalProofMd({ goalText: goal.text, items: proofItems
|
|
2404
|
+
content: buildGoalProofMd({ goalText: goal.text, items: proofItems }),
|
|
2401
2405
|
}] : [],
|
|
2402
2406
|
...(isRework ? { existingBranch: goal.prBranch, existingPrUrl: goal.pr } : {}),
|
|
2403
2407
|
});
|
|
@@ -2746,22 +2750,15 @@ const server = createServer(async (req, res) => {
|
|
|
2746
2750
|
// for pending/review items (they never spawn a worker).
|
|
2747
2751
|
const runnable = !pending && !review;
|
|
2748
2752
|
const needsFolder = runnable && needsProjectFolder({ dir: project.dir, homeDir: HOME, isRepo: isGitRepo(project.dir) });
|
|
2749
|
-
const
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
// project, before its first runnable goal — whether this project wants
|
|
2753
|
-
// PRs. Skipped this round if a folder question already claimed the
|
|
2754
|
-
// needsInput slot; that goal gets rebound to a real repo via /answer
|
|
2755
|
-
// and the NEXT goal for that project is what actually asks this.
|
|
2756
|
-
const needsReviewPref = runnable && !needsFolder && needsReviewPreference({ askedBefore: project.reviewPrefAsked });
|
|
2757
|
-
const reviewPrefQuestion = needsReviewPref ? buildReviewPreferenceQuestion({ lang }) : undefined;
|
|
2758
|
-
if (needsReviewPref) { project.reviewPrefAsked = true; saveProjects(); }
|
|
2753
|
+
const folderQuestion = needsFolder
|
|
2754
|
+
? buildFolderQuestion(detectRepos(HOME), { lang: /[-ヿ㐀-鿿]/.test(text) ? 'ja' : 'en' })
|
|
2755
|
+
: undefined;
|
|
2759
2756
|
const goalAgent = workerAgent(agent);
|
|
2760
2757
|
const priorFailureMemory = recentProjectFailureMemory(projectId, text.trim());
|
|
2761
2758
|
const goal = {
|
|
2762
2759
|
id: nextId++, projectId, text: text.trim().slice(0, 8000),
|
|
2763
|
-
status:
|
|
2764
|
-
question: folderQuestion
|
|
2760
|
+
status: needsFolder ? 'needsInput' : review ? 'review' : pending ? 'pending' : 'planning',
|
|
2761
|
+
question: folderQuestion,
|
|
2765
2762
|
reviewOnly: review ? true : undefined,
|
|
2766
2763
|
note: review && typeof note === 'string' && note.trim() ? note.trim().slice(0, 8000) : undefined,
|
|
2767
2764
|
wantsPR: review ? false : resolveWantsPR(pr, text, getReviewDefinition(projectId).defaultWantsPR),
|
|
@@ -2791,7 +2788,7 @@ const server = createServer(async (req, res) => {
|
|
|
2791
2788
|
// pending = deliberately shelved (Phase 2 material): never planned,
|
|
2792
2789
|
// never queued, until POST /api/goals/:id/activate. review-only items
|
|
2793
2790
|
// are terminal-until-human too, so they also skip planGoal.
|
|
2794
|
-
if (runnable && !needsFolder
|
|
2791
|
+
if (runnable && !needsFolder) {
|
|
2795
2792
|
planGoal(goal).catch((e) => {
|
|
2796
2793
|
goal.status = 'failed'; goal.prError = String(e).slice(0, 300); saveGoal(goal);
|
|
2797
2794
|
});
|
|
@@ -3118,28 +3115,6 @@ const server = createServer(async (req, res) => {
|
|
|
3118
3115
|
saveGoal(goal);
|
|
3119
3116
|
return json(res, 200, goal);
|
|
3120
3117
|
}
|
|
3121
|
-
// Review-preference answer (task 5 onboarding funnel): fold into the
|
|
3122
|
-
// project's review definition (defaultWantsPR) rather than goal.text —
|
|
3123
|
-
// like the folder question, this is project setup, not a task detail.
|
|
3124
|
-
if (goal.question?.kind === 'reviewPref') {
|
|
3125
|
-
const project = projects.find((p) => p.id === goal.projectId);
|
|
3126
|
-
const patch = resolveReviewPreferenceAnswer(answer, goal.question);
|
|
3127
|
-
if (project) {
|
|
3128
|
-
const merged = validateReviewDefinition({ ...getReviewDefinition(project.id), ...patch });
|
|
3129
|
-
if (merged.ok) {
|
|
3130
|
-
reviewDefinitions[project.id] = merged.definition;
|
|
3131
|
-
saveReviewDefinitions();
|
|
3132
|
-
send({ ev: 'review-definition', projectId: project.id, reviewDefinition: merged.definition });
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
goal.wantsPR = resolveWantsPR(undefined, goal.text, patch.defaultWantsPR);
|
|
3136
|
-
goal.question = null;
|
|
3137
|
-
goal.clarified = true;
|
|
3138
|
-
goal.status = 'planning';
|
|
3139
|
-
saveGoal(goal);
|
|
3140
|
-
planGoal(goal).catch((e) => { goal.status = 'failed'; goal.prError = String(e).slice(0, 300); saveGoal(goal); });
|
|
3141
|
-
return json(res, 200, goal);
|
|
3142
|
-
}
|
|
3143
3118
|
goal.text = `${goal.text}\n[確認: ${goal.question?.text ?? ''} → ${answer}]`;
|
|
3144
3119
|
goal.clarified = true; // answered once → planGoal must never re-ask (persisted via saveGoal)
|
|
3145
3120
|
goal.question = null;
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "21.7",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z/attempt-1.webm"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z",
|
|
19
|
-
"finishedAt": "2026-07-03T06:24:22.279Z"
|
|
20
|
-
}
|
|
Binary file
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "19.8",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z/attempt-1.webm"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z",
|
|
19
|
-
"finishedAt": "2026-07-03T06:30:17.155Z"
|
|
20
|
-
}
|
|
Binary file
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "18.2",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z/attempt-1.webm"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z",
|
|
19
|
-
"finishedAt": "2026-07-03T06:40:46.785Z"
|
|
20
|
-
}
|
|
Binary file
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "22.3",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z/attempt-1.mp4"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z",
|
|
19
|
-
"finishedAt": "2026-07-03T07:35:25.892Z",
|
|
20
|
-
"pr": "https://github.com/kodo-inc/common/pull/1"
|
|
21
|
-
}
|
|
Binary file
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "21.9",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z/attempt-1.mp4"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z",
|
|
19
|
-
"finishedAt": "2026-07-03T07:54:11.642Z",
|
|
20
|
-
"pr": "https://github.com/kodo-inc/galda2/pull/1"
|
|
21
|
-
}
|
|
Binary file
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": {
|
|
3
|
-
"id": "task-001",
|
|
4
|
-
"title": "Save button toast",
|
|
5
|
-
"passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
|
|
6
|
-
},
|
|
7
|
-
"status": "done",
|
|
8
|
-
"attempts": [
|
|
9
|
-
{
|
|
10
|
-
"attempt": 1,
|
|
11
|
-
"workerSecs": "16.3",
|
|
12
|
-
"pass": true,
|
|
13
|
-
"detail": "clicked #save → toast \"Saved!\" visible",
|
|
14
|
-
"proofScreenshot": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z/attempt-1-proof.png",
|
|
15
|
-
"proofVideo": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z/attempt-1.mp4"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"runDir": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z",
|
|
19
|
-
"finishedAt": "2026-07-03T08:02:33.935Z",
|
|
20
|
-
"pr": "https://github.com/kodo-inc/galda2/pull/2"
|
|
21
|
-
}
|