agent-tempo 1.7.0-beta.8 → 1.7.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/CLAUDE.md +29 -6
- package/README.md +2 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/maestro.d.ts +46 -1
- package/dist/activities/maestro.js +69 -20
- package/dist/activities/outbox.d.ts +14 -1
- package/dist/activities/outbox.js +71 -10
- package/dist/activities/resolve.d.ts +118 -17
- package/dist/activities/resolve.js +256 -28
- package/dist/activities/schedule-fire.js +5 -2
- package/dist/adapters/base.d.ts +2 -0
- package/dist/adapters/base.js +9 -0
- package/dist/adapters/claude-api/adapter.js +26 -4
- package/dist/adapters/claude-code/adapter.js +15 -9
- package/dist/adapters/claude-code-headless/adapter.js +22 -4
- package/dist/adapters/copilot/adapter.js +58 -8
- package/dist/adapters/opencode/adapter.js +26 -6
- package/dist/adapters/sdk/base.d.ts +60 -0
- package/dist/adapters/sdk/base.js +83 -0
- package/dist/adapters/sdk/doorbell-client.d.ts +106 -0
- package/dist/adapters/sdk/doorbell-client.js +261 -0
- package/dist/adapters/sdk/idle-backoff.d.ts +71 -0
- package/dist/adapters/sdk/idle-backoff.js +136 -0
- package/dist/cli/command-center-command.js +25 -2
- package/dist/cli/commands.d.ts +28 -0
- package/dist/cli/commands.js +111 -20
- package/dist/cli/config-command.js +14 -0
- package/dist/cli/daemon-command.js +41 -5
- package/dist/cli/daemon.d.ts +245 -5
- package/dist/cli/daemon.js +505 -44
- package/dist/cli/help-text.js +3 -1
- package/dist/cli/mcp.d.ts +26 -2
- package/dist/cli/mcp.js +33 -4
- package/dist/cli/sa-preflight.d.ts +27 -1
- package/dist/cli/sa-preflight.js +29 -5
- package/dist/cli/startup.js +8 -2
- package/dist/cli/upgrade-to-2-command.d.ts +10 -0
- package/dist/cli/upgrade-to-2-command.js +159 -0
- package/dist/cli.js +37 -5
- package/dist/client/core.d.ts +6 -0
- package/dist/client/core.js +81 -34
- package/dist/client/subscribe.d.ts +10 -0
- package/dist/client/subscribe.js +2 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.js +70 -1
- package/dist/daemon.d.ts +78 -7
- package/dist/daemon.js +313 -29
- package/dist/ensemble/saver.d.ts +10 -0
- package/dist/ensemble/saver.js +18 -6
- package/dist/http/aggregate.d.ts +88 -1
- package/dist/http/aggregate.js +157 -13
- package/dist/http/deliverability.d.ts +68 -0
- package/dist/http/deliverability.js +78 -0
- package/dist/http/doorbell-routes.d.ts +32 -0
- package/dist/http/doorbell-routes.js +72 -0
- package/dist/http/doorbell.d.ts +45 -0
- package/dist/http/doorbell.js +162 -0
- package/dist/http/inner-loop-routes.d.ts +9 -0
- package/dist/http/inner-loop-routes.js +4 -0
- package/dist/http/qa.js +8 -1
- package/dist/http/server.d.ts +9 -0
- package/dist/http/server.js +45 -1
- package/dist/http/snapshot.d.ts +28 -5
- package/dist/http/snapshot.js +16 -6
- package/dist/http/writes.js +24 -2
- package/dist/pi/cue-pump.d.ts +107 -7
- package/dist/pi/cue-pump.js +131 -24
- package/dist/pi/extension.js +11 -0
- package/dist/pi/headless.js +49 -13
- package/dist/pi/install.d.ts +15 -0
- package/dist/pi/install.js +35 -0
- package/dist/pi/mission-control/actions.d.ts +52 -3
- package/dist/pi/mission-control/actions.js +112 -3
- package/dist/pi/mission-control/board.d.ts +104 -0
- package/dist/pi/mission-control/board.js +103 -2
- package/dist/pi/mission-control/extension.d.ts +166 -1
- package/dist/pi/mission-control/extension.js +477 -15
- package/dist/pi/mission-control/render.d.ts +25 -0
- package/dist/pi/mission-control/render.js +142 -8
- package/dist/pi/workflow-client.d.ts +29 -0
- package/dist/pi/workflow-client.js +87 -3
- package/dist/reconcile/orphans.js +8 -9
- package/dist/server-tools.js +1 -1
- package/dist/server.js +11 -1
- package/dist/spawn.d.ts +78 -26
- package/dist/spawn.js +69 -37
- package/dist/tools/broadcast.js +15 -1
- package/dist/tools/cue.js +52 -11
- package/dist/tools/ensemble.js +43 -3
- package/dist/tools/recruit.js +11 -29
- package/dist/tools/who-am-i.d.ts +3 -2
- package/dist/tools/who-am-i.js +9 -2
- package/dist/types.d.ts +42 -0
- package/dist/upgrade/phase-engine.d.ts +160 -0
- package/dist/upgrade/phase-engine.js +555 -0
- package/dist/upgrade/snapshot-v1.d.ts +214 -0
- package/dist/upgrade/snapshot-v1.js +165 -0
- package/dist/utils/action-counters.d.ts +75 -0
- package/dist/utils/action-counters.js +279 -0
- package/dist/utils/format-hosts.js +7 -0
- package/dist/utils/hosts.js +5 -0
- package/dist/utils/sdk-probe.d.ts +12 -0
- package/dist/utils/sdk-probe.js +28 -0
- package/dist/utils/search-attributes.d.ts +76 -4
- package/dist/utils/search-attributes.js +89 -4
- package/dist/utils/suspension.d.ts +99 -0
- package/dist/utils/suspension.js +128 -0
- package/dist/utils/visibility-deadline.js +5 -0
- package/dist/worker.d.ts +16 -1
- package/dist/worker.js +25 -4
- package/dist/workflows/maestro.d.ts +10 -0
- package/dist/workflows/maestro.js +126 -21
- package/dist/workflows/scheduler.js +17 -2
- package/dist/workflows/session.js +251 -14
- package/dist/workflows/signals.d.ts +8 -0
- package/dist/workflows/signals.js +3 -2
- package/package.json +8 -4
- package/workflow-bundle.js +580 -40
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { Client, WorkflowHandle } from '@temporalio/client';
|
|
2
2
|
import { AttachmentPhase } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Mode-B describe-by-id timeout (#845). The strongly-consistent
|
|
5
|
+
* `describe()` fallback on `resolveSession`'s not-found branch is a single
|
|
6
|
+
* O(1) RPC; 2s mirrors {@link DEFAULT_QUERY_TIMEOUT_MS} — two orders of
|
|
7
|
+
* magnitude over a healthy describe — so a wedged frontend can't re-hang
|
|
8
|
+
* the outbox loop the visibility deadline (#336/#529) was added to bound.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RESOLVE_DESCRIBE_TIMEOUT_MS = 2000;
|
|
11
|
+
/** Shared query for listing running session workflows. Exported for the
|
|
12
|
+
* ensemble-scoped variants in `client/core.ts` (#751). */
|
|
13
|
+
export declare const SESSION_LIST_QUERY = "WorkflowType = \"agentSessionWorkflow\" AND ExecutionStatus = \"Running\"";
|
|
3
14
|
/**
|
|
4
15
|
* Resolve a session by player name.
|
|
5
16
|
* Lists all running session workflows and queries each for metadata.
|
|
@@ -8,15 +19,43 @@ import { AttachmentPhase } from '../types';
|
|
|
8
19
|
*
|
|
9
20
|
* Shared by activity files (outbox, schedule-fire) and the tools layer.
|
|
10
21
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
22
|
+
* DECISION-PATH FENCE (#748): this resolver feeds DECISION paths (outbox
|
|
23
|
+
* delivery addressing, schedule fires, tool targets). It must keep its
|
|
24
|
+
* direct per-session `getMetadata` queries — do NOT migrate it to the
|
|
25
|
+
* eventually-consistent SA/memo read path. Observation-only scans belong
|
|
26
|
+
* in `scanEnsembleSessionsCloud`. Enforced by
|
|
27
|
+
* tests/conformance/decision-path-fence.test.ts.
|
|
28
|
+
*
|
|
29
|
+
* **Mode A — deadline truncation (#336/#529):** the visibility iterator is
|
|
30
|
+
* bounded by `VISIBILITY_DEADLINES_MS.resolveSession` (default 10s). On
|
|
31
|
+
* timeout it throws `VisibilityIteratorTimeoutError` rather than returning
|
|
32
|
+
* `null` — silent `null` on a partially-scanned set would be
|
|
33
|
+
* indistinguishable from "definitely not found." The throw is classified
|
|
34
|
+
* **retryable** by the outbox activity (`isRetryableTemporalError`), so
|
|
35
|
+
* Temporal's activity retry policy re-runs the lookup with a fresh
|
|
36
|
+
* deadline rather than collapsing it to a permanent "player not found."
|
|
37
|
+
* Synchronous tool/CLI callers surface it as a distinct "resolution
|
|
38
|
+
* incomplete — retry," never "not found."
|
|
39
|
+
*
|
|
40
|
+
* **Mode B — visibility-index lag (#845):** `list()` can complete normally
|
|
41
|
+
* (no throw) yet miss a freshly-started workflow because the visibility
|
|
42
|
+
* index trails the workflow store (observed live as a 3/8→8/8 roster
|
|
43
|
+
* during post-restart worker warmup). An early-exhausting scan is NOT
|
|
44
|
+
* proof of absence. So on the not-found branch we do **exactly one**
|
|
45
|
+
* strongly-consistent `describe()` against the *derived* workflow id —
|
|
46
|
+
* an O(1) read by primary key that bypasses the lagging index. This is a
|
|
47
|
+
* point lookup, NOT a re-scan: it cannot re-introduce the unbounded-scan
|
|
48
|
+
* hang the deadline guard was added to prevent.
|
|
49
|
+
*
|
|
50
|
+
* **Documented Mode-B limitation:** the derived id
|
|
51
|
+
* `agent-session-{ensemble}-{playerName}` is minted from a player's
|
|
52
|
+
* INITIAL name at spawn; `set_name` does not change the workflow id. So
|
|
53
|
+
* describe-by-derived-id false-negatives for a player that was both
|
|
54
|
+
* RENAMED and is currently index-lagged — it falls back to `null` (looks
|
|
55
|
+
* absent) for that narrow intersection. Accepted by design: it closes the
|
|
56
|
+
* gap for the cold-boot/warmup incident class (nobody renames mid-boot),
|
|
57
|
+
* and a second full re-scan to cover renamed∩lagged would put scan cost on
|
|
58
|
+
* every genuine typo'd-name lookup. See issue #845.
|
|
20
59
|
*/
|
|
21
60
|
export declare function resolveSession(client: Client, ensemble: string, playerName: string): Promise<WorkflowHandle | null>;
|
|
22
61
|
/** Info returned for each session by scanEnsembleSessions. */
|
|
@@ -50,15 +89,77 @@ export interface EnsembleSessionInfo {
|
|
|
50
89
|
lastActivityAt?: string;
|
|
51
90
|
}
|
|
52
91
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
92
|
+
* T0.1 (#748) — cloud-profile ensemble scan. Observation path ONLY (see the
|
|
93
|
+
* DECISION-PATH FENCE on {@link resolveSession}).
|
|
94
|
+
*
|
|
95
|
+
* Differences vs the legacy {@link scanEnsembleSessions}:
|
|
96
|
+
* - The visibility query is **ensemble-scoped** via the `AgentTempoEnsemble`
|
|
97
|
+
* filter SA — no more cluster-wide list + per-session `getMetadata`
|
|
98
|
+
* pre-filtering (the unfiltered scan was the dominant idle-burn driver).
|
|
99
|
+
* - For v1.8+ runs (memo-complete: `AgentTempoWorkDir` present), the entire
|
|
100
|
+
* player row is read from the list result (SAs + memo) — **zero**
|
|
101
|
+
* per-player queries except the BPM `getActivityState` query, which is
|
|
102
|
+
* intentionally kept per the architect's ruling (deriving BPM from phase
|
|
103
|
+
* transitions would change the metric's meaning).
|
|
104
|
+
* - Pre-v1.8 runs (no observation memo) fall back to the legacy per-player
|
|
105
|
+
* `getMetadata` + `getPart` queries — cost shrinks as old runs cycle out.
|
|
106
|
+
*
|
|
107
|
+
* Staleness: SA/memo reads are eventually consistent (tens of seconds worst
|
|
108
|
+
* case under backlog) — acceptable for the observation path per the design
|
|
109
|
+
* addendum §B; the aggregate's confirm-on-change hook re-validates phase
|
|
110
|
+
* transitions with a direct query before emitting SSE events.
|
|
111
|
+
*/
|
|
112
|
+
export declare function scanEnsembleSessionsCloud(client: Client, ensemble: string, log?: (...args: unknown[]) => void): Promise<EnsembleSessionInfo[]>;
|
|
113
|
+
/**
|
|
114
|
+
* Result of {@link scanEnsembleSessionsWithStatus} — the session rows plus
|
|
115
|
+
* whether the visibility scan completed or was cut short (#845).
|
|
116
|
+
*
|
|
117
|
+
* `truncated` is the Mode-A signal: a `VisibilityIteratorTimeoutError`
|
|
118
|
+
* fired (the wall-clock deadline tripped mid-scan), so `sessions` is a
|
|
119
|
+
* partial snapshot, NOT the full roster. Callers that render a roster (the
|
|
120
|
+
* `ensemble` tool) MUST surface this so a partial set is never mistaken
|
|
121
|
+
* for a complete one. NOTE: this does NOT cover Mode B (visibility-index
|
|
122
|
+
* lag) — there the scan completes normally and `truncated` is `false` even
|
|
123
|
+
* though a freshly-started workflow may be missing; that's best-effort by
|
|
124
|
+
* design and self-heals on the next tick.
|
|
125
|
+
*
|
|
126
|
+
* `scanned` is the number of running workflows the iterator visited before
|
|
127
|
+
* completing or timing out — useful for warn logs ("partial: 3 of ≥N").
|
|
128
|
+
*/
|
|
129
|
+
export interface EnsembleScanResult {
|
|
130
|
+
sessions: EnsembleSessionInfo[];
|
|
131
|
+
truncated: boolean;
|
|
132
|
+
scanned: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Scan all running session workflows in an ensemble, reporting whether the
|
|
136
|
+
* scan completed or was truncated by the visibility deadline (#845).
|
|
137
|
+
*
|
|
138
|
+
* This is the single source of truth for the local-profile ensemble scan;
|
|
139
|
+
* {@link scanEnsembleSessions} is a thin array-facade over it that drops
|
|
140
|
+
* the status fields for the many callers that don't need them.
|
|
56
141
|
*
|
|
57
142
|
* **Deadline (#336/#529):** the iterator is bounded by
|
|
58
|
-
* `VISIBILITY_DEADLINES_MS.scanEnsembleSessions` (default 15s). On
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
143
|
+
* `VISIBILITY_DEADLINES_MS.scanEnsembleSessions` (default 15s). On timeout
|
|
144
|
+
* the accumulated rows are returned with `truncated: true` and a warn log
|
|
145
|
+
* — the scan is **partial-tolerant by design**, but the truncation is now
|
|
146
|
+
* SIGNALLED rather than silent so a roster renderer can flag it.
|
|
147
|
+
*
|
|
148
|
+
* T0.1 (#748): this legacy shape is the `costProfile: 'local'` path —
|
|
149
|
+
* byte-identical row data to pre-#748 behavior. The cloud profile uses
|
|
150
|
+
* {@link scanEnsembleSessionsCloud}.
|
|
151
|
+
*/
|
|
152
|
+
export declare function scanEnsembleSessionsWithStatus(client: Client, ensemble: string, log?: (...args: unknown[]) => void): Promise<EnsembleScanResult>;
|
|
153
|
+
/**
|
|
154
|
+
* Scan all running session workflows in an ensemble — array facade over
|
|
155
|
+
* {@link scanEnsembleSessionsWithStatus}.
|
|
156
|
+
*
|
|
157
|
+
* Returns just the session rows; the truncation/scan-status fields are
|
|
158
|
+
* dropped. This is the byte-identical shape the maestro refresh activity,
|
|
159
|
+
* the #785 upgrade-snapshot, and the other roster consumers already depend
|
|
160
|
+
* on — keeping it a thin delegate means the truncation-signalling work
|
|
161
|
+
* (#845) does NOT ripple through those call sites. Callers that need to
|
|
162
|
+
* know whether the scan was complete (the `ensemble` tool) call the rich
|
|
163
|
+
* sibling directly.
|
|
63
164
|
*/
|
|
64
165
|
export declare function scanEnsembleSessions(client: Client, ensemble: string, log?: (...args: unknown[]) => void): Promise<EnsembleSessionInfo[]>;
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SESSION_LIST_QUERY = exports.RESOLVE_DESCRIBE_TIMEOUT_MS = void 0;
|
|
3
4
|
exports.resolveSession = resolveSession;
|
|
5
|
+
exports.scanEnsembleSessionsCloud = scanEnsembleSessionsCloud;
|
|
6
|
+
exports.scanEnsembleSessionsWithStatus = scanEnsembleSessionsWithStatus;
|
|
4
7
|
exports.scanEnsembleSessions = scanEnsembleSessions;
|
|
8
|
+
const config_1 = require("../config");
|
|
5
9
|
const search_attributes_1 = require("../utils/search-attributes");
|
|
6
10
|
const signals_1 = require("../workflows/signals");
|
|
7
11
|
const query_timeout_1 = require("../utils/query-timeout");
|
|
8
12
|
const visibility_deadline_1 = require("../utils/visibility-deadline");
|
|
9
|
-
/**
|
|
10
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Mode-B describe-by-id timeout (#845). The strongly-consistent
|
|
15
|
+
* `describe()` fallback on `resolveSession`'s not-found branch is a single
|
|
16
|
+
* O(1) RPC; 2s mirrors {@link DEFAULT_QUERY_TIMEOUT_MS} — two orders of
|
|
17
|
+
* magnitude over a healthy describe — so a wedged frontend can't re-hang
|
|
18
|
+
* the outbox loop the visibility deadline (#336/#529) was added to bound.
|
|
19
|
+
*/
|
|
20
|
+
exports.RESOLVE_DESCRIBE_TIMEOUT_MS = 2000;
|
|
21
|
+
/** Shared query for listing running session workflows. Exported for the
|
|
22
|
+
* ensemble-scoped variants in `client/core.ts` (#751). */
|
|
23
|
+
exports.SESSION_LIST_QUERY = `WorkflowType = "agentSessionWorkflow" AND ExecutionStatus = "Running"`;
|
|
11
24
|
/**
|
|
12
25
|
* Resolve a session by player name.
|
|
13
26
|
* Lists all running session workflows and queries each for metadata.
|
|
@@ -16,18 +29,46 @@ const SESSION_LIST_QUERY = `WorkflowType = "agentSessionWorkflow" AND ExecutionS
|
|
|
16
29
|
*
|
|
17
30
|
* Shared by activity files (outbox, schedule-fire) and the tools layer.
|
|
18
31
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
32
|
+
* DECISION-PATH FENCE (#748): this resolver feeds DECISION paths (outbox
|
|
33
|
+
* delivery addressing, schedule fires, tool targets). It must keep its
|
|
34
|
+
* direct per-session `getMetadata` queries — do NOT migrate it to the
|
|
35
|
+
* eventually-consistent SA/memo read path. Observation-only scans belong
|
|
36
|
+
* in `scanEnsembleSessionsCloud`. Enforced by
|
|
37
|
+
* tests/conformance/decision-path-fence.test.ts.
|
|
38
|
+
*
|
|
39
|
+
* **Mode A — deadline truncation (#336/#529):** the visibility iterator is
|
|
40
|
+
* bounded by `VISIBILITY_DEADLINES_MS.resolveSession` (default 10s). On
|
|
41
|
+
* timeout it throws `VisibilityIteratorTimeoutError` rather than returning
|
|
42
|
+
* `null` — silent `null` on a partially-scanned set would be
|
|
43
|
+
* indistinguishable from "definitely not found." The throw is classified
|
|
44
|
+
* **retryable** by the outbox activity (`isRetryableTemporalError`), so
|
|
45
|
+
* Temporal's activity retry policy re-runs the lookup with a fresh
|
|
46
|
+
* deadline rather than collapsing it to a permanent "player not found."
|
|
47
|
+
* Synchronous tool/CLI callers surface it as a distinct "resolution
|
|
48
|
+
* incomplete — retry," never "not found."
|
|
49
|
+
*
|
|
50
|
+
* **Mode B — visibility-index lag (#845):** `list()` can complete normally
|
|
51
|
+
* (no throw) yet miss a freshly-started workflow because the visibility
|
|
52
|
+
* index trails the workflow store (observed live as a 3/8→8/8 roster
|
|
53
|
+
* during post-restart worker warmup). An early-exhausting scan is NOT
|
|
54
|
+
* proof of absence. So on the not-found branch we do **exactly one**
|
|
55
|
+
* strongly-consistent `describe()` against the *derived* workflow id —
|
|
56
|
+
* an O(1) read by primary key that bypasses the lagging index. This is a
|
|
57
|
+
* point lookup, NOT a re-scan: it cannot re-introduce the unbounded-scan
|
|
58
|
+
* hang the deadline guard was added to prevent.
|
|
59
|
+
*
|
|
60
|
+
* **Documented Mode-B limitation:** the derived id
|
|
61
|
+
* `agent-session-{ensemble}-{playerName}` is minted from a player's
|
|
62
|
+
* INITIAL name at spawn; `set_name` does not change the workflow id. So
|
|
63
|
+
* describe-by-derived-id false-negatives for a player that was both
|
|
64
|
+
* RENAMED and is currently index-lagged — it falls back to `null` (looks
|
|
65
|
+
* absent) for that narrow intersection. Accepted by design: it closes the
|
|
66
|
+
* gap for the cold-boot/warmup incident class (nobody renames mid-boot),
|
|
67
|
+
* and a second full re-scan to cover renamed∩lagged would put scan cost on
|
|
68
|
+
* every genuine typo'd-name lookup. See issue #845.
|
|
28
69
|
*/
|
|
29
70
|
async function resolveSession(client, ensemble, playerName) {
|
|
30
|
-
for await (const wf of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query: SESSION_LIST_QUERY }), visibility_deadline_1.VISIBILITY_DEADLINES_MS.resolveSession, 'resolveSession')) {
|
|
71
|
+
for await (const wf of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query: exports.SESSION_LIST_QUERY }), visibility_deadline_1.VISIBILITY_DEADLINES_MS.resolveSession, 'resolveSession')) {
|
|
31
72
|
try {
|
|
32
73
|
const handle = client.workflow.getHandle(wf.workflowId);
|
|
33
74
|
// Issue #433 — bound the per-session metadata query so a wedged
|
|
@@ -39,33 +80,204 @@ async function resolveSession(client, ensemble, playerName) {
|
|
|
39
80
|
}
|
|
40
81
|
}
|
|
41
82
|
catch (err) {
|
|
42
|
-
// Re-throw deadline timeouts — callers that wrap us in
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// outbox logs / user-facing tool errors.
|
|
83
|
+
// Re-throw deadline timeouts (Mode A) — callers that wrap us in
|
|
84
|
+
// try/catch treat the typed throw as a soft "lookup timed out" path,
|
|
85
|
+
// distinct from the not-found `null` below.
|
|
46
86
|
if ((0, visibility_deadline_1.isVisibilityTimeout)(err))
|
|
47
87
|
throw err;
|
|
48
88
|
// Workflow may have just completed, or worker is wedged (#433) — skip
|
|
49
89
|
}
|
|
50
90
|
}
|
|
51
|
-
|
|
91
|
+
// Mode B (#845): the scan completed without a match, but the visibility
|
|
92
|
+
// index may simply be lagging a just-started workflow. One strongly-
|
|
93
|
+
// consistent describe-by-derived-id disambiguates "index lag" from
|
|
94
|
+
// "genuinely absent" without a second scan.
|
|
95
|
+
return resolveByDerivedId(client, ensemble, playerName);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Mode-B (#845) strongly-consistent fallback for {@link resolveSession}.
|
|
99
|
+
*
|
|
100
|
+
* Reads the session workflow by its *derived* id
|
|
101
|
+
* (`agent-session-{ensemble}-{playerName}`) via a single bounded
|
|
102
|
+
* `describe()` — a primary-key lookup that bypasses the eventually-
|
|
103
|
+
* consistent visibility index. Returns the handle whenever the execution
|
|
104
|
+
* is `RUNNING`; otherwise `null` (genuinely absent, terminated/completed,
|
|
105
|
+
* renamed-false-negative, or describe timed out).
|
|
106
|
+
*
|
|
107
|
+
* Deliberately RUNNING-only — NO attachment-phase filter (#845 JC2): a
|
|
108
|
+
* `gone` player has a LIVE workflow with a terminal adapter, which the
|
|
109
|
+
* #822/#834 deliverability contract handles as warn-but-queue, not
|
|
110
|
+
* "not found". Filtering it here would regress #834 for the lagged-gone
|
|
111
|
+
* window and diverge from the main scan loop (which has no phase filter).
|
|
112
|
+
*/
|
|
113
|
+
async function resolveByDerivedId(client, ensemble, playerName) {
|
|
114
|
+
let timer;
|
|
115
|
+
try {
|
|
116
|
+
// `getHandle` is a lazy, no-RPC handle construction in the real client;
|
|
117
|
+
// kept inside the try purely so a defensive throw can never escape the
|
|
118
|
+
// fallback (it must only ever upgrade a null to a handle, never error).
|
|
119
|
+
const handle = client.workflow.getHandle((0, config_1.sessionWorkflowId)(ensemble, playerName));
|
|
120
|
+
const timeout = new Promise((_, reject) => {
|
|
121
|
+
timer = setTimeout(() => reject(new Error('describe-by-id timed out')), exports.RESOLVE_DESCRIBE_TIMEOUT_MS);
|
|
122
|
+
timer.unref?.();
|
|
123
|
+
});
|
|
124
|
+
const desc = await Promise.race([handle.describe(), timeout]);
|
|
125
|
+
// Only a live (RUNNING) execution is a valid resolve target. A
|
|
126
|
+
// COMPLETED/TERMINATED latest run at this id means the player is gone,
|
|
127
|
+
// or the id was reused by a since-closed run → null. A RUNNING run
|
|
128
|
+
// under a reused id is legitimately the current player → return it.
|
|
129
|
+
//
|
|
130
|
+
// No attachment-phase filter (#845 JC2, architect ruling): the main
|
|
131
|
+
// scan loop returns the handle for ANY running session — phase=`gone`
|
|
132
|
+
// included — and #822/#834 treat `gone` as warn-but-QUEUE (the cue
|
|
133
|
+
// durably queues and auto-redelivers on re-attach), NOT "not found".
|
|
134
|
+
// Returning null for a lagged-`gone` player would bypass #822, re-
|
|
135
|
+
// introduce the false-not-found #834 fixed, and make resolution depend
|
|
136
|
+
// on visibility-index timing. The "don't deliver to a torn-down
|
|
137
|
+
// adapter" concern lives at the deliverability layer, not here.
|
|
138
|
+
if (desc.status.name !== 'RUNNING')
|
|
139
|
+
return null;
|
|
140
|
+
return handle;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// NotFound → genuinely absent (or the renamed∩lagged false-negative
|
|
144
|
+
// documented on resolveSession). Timeout/other → treat as absent; the
|
|
145
|
+
// caller's not-found path (or the activity retry policy for Mode A)
|
|
146
|
+
// handles it. We never throw from the fallback — it can only upgrade a
|
|
147
|
+
// null to a found handle, never turn a clean lookup into an error.
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
if (timer)
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
}
|
|
52
154
|
}
|
|
53
155
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
156
|
+
* T0.1 (#748) — cloud-profile ensemble scan. Observation path ONLY (see the
|
|
157
|
+
* DECISION-PATH FENCE on {@link resolveSession}).
|
|
158
|
+
*
|
|
159
|
+
* Differences vs the legacy {@link scanEnsembleSessions}:
|
|
160
|
+
* - The visibility query is **ensemble-scoped** via the `AgentTempoEnsemble`
|
|
161
|
+
* filter SA — no more cluster-wide list + per-session `getMetadata`
|
|
162
|
+
* pre-filtering (the unfiltered scan was the dominant idle-burn driver).
|
|
163
|
+
* - For v1.8+ runs (memo-complete: `AgentTempoWorkDir` present), the entire
|
|
164
|
+
* player row is read from the list result (SAs + memo) — **zero**
|
|
165
|
+
* per-player queries except the BPM `getActivityState` query, which is
|
|
166
|
+
* intentionally kept per the architect's ruling (deriving BPM from phase
|
|
167
|
+
* transitions would change the metric's meaning).
|
|
168
|
+
* - Pre-v1.8 runs (no observation memo) fall back to the legacy per-player
|
|
169
|
+
* `getMetadata` + `getPart` queries — cost shrinks as old runs cycle out.
|
|
170
|
+
*
|
|
171
|
+
* Staleness: SA/memo reads are eventually consistent (tens of seconds worst
|
|
172
|
+
* case under backlog) — acceptable for the observation path per the design
|
|
173
|
+
* addendum §B; the aggregate's confirm-on-change hook re-validates phase
|
|
174
|
+
* transitions with a direct query before emitting SSE events.
|
|
175
|
+
*/
|
|
176
|
+
async function scanEnsembleSessionsCloud(client, ensemble, log = () => { }) {
|
|
177
|
+
const sessions = [];
|
|
178
|
+
const query = `${exports.SESSION_LIST_QUERY} AND AgentTempoEnsemble = "${(0, search_attributes_1.sanitizeQueryValue)(ensemble)}"`;
|
|
179
|
+
try {
|
|
180
|
+
for await (const workflow of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query }), visibility_deadline_1.VISIBILITY_DEADLINES_MS.scanEnsembleSessions, 'scanEnsembleSessionsCloud')) {
|
|
181
|
+
try {
|
|
182
|
+
const handle = client.workflow.getHandle(workflow.workflowId);
|
|
183
|
+
const phase = (0, search_attributes_1.getAttachmentPhase)(workflow);
|
|
184
|
+
const playerId = (0, search_attributes_1.getSearchAttrString)(workflow, 'AgentTempoPlayerId') ?? workflow.workflowId;
|
|
185
|
+
const hostname = (0, search_attributes_1.getSearchAttrString)(workflow, 'AgentTempoHostname') ?? '';
|
|
186
|
+
// v1.8-memo-observation-fields runs carry workDir on the memo — use
|
|
187
|
+
// its presence as the "memo-complete row" discriminator.
|
|
188
|
+
const workDir = (0, search_attributes_1.getMemoString)(workflow, search_attributes_1.MEMO_KEYS.workDir);
|
|
189
|
+
let row;
|
|
190
|
+
if (workDir !== undefined) {
|
|
191
|
+
row = {
|
|
192
|
+
playerId,
|
|
193
|
+
part: (0, search_attributes_1.getPart)(workflow) ?? '',
|
|
194
|
+
hostname,
|
|
195
|
+
workDir,
|
|
196
|
+
gitRoot: (0, search_attributes_1.getWorkflowMetaString)(workflow, search_attributes_1.MEMO_KEYS.gitRoot),
|
|
197
|
+
gitBranch: (0, search_attributes_1.getMemoString)(workflow, search_attributes_1.MEMO_KEYS.gitBranch),
|
|
198
|
+
isConductor: (0, search_attributes_1.getIsConductor)(workflow)
|
|
199
|
+
?? (workflow.workflowId?.endsWith('-conductor') ?? false),
|
|
200
|
+
agentType: (0, search_attributes_1.getMemoString)(workflow, search_attributes_1.MEMO_KEYS.agentType) || 'claude',
|
|
201
|
+
playerType: (0, search_attributes_1.getPlayerType)(workflow),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Legacy run (pre-v1.8 memo) — per-player query fallback, bounded
|
|
206
|
+
// (#433). Same two queries the legacy scan used.
|
|
207
|
+
const metadata = await (0, query_timeout_1.queryHandleWithTimeout)(handle, 'getMetadata');
|
|
208
|
+
const part = await (0, query_timeout_1.queryHandleWithTimeout)(handle, 'getPart');
|
|
209
|
+
row = {
|
|
210
|
+
playerId: metadata.playerId,
|
|
211
|
+
part,
|
|
212
|
+
hostname: metadata.hostname,
|
|
213
|
+
workDir: metadata.workDir,
|
|
214
|
+
gitRoot: metadata.gitRoot,
|
|
215
|
+
gitBranch: metadata.gitBranch,
|
|
216
|
+
isConductor: metadata.isConductor,
|
|
217
|
+
agentType: metadata.agentType || 'claude',
|
|
218
|
+
playerType: metadata.playerType,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// BPM fields filled in below — kept out of the enumeration loop so
|
|
222
|
+
// per-player query latency can't eat the visibility deadline.
|
|
223
|
+
sessions.push({ workflowId: workflow.workflowId, ...row, phase });
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Workflow may have just completed, or a legacy-fallback query timed
|
|
227
|
+
// out (#433) — skip this row; the next tick fills it in.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if ((0, visibility_deadline_1.isVisibilityTimeout)(err)) {
|
|
233
|
+
log(`scanEnsembleSessionsCloud: ${err.message} — returning partial (${sessions.length} sessions)`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// BPM source — kept as a direct per-player query at the stretched cadence
|
|
240
|
+
// (architect's ruling; see design addendum §C(b)). Fired in PARALLEL after
|
|
241
|
+
// enumeration: same query count, but N bounded queries (≤2s each) overlap
|
|
242
|
+
// instead of stacking sequentially against the scan's wall clock.
|
|
243
|
+
// Best-effort per player — a wedged session contributes zero tempo.
|
|
244
|
+
await Promise.all(sessions.map(async (s) => {
|
|
245
|
+
try {
|
|
246
|
+
const activity = await (0, query_timeout_1.queryHandleWithTimeout)(client.workflow.getHandle(s.workflowId), signals_1.getActivityStateQuery);
|
|
247
|
+
s.activityCount = activity.activityCount;
|
|
248
|
+
s.lastActivityAt = activity.lastActivityAt;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Session predates W2 or worker wedged — contributes zero tempo.
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
return sessions;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Scan all running session workflows in an ensemble, reporting whether the
|
|
258
|
+
* scan completed or was truncated by the visibility deadline (#845).
|
|
259
|
+
*
|
|
260
|
+
* This is the single source of truth for the local-profile ensemble scan;
|
|
261
|
+
* {@link scanEnsembleSessions} is a thin array-facade over it that drops
|
|
262
|
+
* the status fields for the many callers that don't need them.
|
|
57
263
|
*
|
|
58
264
|
* **Deadline (#336/#529):** the iterator is bounded by
|
|
59
|
-
* `VISIBILITY_DEADLINES_MS.scanEnsembleSessions` (default 15s). On
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
265
|
+
* `VISIBILITY_DEADLINES_MS.scanEnsembleSessions` (default 15s). On timeout
|
|
266
|
+
* the accumulated rows are returned with `truncated: true` and a warn log
|
|
267
|
+
* — the scan is **partial-tolerant by design**, but the truncation is now
|
|
268
|
+
* SIGNALLED rather than silent so a roster renderer can flag it.
|
|
269
|
+
*
|
|
270
|
+
* T0.1 (#748): this legacy shape is the `costProfile: 'local'` path —
|
|
271
|
+
* byte-identical row data to pre-#748 behavior. The cloud profile uses
|
|
272
|
+
* {@link scanEnsembleSessionsCloud}.
|
|
64
273
|
*/
|
|
65
|
-
async function
|
|
274
|
+
async function scanEnsembleSessionsWithStatus(client, ensemble, log = () => { }) {
|
|
66
275
|
const sessions = [];
|
|
276
|
+
let truncated = false;
|
|
277
|
+
let scanned = 0;
|
|
67
278
|
try {
|
|
68
|
-
for await (const workflow of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query: SESSION_LIST_QUERY }), visibility_deadline_1.VISIBILITY_DEADLINES_MS.scanEnsembleSessions, 'scanEnsembleSessions')) {
|
|
279
|
+
for await (const workflow of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query: exports.SESSION_LIST_QUERY }), visibility_deadline_1.VISIBILITY_DEADLINES_MS.scanEnsembleSessions, 'scanEnsembleSessions')) {
|
|
280
|
+
scanned++;
|
|
69
281
|
try {
|
|
70
282
|
const handle = client.workflow.getHandle(workflow.workflowId);
|
|
71
283
|
// Issue #433 — bound the metadata + part queries so a single wedged
|
|
@@ -119,11 +331,27 @@ async function scanEnsembleSessions(client, ensemble, log = () => { }) {
|
|
|
119
331
|
}
|
|
120
332
|
catch (err) {
|
|
121
333
|
if ((0, visibility_deadline_1.isVisibilityTimeout)(err)) {
|
|
334
|
+
truncated = true;
|
|
122
335
|
log(`scanEnsembleSessions: ${err.message} — returning partial (${sessions.length} sessions)`);
|
|
123
336
|
}
|
|
124
337
|
else {
|
|
125
338
|
throw err;
|
|
126
339
|
}
|
|
127
340
|
}
|
|
128
|
-
return sessions;
|
|
341
|
+
return { sessions, truncated, scanned };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Scan all running session workflows in an ensemble — array facade over
|
|
345
|
+
* {@link scanEnsembleSessionsWithStatus}.
|
|
346
|
+
*
|
|
347
|
+
* Returns just the session rows; the truncation/scan-status fields are
|
|
348
|
+
* dropped. This is the byte-identical shape the maestro refresh activity,
|
|
349
|
+
* the #785 upgrade-snapshot, and the other roster consumers already depend
|
|
350
|
+
* on — keeping it a thin delegate means the truncation-signalling work
|
|
351
|
+
* (#845) does NOT ripple through those call sites. Callers that need to
|
|
352
|
+
* know whether the scan was complete (the `ensemble` tool) call the rich
|
|
353
|
+
* sibling directly.
|
|
354
|
+
*/
|
|
355
|
+
async function scanEnsembleSessions(client, ensemble, log = () => { }) {
|
|
356
|
+
return (await scanEnsembleSessionsWithStatus(client, ensemble, log)).sessions;
|
|
129
357
|
}
|
|
@@ -4,13 +4,16 @@ exports.createScheduleActivities = createScheduleActivities;
|
|
|
4
4
|
const croner_1 = require("croner");
|
|
5
5
|
const config_1 = require("../config");
|
|
6
6
|
const resolve_1 = require("./resolve");
|
|
7
|
+
const action_counters_1 = require("../utils/action-counters");
|
|
7
8
|
const visibility_deadline_1 = require("../utils/visibility-deadline");
|
|
8
9
|
/**
|
|
9
10
|
* Create the schedule-fire activity bound to a Temporal client.
|
|
10
11
|
* The returned object is registered with the worker as activities.
|
|
11
12
|
*/
|
|
12
13
|
function createScheduleActivities(client) {
|
|
13
|
-
|
|
14
|
+
// #753 — attribute every Temporal call made by these activities (however
|
|
15
|
+
// deep, e.g. resolveSession → queryHandleWithTimeout) to the scheduler.
|
|
16
|
+
return (0, action_counters_1.tagActionSource)('schedule', {
|
|
14
17
|
async computeNextCronFire(input) {
|
|
15
18
|
const job = new croner_1.Cron(input.cronExpression, { timezone: input.timezone || 'UTC' });
|
|
16
19
|
const next = job.nextRun();
|
|
@@ -104,7 +107,7 @@ function createScheduleActivities(client) {
|
|
|
104
107
|
return { success: false, error: errorMsg };
|
|
105
108
|
}
|
|
106
109
|
},
|
|
107
|
-
};
|
|
110
|
+
});
|
|
108
111
|
}
|
|
109
112
|
/**
|
|
110
113
|
* Notify the schedule creator (or conductor as fallback) that delivery failed.
|
package/dist/adapters/base.d.ts
CHANGED
|
@@ -217,6 +217,7 @@ export declare abstract class BaseAttachment {
|
|
|
217
217
|
* whatever comes next.
|
|
218
218
|
*/
|
|
219
219
|
private tickHeartbeat;
|
|
220
|
+
private tickHeartbeatInner;
|
|
220
221
|
private schedulePhaseWatcher;
|
|
221
222
|
/**
|
|
222
223
|
* Single tick of the phase-watcher loop. Same orphan-resistance scaffolding as
|
|
@@ -227,6 +228,7 @@ export declare abstract class BaseAttachment {
|
|
|
227
228
|
* restart.
|
|
228
229
|
*/
|
|
229
230
|
private tickPhaseWatcher;
|
|
231
|
+
private tickPhaseWatcherInner;
|
|
230
232
|
/**
|
|
231
233
|
* Shared error-classification path for the heartbeat + phase-watcher ticks (#226).
|
|
232
234
|
*
|
package/dist/adapters/base.js
CHANGED
|
@@ -7,6 +7,7 @@ exports._resetProcessLifecycleTelemetryForTest = _resetProcessLifecycleTelemetry
|
|
|
7
7
|
exports._liveAdaptersForTest = _liveAdaptersForTest;
|
|
8
8
|
const signals_1 = require("../workflows/signals");
|
|
9
9
|
const terminal_error_1 = require("./terminal-error");
|
|
10
|
+
const action_counters_1 = require("../utils/action-counters");
|
|
10
11
|
const log = (...args) => console.error('[agent-tempo:adapter]', ...args);
|
|
11
12
|
// ── Hypothesis A telemetry (#258 follow-up) ─────────────────────────────
|
|
12
13
|
//
|
|
@@ -491,6 +492,10 @@ class BaseAttachment {
|
|
|
491
492
|
* whatever comes next.
|
|
492
493
|
*/
|
|
493
494
|
async tickHeartbeat() {
|
|
495
|
+
// #753 — liveness-lease signals are metered as their own source.
|
|
496
|
+
return (0, action_counters_1.withActionSource)('heartbeat', () => this.tickHeartbeatInner());
|
|
497
|
+
}
|
|
498
|
+
async tickHeartbeatInner() {
|
|
494
499
|
try {
|
|
495
500
|
if (this.stopped || this.terminalFired) {
|
|
496
501
|
this.logGuardTrip('heartbeat');
|
|
@@ -551,6 +556,10 @@ class BaseAttachment {
|
|
|
551
556
|
* restart.
|
|
552
557
|
*/
|
|
553
558
|
async tickPhaseWatcher() {
|
|
559
|
+
// #753 — relaxed-poll attachmentInfo queries are metered as their own source.
|
|
560
|
+
return (0, action_counters_1.withActionSource)('phase-watcher', () => this.tickPhaseWatcherInner());
|
|
561
|
+
}
|
|
562
|
+
async tickPhaseWatcherInner() {
|
|
554
563
|
try {
|
|
555
564
|
if (this.stopped || this.terminalFired) {
|
|
556
565
|
this.logGuardTrip('phase-watcher');
|
|
@@ -75,6 +75,7 @@ const client_1 = require("@temporalio/client");
|
|
|
75
75
|
const base_1 = require("../sdk/base");
|
|
76
76
|
const config_1 = require("../../config");
|
|
77
77
|
const connection_1 = require("../../connection");
|
|
78
|
+
const action_counters_1 = require("../../utils/action-counters");
|
|
78
79
|
const signals_1 = require("../../workflows/signals");
|
|
79
80
|
const server_tools_1 = require("../../server-tools");
|
|
80
81
|
const mcp_bridge_1 = require("./mcp-bridge");
|
|
@@ -135,7 +136,11 @@ function truncateErr(err, max = 240) {
|
|
|
135
136
|
}
|
|
136
137
|
/** Default model id — verification addendum §1 (no date suffix on direct API). */
|
|
137
138
|
const DEFAULT_MODEL = 'claude-opus-4-7';
|
|
138
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Busy-wait cadence (ms) while an SDK turn is in flight. The IDLE cadence is
|
|
141
|
+
* owned by the inherited `pollBackoff` (#749, T0.2): 2s base stretching to a
|
|
142
|
+
* 30s cap on empty polls, snapping back to base on any delivered message.
|
|
143
|
+
*/
|
|
139
144
|
const POLL_INTERVAL_MS = 2000;
|
|
140
145
|
/** Workflow-register poll loop bounds. */
|
|
141
146
|
const WORKFLOW_REGISTER_ATTEMPTS = 30;
|
|
@@ -223,6 +228,7 @@ class DirectApiAttachment extends base_1.SdkAttachment {
|
|
|
223
228
|
const client = new client_1.Client({
|
|
224
229
|
connection,
|
|
225
230
|
namespace: config.temporalNamespace,
|
|
231
|
+
interceptors: (0, action_counters_1.actionCountingInterceptors)(),
|
|
226
232
|
});
|
|
227
233
|
// Hand the client + host to BaseAttachment so startV2Lifecycle can
|
|
228
234
|
// issue claimAttachment + heartbeat against it.
|
|
@@ -331,6 +337,10 @@ class DirectApiAttachment extends base_1.SdkAttachment {
|
|
|
331
337
|
await this.mcp?.close().catch(() => { });
|
|
332
338
|
process.exit(1);
|
|
333
339
|
}
|
|
340
|
+
// T1.1 PR-2 — cue doorbell: ding ⇒ immediate poll + backoff reset;
|
|
341
|
+
// connected ⇒ 60s idle ceiling. No token / daemon down ⇒ silent no-op
|
|
342
|
+
// (pure #761 polling).
|
|
343
|
+
this.startDoorbell(config.ensemble, playerIdForWorkflow);
|
|
334
344
|
// PID file so callers can find / kill orphaned adapter processes.
|
|
335
345
|
// #690 — write/unlink the EXACT path the spawner computed (ENV.PID_FILE) so the
|
|
336
346
|
// adapter pid can't diverge from the spawner's; helper fallback for a manual launch.
|
|
@@ -356,7 +366,9 @@ class DirectApiAttachment extends base_1.SdkAttachment {
|
|
|
356
366
|
process.on('SIGINT', shutdown);
|
|
357
367
|
process.on('SIGTERM', shutdown);
|
|
358
368
|
// Drive the poll loop until cleanup is requested.
|
|
359
|
-
|
|
369
|
+
// #753 — meter the poll loop's Temporal calls under 'sdk-poller'.
|
|
370
|
+
await (0, action_counters_1.withActionSource)('sdk-poller', () => this.pollLoop(handle));
|
|
371
|
+
this.stopDoorbell();
|
|
360
372
|
try {
|
|
361
373
|
fs.unlinkSync(pidFile);
|
|
362
374
|
}
|
|
@@ -381,6 +393,9 @@ class DirectApiAttachment extends base_1.SdkAttachment {
|
|
|
381
393
|
// wedge the player indefinitely.
|
|
382
394
|
let consecutiveFailures = 0;
|
|
383
395
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
396
|
+
// #749: fresh poll loop (initial start OR reconnect) begins at the fast
|
|
397
|
+
// cadence — never inherit a stale 30s interval across a restart.
|
|
398
|
+
this.pollBackoff.reset();
|
|
384
399
|
while (polling && !this.shouldStop()) {
|
|
385
400
|
pollCount++;
|
|
386
401
|
// Periodic workflow status check — detect external destroy / completion.
|
|
@@ -418,13 +433,20 @@ class DirectApiAttachment extends base_1.SdkAttachment {
|
|
|
418
433
|
}
|
|
419
434
|
catch (err) {
|
|
420
435
|
log(`pendingMessages query failed: ${err?.message ?? err}`);
|
|
421
|
-
|
|
436
|
+
// #749: grow on errors too — mirrors the interactive poller's shape
|
|
437
|
+
// (a wedged worker shouldn't be hammered at 2s).
|
|
438
|
+
// T1.1: pollSleep is doorbell-interruptible — a ding ends it early.
|
|
439
|
+
await this.pollSleep(this.pollBackoff.next(false));
|
|
422
440
|
continue;
|
|
423
441
|
}
|
|
424
442
|
if (messages.length === 0) {
|
|
425
|
-
|
|
443
|
+
// #749: idle backoff — 2s → 30s while nothing is pending (60s
|
|
444
|
+
// ceiling while the T1.1 doorbell is connected; a ding wakes it).
|
|
445
|
+
await this.pollSleep(this.pollBackoff.next(false));
|
|
426
446
|
continue;
|
|
427
447
|
}
|
|
448
|
+
// #749: live conversation — snap back to the fast cadence.
|
|
449
|
+
this.pollBackoff.reset();
|
|
428
450
|
processing = true;
|
|
429
451
|
try {
|
|
430
452
|
// Representative message for processingStart/End pairing; ack the
|