@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.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/AGENTS.md +155 -153
- package/CHANGELOG.md +338 -265
- package/README.md +345 -298
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/generate-task-dag.js +28 -28
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +52 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +130 -0
- package/dist/cli/command-definitions.js +27 -7
- package/dist/cli/program.js +8 -4
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/eval.js +235 -0
- package/dist/commands/init.js +544 -506
- package/dist/commands/knowledge.js +129 -31
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/pi-sdk-executor.js +38 -24
- package/dist/executors/shell-executor.js +34 -2
- package/dist/executors/shell-presets.js +20 -0
- package/dist/executors/shell-verification.js +7 -0
- package/dist/governance/manifest-types.js +4 -0
- package/dist/infrastructure/evaluation/candidate-store.js +435 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/task/config-types.js +28 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/cli.js +96 -1
- package/dist/worker/delivery/package.js +3 -3
- package/dist/worker/feature/decision-loader.js +37 -6
- package/dist/worker/feature/next-action.js +10 -2
- package/dist/worker/feature/ready-plan-projection.js +81 -0
- package/dist/worker/feature/reducer.js +2 -1
- package/dist/worker/feature/review.js +19 -2
- package/dist/worker/feature/run.js +27 -2
- package/dist/worker/follow-up/approve.js +5 -2
- package/dist/worker/follow-up/factory.js +1 -1
- package/dist/worker/observability/read-model.js +246 -41
- package/dist/worker/observe/routes.js +173 -15
- package/dist/worker/observe/spec-evidence.js +281 -0
- package/dist/worker/observe/static/api.js +46 -27
- package/dist/worker/observe/static/app.js +150 -150
- package/dist/worker/observe/static/constants.js +148 -148
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-helpers.js +172 -172
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/dag-model.js +72 -72
- package/dist/worker/observe/static/dom.js +61 -61
- package/dist/worker/observe/static/format-pool.js +67 -67
- package/dist/worker/observe/static/format.js +292 -292
- package/dist/worker/observe/static/index.html +308 -308
- package/dist/worker/observe/static/kpi.js +94 -94
- package/dist/worker/observe/static/relations.js +133 -128
- package/dist/worker/observe/static/router.js +93 -85
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/shell-chrome.js +68 -68
- package/dist/worker/observe/static/state.js +253 -253
- package/dist/worker/observe/static/styles.css +1902 -1890
- package/dist/worker/observe/static/views/batch.js +227 -226
- package/dist/worker/observe/static/views/dag-graph.js +172 -172
- package/dist/worker/observe/static/views/dag-inspector.js +607 -477
- package/dist/worker/observe/static/views/dag.js +362 -362
- package/dist/worker/observe/static/views/dashboard.js +445 -442
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -453
- package/dist/worker/observe/static/views/pool.js +350 -347
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/session-timeline.js +205 -205
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/task.js +314 -260
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/run-store.js +205 -17
- package/dist/worker/pool/types.js +17 -1
- package/dist/worker/pool/validation.js +100 -15
- package/dist/worker/report/morning-report.js +12 -2
- package/dist/worker/runner/run-ready.js +41 -26
- package/dist/worker/task-graph/ready-planner.js +136 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/convergence/controller.js +16 -8
- package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
- package/dist/workflows/dag/failure-routing.js +12 -1
- package/dist/workflows/dag/init-hybrid.js +2404 -360
- package/dist/workflows/dag/node-execution.js +9 -0
- package/dist/workflows/dag/prompt.js +9 -0
- package/dist/workflows/dag/report.js +35 -1
- package/dist/workflows/dag/runner.js +28 -2
- package/dist/workflows/dag/task-demand-routing.js +383 -0
- package/dist/workflows/dag/types.js +51 -13
- package/dist/workflows/dag/upstream-artifacts.js +1 -0
- package/dist/workflows/dag/validate.js +59 -1
- package/docs/README.md +106 -104
- package/docs/agent-dag-recovery-playbook.md +195 -184
- package/docs/agent-dag-runner.md +67 -67
- package/docs/architecture/README.md +26 -26
- package/docs/architecture/dag-execution.md +140 -140
- package/docs/architecture/evolution.md +54 -53
- package/docs/architecture/facts-and-state.md +71 -58
- package/docs/architecture/runtime-boundaries.md +191 -191
- package/docs/architecture/system-overview.md +93 -93
- package/docs/architecture/worker-and-feature.md +85 -81
- package/docs/cursor-prompt-sidecar.md +36 -36
- package/docs/decisions/README.md +18 -15
- package/docs/design/README.md +167 -77
- package/docs/development-principles.md +73 -73
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +15 -9
- package/docs/exec-plans/completed/README.md +85 -73
- package/docs/feature-workflow.md +389 -261
- package/docs/harness-methodology-debugging.md +153 -153
- package/docs/harness-methodology-tdd.md +130 -130
- package/docs/harness-methodology-verification.md +27 -27
- package/docs/init-surface.manifest.json +289 -280
- package/docs/loop-agent-harness.md +142 -130
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +64 -54
- package/docs/reports/README.md +117 -94
- package/docs/skills/README.md +7 -7
- package/docs/skills/vetted-skill-registry.md +29 -27
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +190 -190
- package/docs/templates/agent-dag.final-verification.json +185 -185
- package/docs/templates/agent-dag.schema.json +411 -383
- package/docs/templates/agent-dag.supervised-implementation.json +501 -501
- package/docs/templates/backend-test-analysis.schema.json +44 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
- package/docs/templates/backend-test-dag.json +311 -276
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -33
- package/docs/templates/frontend-task-constraints.md +35 -25
- package/docs/templates/frontend-task-requirement.md +70 -61
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.json +23 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/harness.schema.json +221 -221
- package/docs/templates/hybrid-dag.json +188 -188
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +8 -8
- package/docs/templates/product-line/README.md +9 -9
- package/docs/templates/product-line/acceptance.yaml +14 -14
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/task-graph.yaml +15 -15
- package/docs/templates/product-line/task.yaml +64 -64
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/progress-log.md +17 -17
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/docs/verification-matrix.md +70 -66
- package/examples/decision-gate-agent-dag.json +177 -177
- package/examples/example-dag.json +46 -46
- package/examples/hybrid-loop-agent-dag.json +189 -189
- package/harness.json +66 -66
- package/package.json +88 -46
- package/scripts/check-product-line-docs.sh +29 -29
- package/scripts/check-task-pool-root.sh +32 -32
- package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-incremental-prepare.sh +5 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-materialize.sh +4 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-graph-promote.sh +4 -0
- package/scripts/kb-query.mjs +554 -0
- package/scripts/kb-query.sh +5 -0
- package/skills/agent-worker/SKILL.md +39 -37
- package/skills/agent-worker/references/agent-worker-operator.md +60 -43
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/SKILL.md +67 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
- package/skills/analyze-product-requirements/SKILL.md +90 -0
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
- package/skills/analyze-product-requirements/references/example.md +86 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/frontend-design-review/SKILL.md +66 -59
- package/skills/frontend-design-review/references/review-checklist.md +58 -37
- package/skills/frontend-implementation/SKILL.md +47 -51
- package/skills/frontend-implementation/references/code-standards.md +32 -34
- package/skills/frontend-implementation/references/design-spec.md +46 -46
- package/skills/frontend-implementation/references/node-contracts.md +76 -32
- package/skills/frontend-review/SKILL.md +59 -53
- package/skills/frontend-review/references/review-findings.md +47 -42
- package/skills/frontend-verification/SKILL.md +53 -40
- package/skills/frontend-verification/references/verification-checklist.md +68 -56
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/grill-with-docs/SKILL.md +88 -88
- package/skills/grill-with-docs/adr-format.md +47 -47
- package/skills/grill-with-docs/context-format.md +60 -60
- package/skills/init-capability-evolution/SKILL.md +70 -70
- package/skills/loop-agent/SKILL.md +151 -151
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +505 -452
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/harness-policy.md +263 -263
- package/skills/loop-agent/references/hybrid-dag.md +238 -233
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +57 -57
- package/skills/loop-agent/references/model-routing.md +36 -36
- package/skills/loop-agent/references/multi-worktree.md +54 -54
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
- package/skills/loop-agent/references/task-workflow.md +89 -89
- package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-cli-case-generator/SKILL.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/SKILL.md +296 -296
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/test-driven-development/SKILL.md +20 -20
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
// Complete implementation of condition-based waiting utilities
|
|
2
|
-
// From: Lace test infrastructure improvements (2025-10-03)
|
|
3
|
-
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
|
|
4
|
-
|
|
5
|
-
import type { ThreadManager } from '~/threads/thread-manager';
|
|
6
|
-
import type { LaceEvent, LaceEventType } from '~/threads/types';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Wait for a specific event type to appear in thread
|
|
10
|
-
*
|
|
11
|
-
* @param threadManager - The thread manager to query
|
|
12
|
-
* @param threadId - Thread to check for events
|
|
13
|
-
* @param eventType - Type of event to wait for
|
|
14
|
-
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
15
|
-
* @returns Promise resolving to the first matching event
|
|
16
|
-
*
|
|
17
|
-
* Example:
|
|
18
|
-
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
|
-
*/
|
|
20
|
-
export function waitForEvent(
|
|
21
|
-
threadManager: ThreadManager,
|
|
22
|
-
threadId: string,
|
|
23
|
-
eventType: LaceEventType,
|
|
24
|
-
timeoutMs = 5000
|
|
25
|
-
): Promise<LaceEvent> {
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const startTime = Date.now();
|
|
28
|
-
|
|
29
|
-
const check = () => {
|
|
30
|
-
const events = threadManager.getEvents(threadId);
|
|
31
|
-
const event = events.find((e) => e.type === eventType);
|
|
32
|
-
|
|
33
|
-
if (event) {
|
|
34
|
-
resolve(event);
|
|
35
|
-
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
-
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
|
|
37
|
-
} else {
|
|
38
|
-
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
check();
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Wait for a specific number of events of a given type
|
|
48
|
-
*
|
|
49
|
-
* @param threadManager - The thread manager to query
|
|
50
|
-
* @param threadId - Thread to check for events
|
|
51
|
-
* @param eventType - Type of event to wait for
|
|
52
|
-
* @param count - Number of events to wait for
|
|
53
|
-
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
54
|
-
* @returns Promise resolving to all matching events once count is reached
|
|
55
|
-
*
|
|
56
|
-
* Example:
|
|
57
|
-
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
|
|
58
|
-
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
59
|
-
*/
|
|
60
|
-
export function waitForEventCount(
|
|
61
|
-
threadManager: ThreadManager,
|
|
62
|
-
threadId: string,
|
|
63
|
-
eventType: LaceEventType,
|
|
64
|
-
count: number,
|
|
65
|
-
timeoutMs = 5000
|
|
66
|
-
): Promise<LaceEvent[]> {
|
|
67
|
-
return new Promise((resolve, reject) => {
|
|
68
|
-
const startTime = Date.now();
|
|
69
|
-
|
|
70
|
-
const check = () => {
|
|
71
|
-
const events = threadManager.getEvents(threadId);
|
|
72
|
-
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
73
|
-
|
|
74
|
-
if (matchingEvents.length >= count) {
|
|
75
|
-
resolve(matchingEvents);
|
|
76
|
-
} else if (Date.now() - startTime > timeoutMs) {
|
|
77
|
-
reject(
|
|
78
|
-
new Error(
|
|
79
|
-
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
|
|
80
|
-
)
|
|
81
|
-
);
|
|
82
|
-
} else {
|
|
83
|
-
setTimeout(check, 10);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
check();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Wait for an event matching a custom predicate
|
|
93
|
-
* Useful when you need to check event data, not just type
|
|
94
|
-
*
|
|
95
|
-
* @param threadManager - The thread manager to query
|
|
96
|
-
* @param threadId - Thread to check for events
|
|
97
|
-
* @param predicate - Function that returns true when event matches
|
|
98
|
-
* @param description - Human-readable description for error messages
|
|
99
|
-
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
100
|
-
* @returns Promise resolving to the first matching event
|
|
101
|
-
*
|
|
102
|
-
* Example:
|
|
103
|
-
* // Wait for TOOL_RESULT with specific ID
|
|
104
|
-
* await waitForEventMatch(
|
|
105
|
-
* threadManager,
|
|
106
|
-
* agentThreadId,
|
|
107
|
-
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
|
|
108
|
-
* 'TOOL_RESULT with id=call_123'
|
|
109
|
-
* );
|
|
110
|
-
*/
|
|
111
|
-
export function waitForEventMatch(
|
|
112
|
-
threadManager: ThreadManager,
|
|
113
|
-
threadId: string,
|
|
114
|
-
predicate: (event: LaceEvent) => boolean,
|
|
115
|
-
description: string,
|
|
116
|
-
timeoutMs = 5000
|
|
117
|
-
): Promise<LaceEvent> {
|
|
118
|
-
return new Promise((resolve, reject) => {
|
|
119
|
-
const startTime = Date.now();
|
|
120
|
-
|
|
121
|
-
const check = () => {
|
|
122
|
-
const events = threadManager.getEvents(threadId);
|
|
123
|
-
const event = events.find(predicate);
|
|
124
|
-
|
|
125
|
-
if (event) {
|
|
126
|
-
resolve(event);
|
|
127
|
-
} else if (Date.now() - startTime > timeoutMs) {
|
|
128
|
-
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
|
|
129
|
-
} else {
|
|
130
|
-
setTimeout(check, 10);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
check();
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Usage example from actual debugging session:
|
|
139
|
-
//
|
|
140
|
-
// BEFORE (flaky):
|
|
141
|
-
// ---------------
|
|
142
|
-
// const messagePromise = agent.sendMessage('Execute tools');
|
|
143
|
-
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
|
|
144
|
-
// agent.abort();
|
|
145
|
-
// await messagePromise;
|
|
146
|
-
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
|
|
147
|
-
// expect(toolResults.length).toBe(2); // Fails randomly
|
|
148
|
-
//
|
|
149
|
-
// AFTER (reliable):
|
|
150
|
-
// ----------------
|
|
151
|
-
// const messagePromise = agent.sendMessage('Execute tools');
|
|
152
|
-
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
|
|
153
|
-
// agent.abort();
|
|
154
|
-
// await messagePromise;
|
|
155
|
-
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
|
|
156
|
-
// expect(toolResults.length).toBe(2); // Always succeeds
|
|
157
|
-
//
|
|
158
|
-
// Result: 60% pass rate → 100%, 40% faster execution
|
|
1
|
+
// Complete implementation of condition-based waiting utilities
|
|
2
|
+
// From: Lace test infrastructure improvements (2025-10-03)
|
|
3
|
+
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
|
|
4
|
+
|
|
5
|
+
import type { ThreadManager } from '~/threads/thread-manager';
|
|
6
|
+
import type { LaceEvent, LaceEventType } from '~/threads/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wait for a specific event type to appear in thread
|
|
10
|
+
*
|
|
11
|
+
* @param threadManager - The thread manager to query
|
|
12
|
+
* @param threadId - Thread to check for events
|
|
13
|
+
* @param eventType - Type of event to wait for
|
|
14
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
15
|
+
* @returns Promise resolving to the first matching event
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
|
+
*/
|
|
20
|
+
export function waitForEvent(
|
|
21
|
+
threadManager: ThreadManager,
|
|
22
|
+
threadId: string,
|
|
23
|
+
eventType: LaceEventType,
|
|
24
|
+
timeoutMs = 5000
|
|
25
|
+
): Promise<LaceEvent> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
|
|
29
|
+
const check = () => {
|
|
30
|
+
const events = threadManager.getEvents(threadId);
|
|
31
|
+
const event = events.find((e) => e.type === eventType);
|
|
32
|
+
|
|
33
|
+
if (event) {
|
|
34
|
+
resolve(event);
|
|
35
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
+
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
|
|
37
|
+
} else {
|
|
38
|
+
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
check();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wait for a specific number of events of a given type
|
|
48
|
+
*
|
|
49
|
+
* @param threadManager - The thread manager to query
|
|
50
|
+
* @param threadId - Thread to check for events
|
|
51
|
+
* @param eventType - Type of event to wait for
|
|
52
|
+
* @param count - Number of events to wait for
|
|
53
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
54
|
+
* @returns Promise resolving to all matching events once count is reached
|
|
55
|
+
*
|
|
56
|
+
* Example:
|
|
57
|
+
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
|
|
58
|
+
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
59
|
+
*/
|
|
60
|
+
export function waitForEventCount(
|
|
61
|
+
threadManager: ThreadManager,
|
|
62
|
+
threadId: string,
|
|
63
|
+
eventType: LaceEventType,
|
|
64
|
+
count: number,
|
|
65
|
+
timeoutMs = 5000
|
|
66
|
+
): Promise<LaceEvent[]> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
|
|
70
|
+
const check = () => {
|
|
71
|
+
const events = threadManager.getEvents(threadId);
|
|
72
|
+
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
73
|
+
|
|
74
|
+
if (matchingEvents.length >= count) {
|
|
75
|
+
resolve(matchingEvents);
|
|
76
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
77
|
+
reject(
|
|
78
|
+
new Error(
|
|
79
|
+
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
setTimeout(check, 10);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
check();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Wait for an event matching a custom predicate
|
|
93
|
+
* Useful when you need to check event data, not just type
|
|
94
|
+
*
|
|
95
|
+
* @param threadManager - The thread manager to query
|
|
96
|
+
* @param threadId - Thread to check for events
|
|
97
|
+
* @param predicate - Function that returns true when event matches
|
|
98
|
+
* @param description - Human-readable description for error messages
|
|
99
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
100
|
+
* @returns Promise resolving to the first matching event
|
|
101
|
+
*
|
|
102
|
+
* Example:
|
|
103
|
+
* // Wait for TOOL_RESULT with specific ID
|
|
104
|
+
* await waitForEventMatch(
|
|
105
|
+
* threadManager,
|
|
106
|
+
* agentThreadId,
|
|
107
|
+
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
|
|
108
|
+
* 'TOOL_RESULT with id=call_123'
|
|
109
|
+
* );
|
|
110
|
+
*/
|
|
111
|
+
export function waitForEventMatch(
|
|
112
|
+
threadManager: ThreadManager,
|
|
113
|
+
threadId: string,
|
|
114
|
+
predicate: (event: LaceEvent) => boolean,
|
|
115
|
+
description: string,
|
|
116
|
+
timeoutMs = 5000
|
|
117
|
+
): Promise<LaceEvent> {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
const check = () => {
|
|
122
|
+
const events = threadManager.getEvents(threadId);
|
|
123
|
+
const event = events.find(predicate);
|
|
124
|
+
|
|
125
|
+
if (event) {
|
|
126
|
+
resolve(event);
|
|
127
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
128
|
+
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
|
|
129
|
+
} else {
|
|
130
|
+
setTimeout(check, 10);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
check();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Usage example from actual debugging session:
|
|
139
|
+
//
|
|
140
|
+
// BEFORE (flaky):
|
|
141
|
+
// ---------------
|
|
142
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
143
|
+
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
|
|
144
|
+
// agent.abort();
|
|
145
|
+
// await messagePromise;
|
|
146
|
+
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
|
|
147
|
+
// expect(toolResults.length).toBe(2); // Fails randomly
|
|
148
|
+
//
|
|
149
|
+
// AFTER (reliable):
|
|
150
|
+
// ----------------
|
|
151
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
152
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
|
|
153
|
+
// agent.abort();
|
|
154
|
+
// await messagePromise;
|
|
155
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
|
|
156
|
+
// expect(toolResults.length).toBe(2); // Always succeeds
|
|
157
|
+
//
|
|
158
|
+
// Result: 60% pass rate → 100%, 40% faster execution
|
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
# Condition-Based Waiting
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Flaky tests 常用 arbitrary delays 猜 timing。这制造 race conditions:fast machines 上 pass,load 或 CI 下 fail。
|
|
6
|
-
|
|
7
|
-
**Core principle:** Wait for 你真正关心的 actual condition,不是猜需要多久。
|
|
8
|
-
|
|
9
|
-
## When to Use
|
|
10
|
-
|
|
11
|
-
```dot
|
|
12
|
-
digraph when_to_use {
|
|
13
|
-
"Test uses setTimeout/sleep?" [shape=diamond];
|
|
14
|
-
"Testing timing behavior?" [shape=diamond];
|
|
15
|
-
"Document WHY timeout needed" [shape=box];
|
|
16
|
-
"Use condition-based waiting" [shape=box];
|
|
17
|
-
|
|
18
|
-
"Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
|
|
19
|
-
"Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
|
|
20
|
-
"Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Use when:**
|
|
25
|
-
- Tests 有 arbitrary delays(`setTimeout`、`sleep`、`time.sleep()`)
|
|
26
|
-
- Tests flaky(有时 pass,load 下 fail)
|
|
27
|
-
- Parallel 运行时 timeout
|
|
28
|
-
- 等待 async operations 完成
|
|
29
|
-
|
|
30
|
-
**Don't use when:**
|
|
31
|
-
- 测试 actual timing behavior(debounce、throttle intervals)
|
|
32
|
-
- 若用 arbitrary timeout,ALWAYS document WHY
|
|
33
|
-
|
|
34
|
-
## Core Pattern
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
// ❌ BEFORE: Guessing at timing
|
|
38
|
-
await new Promise(r => setTimeout(r, 50));
|
|
39
|
-
const result = getResult();
|
|
40
|
-
expect(result).toBeDefined();
|
|
41
|
-
|
|
42
|
-
// ✅ AFTER: Waiting for condition
|
|
43
|
-
await waitFor(() => getResult() !== undefined);
|
|
44
|
-
const result = getResult();
|
|
45
|
-
expect(result).toBeDefined();
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Quick Patterns
|
|
49
|
-
|
|
50
|
-
| Scenario | Pattern |
|
|
51
|
-
|----------|---------|
|
|
52
|
-
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
|
|
53
|
-
| Wait for state | `waitFor(() => machine.state === 'ready')` |
|
|
54
|
-
| Wait for count | `waitFor(() => items.length >= 5)` |
|
|
55
|
-
| Wait for file | `waitFor(() => fs.existsSync(path))` |
|
|
56
|
-
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
|
|
57
|
-
|
|
58
|
-
## Implementation
|
|
59
|
-
|
|
60
|
-
Generic polling function:
|
|
61
|
-
```typescript
|
|
62
|
-
async function waitFor<T>(
|
|
63
|
-
condition: () => T | undefined | null | false,
|
|
64
|
-
description: string,
|
|
65
|
-
timeoutMs = 5000
|
|
66
|
-
): Promise<T> {
|
|
67
|
-
const startTime = Date.now();
|
|
68
|
-
|
|
69
|
-
while (true) {
|
|
70
|
-
const result = condition();
|
|
71
|
-
if (result) return result;
|
|
72
|
-
|
|
73
|
-
if (Date.now() - startTime > timeoutMs) {
|
|
74
|
-
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
完整实现及 domain-specific helpers(`waitForEvent`、`waitForEventCount`、`waitForEventMatch`)见本目录 `condition-based-waiting-example.ts`,来自 actual debugging session。
|
|
83
|
-
|
|
84
|
-
## Common Mistakes
|
|
85
|
-
|
|
86
|
-
**❌ Polling too fast:** `setTimeout(check, 1)` — wastes CPU
|
|
87
|
-
**✅ Fix:** Poll every 10ms
|
|
88
|
-
|
|
89
|
-
**❌ No timeout:** 条件永不满足则 loop forever
|
|
90
|
-
**✅ Fix:** Always include timeout with clear error
|
|
91
|
-
|
|
92
|
-
**❌ Stale data:** Loop 前 cache state
|
|
93
|
-
**✅ Fix:** Loop 内 call getter 取 fresh data
|
|
94
|
-
|
|
95
|
-
## When Arbitrary Timeout IS Correct
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
// Tool ticks every 100ms - need 2 ticks to verify partial output
|
|
99
|
-
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
|
|
100
|
-
await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
|
|
101
|
-
// 200ms = 2 ticks at 100ms intervals - documented and justified
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
**Requirements:**
|
|
105
|
-
1. First wait for triggering condition
|
|
106
|
-
2. Based on known timing(not guessing)
|
|
107
|
-
3. Comment explaining WHY
|
|
108
|
-
|
|
109
|
-
## Real-World Impact
|
|
110
|
-
|
|
111
|
-
来自 debugging session (2025-10-03):
|
|
112
|
-
- 修复 3 个文件中 15 个 flaky tests
|
|
113
|
-
- Pass rate:60% → 100%
|
|
114
|
-
- Execution time:40% faster
|
|
115
|
-
- No more race conditions
|
|
1
|
+
# Condition-Based Waiting
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Flaky tests 常用 arbitrary delays 猜 timing。这制造 race conditions:fast machines 上 pass,load 或 CI 下 fail。
|
|
6
|
+
|
|
7
|
+
**Core principle:** Wait for 你真正关心的 actual condition,不是猜需要多久。
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
```dot
|
|
12
|
+
digraph when_to_use {
|
|
13
|
+
"Test uses setTimeout/sleep?" [shape=diamond];
|
|
14
|
+
"Testing timing behavior?" [shape=diamond];
|
|
15
|
+
"Document WHY timeout needed" [shape=box];
|
|
16
|
+
"Use condition-based waiting" [shape=box];
|
|
17
|
+
|
|
18
|
+
"Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
|
|
19
|
+
"Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
|
|
20
|
+
"Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Use when:**
|
|
25
|
+
- Tests 有 arbitrary delays(`setTimeout`、`sleep`、`time.sleep()`)
|
|
26
|
+
- Tests flaky(有时 pass,load 下 fail)
|
|
27
|
+
- Parallel 运行时 timeout
|
|
28
|
+
- 等待 async operations 完成
|
|
29
|
+
|
|
30
|
+
**Don't use when:**
|
|
31
|
+
- 测试 actual timing behavior(debounce、throttle intervals)
|
|
32
|
+
- 若用 arbitrary timeout,ALWAYS document WHY
|
|
33
|
+
|
|
34
|
+
## Core Pattern
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ❌ BEFORE: Guessing at timing
|
|
38
|
+
await new Promise(r => setTimeout(r, 50));
|
|
39
|
+
const result = getResult();
|
|
40
|
+
expect(result).toBeDefined();
|
|
41
|
+
|
|
42
|
+
// ✅ AFTER: Waiting for condition
|
|
43
|
+
await waitFor(() => getResult() !== undefined);
|
|
44
|
+
const result = getResult();
|
|
45
|
+
expect(result).toBeDefined();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Patterns
|
|
49
|
+
|
|
50
|
+
| Scenario | Pattern |
|
|
51
|
+
|----------|---------|
|
|
52
|
+
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
|
|
53
|
+
| Wait for state | `waitFor(() => machine.state === 'ready')` |
|
|
54
|
+
| Wait for count | `waitFor(() => items.length >= 5)` |
|
|
55
|
+
| Wait for file | `waitFor(() => fs.existsSync(path))` |
|
|
56
|
+
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
|
|
57
|
+
|
|
58
|
+
## Implementation
|
|
59
|
+
|
|
60
|
+
Generic polling function:
|
|
61
|
+
```typescript
|
|
62
|
+
async function waitFor<T>(
|
|
63
|
+
condition: () => T | undefined | null | false,
|
|
64
|
+
description: string,
|
|
65
|
+
timeoutMs = 5000
|
|
66
|
+
): Promise<T> {
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
|
|
69
|
+
while (true) {
|
|
70
|
+
const result = condition();
|
|
71
|
+
if (result) return result;
|
|
72
|
+
|
|
73
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
74
|
+
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
完整实现及 domain-specific helpers(`waitForEvent`、`waitForEventCount`、`waitForEventMatch`)见本目录 `condition-based-waiting-example.ts`,来自 actual debugging session。
|
|
83
|
+
|
|
84
|
+
## Common Mistakes
|
|
85
|
+
|
|
86
|
+
**❌ Polling too fast:** `setTimeout(check, 1)` — wastes CPU
|
|
87
|
+
**✅ Fix:** Poll every 10ms
|
|
88
|
+
|
|
89
|
+
**❌ No timeout:** 条件永不满足则 loop forever
|
|
90
|
+
**✅ Fix:** Always include timeout with clear error
|
|
91
|
+
|
|
92
|
+
**❌ Stale data:** Loop 前 cache state
|
|
93
|
+
**✅ Fix:** Loop 内 call getter 取 fresh data
|
|
94
|
+
|
|
95
|
+
## When Arbitrary Timeout IS Correct
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Tool ticks every 100ms - need 2 ticks to verify partial output
|
|
99
|
+
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
|
|
100
|
+
await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
|
|
101
|
+
// 200ms = 2 ticks at 100ms intervals - documented and justified
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Requirements:**
|
|
105
|
+
1. First wait for triggering condition
|
|
106
|
+
2. Based on known timing(not guessing)
|
|
107
|
+
3. Comment explaining WHY
|
|
108
|
+
|
|
109
|
+
## Real-World Impact
|
|
110
|
+
|
|
111
|
+
来自 debugging session (2025-10-03):
|
|
112
|
+
- 修复 3 个文件中 15 个 flaky tests
|
|
113
|
+
- Pass rate:60% → 100%
|
|
114
|
+
- Execution time:40% faster
|
|
115
|
+
- No more race conditions
|