@agent-relay/factory 0.1.67 → 0.1.69
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/dist/cli/fleet.d.ts.map +1 -1
- package/dist/cli/fleet.js +14 -2
- package/dist/cli/fleet.js.map +1 -1
- package/dist/config/schema.d.ts +87 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +59 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/dispatch/work-unit-identity.d.ts +18 -1
- package/dist/dispatch/work-unit-identity.d.ts.map +1 -1
- package/dist/dispatch/work-unit-identity.js +68 -0
- package/dist/dispatch/work-unit-identity.js.map +1 -1
- package/dist/fleet/create-fleet.d.ts +6 -0
- package/dist/fleet/create-fleet.d.ts.map +1 -1
- package/dist/fleet/create-fleet.js +1 -0
- package/dist/fleet/create-fleet.js.map +1 -1
- package/dist/fleet/relay-fleet-client.d.ts +23 -0
- package/dist/fleet/relay-fleet-client.d.ts.map +1 -1
- package/dist/fleet/relay-fleet-client.js +206 -5
- package/dist/fleet/relay-fleet-client.js.map +1 -1
- package/dist/issue-key-match.d.ts +47 -0
- package/dist/issue-key-match.d.ts.map +1 -1
- package/dist/issue-key-match.js +174 -0
- package/dist/issue-key-match.js.map +1 -1
- package/dist/mount/relayfile-integration-preflight.js +7 -0
- package/dist/mount/relayfile-integration-preflight.js.map +1 -1
- package/dist/orchestrator/atomic-json-file.d.ts +44 -0
- package/dist/orchestrator/atomic-json-file.d.ts.map +1 -0
- package/dist/orchestrator/atomic-json-file.js +87 -0
- package/dist/orchestrator/atomic-json-file.js.map +1 -0
- package/dist/orchestrator/batch-tracker.d.ts +18 -0
- package/dist/orchestrator/batch-tracker.d.ts.map +1 -1
- package/dist/orchestrator/batch-tracker.js +28 -13
- package/dist/orchestrator/batch-tracker.js.map +1 -1
- package/dist/orchestrator/factory.d.ts.map +1 -1
- package/dist/orchestrator/factory.js +410 -91
- package/dist/orchestrator/factory.js.map +1 -1
- package/dist/orchestrator/public-health.d.ts.map +1 -1
- package/dist/orchestrator/public-health.js +115 -13
- package/dist/orchestrator/public-health.js.map +1 -1
- package/dist/ports/state.d.ts +21 -2
- package/dist/ports/state.d.ts.map +1 -1
- package/dist/ports/state.js +18 -1
- package/dist/ports/state.js.map +1 -1
- package/dist/state/file-state-store.d.ts.map +1 -1
- package/dist/state/file-state-store.js +54 -11
- package/dist/state/file-state-store.js.map +1 -1
- package/dist/state/github-lifecycle-identity.d.ts +4 -4
- package/dist/state/github-lifecycle-identity.d.ts.map +1 -1
- package/dist/state/github-lifecycle-identity.js +4 -26
- package/dist/state/github-lifecycle-identity.js.map +1 -1
- package/dist/state/in-memory-state-store.d.ts.map +1 -1
- package/dist/state/in-memory-state-store.js +40 -9
- package/dist/state/in-memory-state-store.js.map +1 -1
- package/dist/state/work-unit-lifecycle-migration.d.ts +85 -0
- package/dist/state/work-unit-lifecycle-migration.d.ts.map +1 -0
- package/dist/state/work-unit-lifecycle-migration.js +246 -0
- package/dist/state/work-unit-lifecycle-migration.js.map +1 -0
- package/dist/triage/heuristic.d.ts.map +1 -1
- package/dist/triage/heuristic.js +3 -1
- package/dist/triage/heuristic.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -5,8 +5,15 @@ const NOTION_PAGE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f
|
|
|
5
5
|
/**
|
|
6
6
|
* Provider-native identity for one issue, independent of whichever Relayfile
|
|
7
7
|
* alias or local mount path surfaced it to Factory.
|
|
8
|
+
*
|
|
9
|
+
* A declared origin outranks the sense path: a Linear mirror of a GitHub issue
|
|
10
|
+
* carries a `/linear/...` path but is the same work unit as the GitHub issue,
|
|
11
|
+
* and must not get an identity of its own.
|
|
8
12
|
*/
|
|
9
13
|
export function dispatchIssueIdentity(issue) {
|
|
14
|
+
const origin = githubOriginIdentity(issue.origin);
|
|
15
|
+
if (origin)
|
|
16
|
+
return `github:${origin}`;
|
|
10
17
|
const github = githubLifecycleIdentity({ issue });
|
|
11
18
|
if (github)
|
|
12
19
|
return `github:${github}`;
|
|
@@ -37,4 +44,65 @@ export function dispatchNotionPageIdentity(pageId) {
|
|
|
37
44
|
export function dispatchAgentIdentityKey(issue, role) {
|
|
38
45
|
return `${DISPATCH_IDENTITY_VERSION}:${dispatchIssueIdentity(issue)}:${role}`;
|
|
39
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The GitHub half of a declared origin, in exactly the spelling
|
|
49
|
+
* `githubLifecycleIdentity` emits for a GitHub-native path, so a mirror and
|
|
50
|
+
* the native row it mirrors produce one identical identity.
|
|
51
|
+
*/
|
|
52
|
+
const githubOriginIdentity = (origin) => {
|
|
53
|
+
if (origin?.provider !== 'github')
|
|
54
|
+
return undefined;
|
|
55
|
+
const owner = origin.owner.trim().toLowerCase();
|
|
56
|
+
// Some providers spell the origin repo `owner/repo`; the identity wants the
|
|
57
|
+
// bare name. Trimmed by prefix rather than by a regex built from `owner`,
|
|
58
|
+
// which would throw on an owner containing a regex metacharacter and take
|
|
59
|
+
// triage and dispatch down with it.
|
|
60
|
+
const rawRepo = origin.repo.trim().toLowerCase();
|
|
61
|
+
const repo = rawRepo.startsWith(`${owner}/`) ? rawRepo.slice(owner.length + 1) : rawRepo;
|
|
62
|
+
if (!owner || !repo || repo.includes('/'))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!Number.isSafeInteger(origin.number) || origin.number <= 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
return `${owner}/${repo}#${origin.number}`;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The structured GitHub origin a surface declares in its provider payload
|
|
70
|
+
* (`source: { provider: 'github', owner, repo, number }`) — the same record
|
|
71
|
+
* `safety/factory-scope.ts` reads to recognise a `[factory]` GitHub mirror.
|
|
72
|
+
*
|
|
73
|
+
* Returns undefined for a native Linear issue, which has no upstream origin
|
|
74
|
+
* and is correctly identified by its own uuid.
|
|
75
|
+
*/
|
|
76
|
+
export function mirrorWorkUnitOrigin(issue) {
|
|
77
|
+
// A surface whose own path already names the GitHub issue is not a mirror —
|
|
78
|
+
// it identifies itself, and recording a redundant origin would widen every
|
|
79
|
+
// persisted IssueRef for nothing.
|
|
80
|
+
if (githubLifecycleIdentity({ issue: { uuid: '', key: '', path: issue.path } }))
|
|
81
|
+
return undefined;
|
|
82
|
+
return workUnitOriginFromRaw(issue.raw);
|
|
83
|
+
}
|
|
84
|
+
export function workUnitOriginFromRaw(raw) {
|
|
85
|
+
const source = asRecord(wrappedPayload(raw).source);
|
|
86
|
+
if (!source || stringValue(source.provider)?.toLowerCase() !== 'github')
|
|
87
|
+
return undefined;
|
|
88
|
+
const owner = stringValue(source.owner)?.trim() ?? '';
|
|
89
|
+
const rawRepo = stringValue(source.repo)?.trim() ?? '';
|
|
90
|
+
const repo = owner && rawRepo.toLowerCase().startsWith(`${owner.toLowerCase()}/`)
|
|
91
|
+
? rawRepo.slice(owner.length + 1)
|
|
92
|
+
: rawRepo;
|
|
93
|
+
const number = typeof source.number === 'number' ? source.number : Number(source.number);
|
|
94
|
+
if (!owner || !repo || repo.includes('/'))
|
|
95
|
+
return undefined;
|
|
96
|
+
if (!Number.isSafeInteger(number) || number <= 0)
|
|
97
|
+
return undefined;
|
|
98
|
+
return { provider: 'github', owner, repo, number };
|
|
99
|
+
}
|
|
100
|
+
const wrappedPayload = (value) => {
|
|
101
|
+
const record = asRecord(value);
|
|
102
|
+
return asRecord(record?.payload) ?? record ?? {};
|
|
103
|
+
};
|
|
104
|
+
const asRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
105
|
+
? value
|
|
106
|
+
: undefined;
|
|
107
|
+
const stringValue = (value) => typeof value === 'string' ? value : undefined;
|
|
40
108
|
//# sourceMappingURL=work-unit-identity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-unit-identity.js","sourceRoot":"","sources":["../../src/dispatch/work-unit-identity.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAA;AAE5E,MAAM,yBAAyB,GAAG,qBAAqB,CAAA;AACvD,MAAM,cAAc,GAAG,iEAAiE,CAAA;AAExF
|
|
1
|
+
{"version":3,"file":"work-unit-identity.js","sourceRoot":"","sources":["../../src/dispatch/work-unit-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,12 @@ export interface CreateFleetOptions {
|
|
|
9
9
|
cwd?: string;
|
|
10
10
|
connectionPath?: string;
|
|
11
11
|
previewConfig?: PreviewConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Workspace identity the relay backend registers as (config `relay.agentName`).
|
|
14
|
+
* Relay-only; the internal backend has no workspace registration. Undefined
|
|
15
|
+
* leaves `RelayFleetClient`'s own default in place.
|
|
16
|
+
*/
|
|
17
|
+
relayAgentName?: string;
|
|
12
18
|
}
|
|
13
19
|
export interface CreateFleetDeps {
|
|
14
20
|
harnessClient?: HarnessDriverClientLike;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-fleet.d.ts","sourceRoot":"","sources":["../../src/fleet/create-fleet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,OAAO,CAAA;AAC/C,eAAO,MAAM,8BAA8B,kCAAkC,CAAA;AAE7E,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"create-fleet.d.ts","sourceRoot":"","sources":["../../src/fleet/create-fleet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,OAAO,CAAA;AAC/C,eAAO,MAAM,8BAA8B,kCAAkC,CAAA;AAE7E,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,uBAAuB,CAAA;IAGvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6BAA6B,CAAC,EAAE,MAAM,CAAA;IACtC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CACxB;AAED,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIrF;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,SAAS,CAWpB;AAED,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,EAAE,IAAI,GAAE,eAAoB,0CAsBvF"}
|
|
@@ -24,6 +24,7 @@ export function createFleet(options = {}, deps = {}) {
|
|
|
24
24
|
if (backend === 'relay') {
|
|
25
25
|
return new RelayFleetClient({
|
|
26
26
|
workspaceKey: deps.workspaceKey,
|
|
27
|
+
agentName: options.relayAgentName,
|
|
27
28
|
env: deps.env,
|
|
28
29
|
log: deps.logger ? (message) => deps.logger?.info?.(message) : undefined,
|
|
29
30
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-fleet.js","sourceRoot":"","sources":["../../src/fleet/create-fleet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgC,MAAM,yBAAyB,CAAA;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAKvD,MAAM,CAAC,MAAM,8BAA8B,GAAG,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"create-fleet.js","sourceRoot":"","sources":["../../src/fleet/create-fleet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgC,MAAM,yBAAyB,CAAA;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAKvD,MAAM,CAAC,MAAM,8BAA8B,GAAG,+BAA+B,CAAA;AA2B7E,MAAM,UAAU,kCAAkC,CAAC,KAAc;IAC/D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IAC3E,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAChE,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AACxE,CAAC;AAED,MAAM,UAAU,oCAAoC,CAClD,QAA4B,EAC5B,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,kCAAkC,CAAC,QAAQ,CAAC,CAAA;QAC3D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAA;QACpG,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IACD,2EAA2E;IAC3E,wDAAwD;IACxD,OAAO,kCAAkC,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE,EAAE,OAAwB,EAAE;IACtF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,UAAU,CAAA;IAE7C,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,IAAI,gBAAgB,CAAC;YAC1B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,OAAO,CAAC,cAAc;YACjC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;SACzE,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,IAAI,mBAAmB,CAAC;QAC7B,MAAM,EAAE,IAAI,CAAC,aAAa;QAC1B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,6BAA6B,EAAE,oCAAoC,CAAC,IAAI,CAAC,6BAA6B,CAAC;QACvG,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -32,6 +32,10 @@ export interface RelayFleetClientOptions {
|
|
|
32
32
|
agentToken?: string;
|
|
33
33
|
/** Workspace agent identity the factory registers/rotates for itself. */
|
|
34
34
|
agentName?: string;
|
|
35
|
+
/** Recorded on the takeover audit row; set it to the real node where known. */
|
|
36
|
+
nodeId?: string;
|
|
37
|
+
/** Injectable for tests; the takeover endpoint is not on the SDK surface. */
|
|
38
|
+
fetch?: typeof globalThis.fetch;
|
|
35
39
|
/** Stable workspace action used for durable agent lifecycle reports. */
|
|
36
40
|
lifecycleActionName?: string;
|
|
37
41
|
/** Engine base URL override. Absent means the SDK default (cast.agentrelay.com). */
|
|
@@ -57,6 +61,7 @@ export interface RelayFleetClientOptions {
|
|
|
57
61
|
sleep?: (ms: number) => Promise<void>;
|
|
58
62
|
log?: (message: string) => void;
|
|
59
63
|
}
|
|
64
|
+
export declare const MAX_REGISTRATION_ATTEMPTS = 10;
|
|
60
65
|
export declare const DEFAULT_LIFECYCLE_ACTION_NAME = "factory.lifecycle";
|
|
61
66
|
/**
|
|
62
67
|
* A placement call that outlived the operation's remaining budget (#306).
|
|
@@ -85,6 +90,14 @@ export declare class RelayFleetClient implements FleetClient {
|
|
|
85
90
|
readonly placementLocality: "remote";
|
|
86
91
|
readonly durableOwnership = true;
|
|
87
92
|
readonly lifecycleActionName: string;
|
|
93
|
+
/**
|
|
94
|
+
* Workspace identity this client registers as, after defaulting.
|
|
95
|
+
*
|
|
96
|
+
* Exposed read-only so a caller (and its tests) can confirm which identity a
|
|
97
|
+
* given configuration actually resolved to. Registration reads `#agentName`
|
|
98
|
+
* directly; this changes nothing about it.
|
|
99
|
+
*/
|
|
100
|
+
get agentName(): string;
|
|
88
101
|
constructor(options?: RelayFleetClientOptions);
|
|
89
102
|
/** Agents spawned through this client that have not exited or been released. */
|
|
90
103
|
trackedAgents(): ReadonlyMap<string, TrackedAgent>;
|
|
@@ -132,5 +145,15 @@ export declare class RelayFleetClient implements FleetClient {
|
|
|
132
145
|
onAgentExit(listener: AgentExitListener): () => void;
|
|
133
146
|
dispose(): Promise<void>;
|
|
134
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Registration could not converge on a usable agent identity. Named so a
|
|
150
|
+
* six-day silent 409 loop surfaces as one actionable failure instead.
|
|
151
|
+
*/
|
|
152
|
+
export declare class FactoryAgentRegistrationError extends Error {
|
|
153
|
+
readonly agentName: string;
|
|
154
|
+
constructor(agentName: string, detail: string, options?: {
|
|
155
|
+
cause?: unknown;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
135
158
|
export {};
|
|
136
159
|
//# sourceMappingURL=relay-fleet-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-fleet-client.d.ts","sourceRoot":"","sources":["../../src/fleet/relay-fleet-client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAkB,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC1P,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,kBAAkB,CAAA;AAEzB,KAAK,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;AAChE,KAAK,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAA;AAC7F,KAAK,oBAAoB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;AAC3D,KAAK,4BAA4B,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1F,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAErE,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAA;CAC1B;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,oFAAoF;IACpF,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,eAAe,CAAA;IACrE;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,iGAAiG;IACjG,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;
|
|
1
|
+
{"version":3,"file":"relay-fleet-client.d.ts","sourceRoot":"","sources":["../../src/fleet/relay-fleet-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,18 @@ const knownCapabilities = new Set(['spawn:claude', 'spawn:codex', 'workflow:run'
|
|
|
4
4
|
const openStatuses = new Set(['pending', 'dispatched', 'invoked']);
|
|
5
5
|
const terminalStatuses = new Set(['completed', 'failed', 'denied']);
|
|
6
6
|
const DEFAULT_AGENT_NAME = 'factory';
|
|
7
|
+
// A backstop against an unbounded re-registration loop, not a tight retry
|
|
8
|
+
// budget: a name conflict already fails immediately, so this only bounds
|
|
9
|
+
// *consecutive* transient failures and is deliberately forgiving of a cold
|
|
10
|
+
// start against a slow relay.
|
|
11
|
+
export const MAX_REGISTRATION_ATTEMPTS = 10;
|
|
12
|
+
// Matches the default @agent-relay/sdk resolves when no baseUrl is configured,
|
|
13
|
+
// so a takeover always targets the host the register went to.
|
|
14
|
+
const DEFAULT_RELAY_BASE_URL = 'https://cast.agentrelay.com';
|
|
15
|
+
// Every engine status that means somebody is holding the identity. `offline` is
|
|
16
|
+
// the only one that is not here, and `unknown` is what the SDK substitutes when
|
|
17
|
+
// status is missing — so an absent status never reads as safe.
|
|
18
|
+
const LIVE_AGENT_STATUSES = new Set(['online', 'active', 'idle', 'blocked', 'waiting', 'unknown']);
|
|
7
19
|
export const DEFAULT_LIFECYCLE_ACTION_NAME = 'factory.lifecycle';
|
|
8
20
|
const DEFAULT_SPAWN_ACK_TIMEOUT_MS = 5 * 60_000;
|
|
9
21
|
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
@@ -56,6 +68,11 @@ export class RelayFleetClient {
|
|
|
56
68
|
lifecycleActionName;
|
|
57
69
|
#options;
|
|
58
70
|
#agentName;
|
|
71
|
+
// The token minted by this process's own registration. Reused across
|
|
72
|
+
// bootstrap retries so a failure *after* registration never re-registers
|
|
73
|
+
// the same name — see #registerFactoryAgent (factory#316).
|
|
74
|
+
#registeredAgentToken;
|
|
75
|
+
#registrationAttempts = 0;
|
|
59
76
|
#spawnAckTimeoutMs;
|
|
60
77
|
#pollIntervalMs;
|
|
61
78
|
#exitWatchIntervalMs;
|
|
@@ -85,6 +102,16 @@ export class RelayFleetClient {
|
|
|
85
102
|
#watchTimer;
|
|
86
103
|
#reconciling;
|
|
87
104
|
#pendingReleaseRetry;
|
|
105
|
+
/**
|
|
106
|
+
* Workspace identity this client registers as, after defaulting.
|
|
107
|
+
*
|
|
108
|
+
* Exposed read-only so a caller (and its tests) can confirm which identity a
|
|
109
|
+
* given configuration actually resolved to. Registration reads `#agentName`
|
|
110
|
+
* directly; this changes nothing about it.
|
|
111
|
+
*/
|
|
112
|
+
get agentName() {
|
|
113
|
+
return this.#agentName;
|
|
114
|
+
}
|
|
88
115
|
constructor(options = {}) {
|
|
89
116
|
this.#options = options;
|
|
90
117
|
this.#agentName = options.agentName ?? DEFAULT_AGENT_NAME;
|
|
@@ -504,8 +531,14 @@ export class RelayFleetClient {
|
|
|
504
531
|
if (!workspaceKey && !agentToken) {
|
|
505
532
|
throw new Error('RelayFleetClient requires a workspace key (rk_live_…) or agent token (at_live_…); set RELAY_WORKSPACE_KEY or RELAY_AGENT_TOKEN');
|
|
506
533
|
}
|
|
534
|
+
// Registration is not idempotent: the engine answers a repeat of a name it
|
|
535
|
+
// already holds with 409 agent_already_exists. Reusing the token this
|
|
536
|
+
// process already minted is what keeps a post-registration bootstrap
|
|
537
|
+
// failure from colliding with our own record (factory#316).
|
|
538
|
+
agentToken ??= this.#registeredAgentToken;
|
|
507
539
|
if (!agentToken) {
|
|
508
540
|
agentToken = await this.#registerFactoryAgent(workspaceKey);
|
|
541
|
+
this.#registeredAgentToken = agentToken;
|
|
509
542
|
}
|
|
510
543
|
const relay = this.#createRelay({
|
|
511
544
|
...(workspaceKey ? { workspaceKey } : {}),
|
|
@@ -515,17 +548,39 @@ export class RelayFleetClient {
|
|
|
515
548
|
this.#messaging = relay.messaging;
|
|
516
549
|
return this.#messaging;
|
|
517
550
|
}
|
|
518
|
-
//
|
|
519
|
-
//
|
|
551
|
+
// Mints this process's agent identity from the workspace key.
|
|
552
|
+
//
|
|
553
|
+
// `registerOrRotate`/`registerOrGet` used to recover from a name conflict by
|
|
554
|
+
// rotating the existing agent's token. In @relaycast/sdk 8.2.0 both became
|
|
555
|
+
// deprecated aliases for plain `register`, and the engine simultaneously
|
|
556
|
+
// closed the rotate path to workspace keys, so a conflict stopped being
|
|
557
|
+
// recoverable. Because the methods still *exist*, an optional-chain fallback
|
|
558
|
+
// never fires — the client just kept re-registering into 409 forever
|
|
559
|
+
// (factory#316). Call `register` directly, and reclaim the name by audited
|
|
560
|
+
// takeover when it is already held.
|
|
520
561
|
async #registerFactoryAgent(workspaceKey) {
|
|
562
|
+
this.#registrationAttempts += 1;
|
|
563
|
+
if (this.#registrationAttempts > MAX_REGISTRATION_ATTEMPTS) {
|
|
564
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `gave up after ${MAX_REGISTRATION_ATTEMPTS} registration attempts`);
|
|
565
|
+
}
|
|
521
566
|
const bootstrap = this.#createRelay({
|
|
522
567
|
workspaceKey,
|
|
523
568
|
...(this.#options.baseUrl ? { baseUrl: this.#options.baseUrl } : {}),
|
|
524
569
|
});
|
|
525
570
|
const agents = bootstrap.messaging.agents;
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
571
|
+
try {
|
|
572
|
+
const registration = await agents.register({ name: this.#agentName });
|
|
573
|
+
this.#registrationAttempts = 0;
|
|
574
|
+
return registration.token;
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
// Re-registering is what looped for six days. The only way out is to
|
|
578
|
+
// reclaim the record we collided with — or to fail by name.
|
|
579
|
+
if (isAgentNameConflictError(error)) {
|
|
580
|
+
return await this.#reclaimFactoryAgent(workspaceKey, agents, error);
|
|
581
|
+
}
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
529
584
|
}
|
|
530
585
|
/** The wall-clock instant one placement operation must be finished by. */
|
|
531
586
|
#operationDeadline() {
|
|
@@ -626,6 +681,114 @@ export class RelayFleetClient {
|
|
|
626
681
|
await this.#releaseLatePlacement(name, outcome.value);
|
|
627
682
|
});
|
|
628
683
|
}
|
|
684
|
+
// The name exists and we hold no token for it. `recover` cannot help: its
|
|
685
|
+
// three authorities are agent-token, origin-node and work-unit-proof, and a
|
|
686
|
+
// workspace key establishes none of them. `/takeover` is the engine's own
|
|
687
|
+
// workspace-admin escape hatch for exactly this, and it is gated by
|
|
688
|
+
// requireWorkspaceKey — the credential we already have. It is absent from the
|
|
689
|
+
// `@agent-relay/sdk` agents surface, so this calls the endpoint directly.
|
|
690
|
+
async #reclaimFactoryAgent(workspaceKey, agents, conflict) {
|
|
691
|
+
let lastError = conflict;
|
|
692
|
+
// The engine rejects a stale `expected_agent_id` with 409
|
|
693
|
+
// agent_identity_conflict; that means the record moved under us, so re-read
|
|
694
|
+
// it and try once more rather than treating it as terminal.
|
|
695
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
696
|
+
const existing = asRecord(await agents.get(this.#agentName).catch(() => undefined));
|
|
697
|
+
const agentId = readString(existing, 'id', 'agent_id', 'agentId');
|
|
698
|
+
if (!agentId) {
|
|
699
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `name is taken but the record could not be read back: ${errorMessage(lastError)}`, { cause: lastError });
|
|
700
|
+
}
|
|
701
|
+
await this.#assertAgentNotLive(agents, existing);
|
|
702
|
+
try {
|
|
703
|
+
return await this.#takeOverAgent(workspaceKey, agentId);
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
lastError = error;
|
|
707
|
+
if (isIdentityMovedError(error) && attempt === 0)
|
|
708
|
+
continue;
|
|
709
|
+
if (error instanceof FactoryAgentRegistrationError)
|
|
710
|
+
throw error;
|
|
711
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `could not reclaim the existing identity: ${errorMessage(error)}`, { cause: error });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `could not reclaim the existing identity: ${errorMessage(lastError)}`, { cause: lastError });
|
|
715
|
+
}
|
|
716
|
+
// Seizing a name from a LIVE agent would strand its credential. Our case is a
|
|
717
|
+
// collision with our own dead record, but the guard is coded rather than
|
|
718
|
+
// assumed — the situation is not a safety property.
|
|
719
|
+
async #assertAgentNotLive(agents, existing) {
|
|
720
|
+
// Allow-list, not a deny-list. The engine also has active/idle/blocked/
|
|
721
|
+
// waiting, all of which mean someone is holding this identity, and
|
|
722
|
+
// `unknown` is what the SDK substitutes when status is missing. Only a
|
|
723
|
+
// record that says offline gets as far as the presence check.
|
|
724
|
+
const status = readString(existing, 'status');
|
|
725
|
+
if (status !== 'offline') {
|
|
726
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `refusing to take over agent in status "${status ?? 'unknown'}"; another factory may still hold this identity`);
|
|
727
|
+
}
|
|
728
|
+
// Presence is the canonical liveness signal, and the agent record can be
|
|
729
|
+
// stale. Everything below fails CLOSED: only a presence row we actually
|
|
730
|
+
// read, for this agent, carrying a status we recognise as not-live, may
|
|
731
|
+
// authorise a seizure. A request that failed, a body we cannot parse, or a
|
|
732
|
+
// missing row is absence of evidence — not evidence of death.
|
|
733
|
+
//
|
|
734
|
+
// This asymmetry is deliberate. Refusing to boot costs us a recoverable
|
|
735
|
+
// outage of our own; seizing a live agent's credential strands another
|
|
736
|
+
// running process and takes the very token it would need to recover.
|
|
737
|
+
let presence;
|
|
738
|
+
try {
|
|
739
|
+
presence = await agents.presence();
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `could not read presence to confirm the agent is not live: ${errorMessage(error)}`, { cause: error });
|
|
743
|
+
}
|
|
744
|
+
if (!Array.isArray(presence)) {
|
|
745
|
+
throw new FactoryAgentRegistrationError(this.#agentName, 'presence did not return a list, so the agent cannot be confirmed offline');
|
|
746
|
+
}
|
|
747
|
+
const entry = presence
|
|
748
|
+
.map((row) => asRecord(row))
|
|
749
|
+
.find((row) => readString(row, 'agentName', 'agent_name', 'name') === this.#agentName);
|
|
750
|
+
if (!entry) {
|
|
751
|
+
throw new FactoryAgentRegistrationError(this.#agentName, 'presence does not list this agent, so it cannot be confirmed offline');
|
|
752
|
+
}
|
|
753
|
+
const seenStatus = readString(entry, 'status');
|
|
754
|
+
if (seenStatus === undefined) {
|
|
755
|
+
throw new FactoryAgentRegistrationError(this.#agentName, 'presence reported no status for this agent, so it cannot be confirmed offline');
|
|
756
|
+
}
|
|
757
|
+
if (LIVE_AGENT_STATUSES.has(seenStatus)) {
|
|
758
|
+
throw new FactoryAgentRegistrationError(this.#agentName, `presence reports this agent as "${seenStatus}"; another factory may still hold this identity`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async #takeOverAgent(workspaceKey, expectedAgentId) {
|
|
762
|
+
const base = (this.#options.baseUrl ?? DEFAULT_RELAY_BASE_URL).replace(/\/+$/, '');
|
|
763
|
+
const url = `${base}/v1/agents/${encodeURIComponent(this.#agentName)}/takeover`;
|
|
764
|
+
const doFetch = this.#options.fetch ?? globalThis.fetch;
|
|
765
|
+
// Every field lands in the audit row, so make them all say something.
|
|
766
|
+
const response = await doFetch(url, {
|
|
767
|
+
method: 'POST',
|
|
768
|
+
headers: { authorization: `Bearer ${workspaceKey}`, 'content-type': 'application/json' },
|
|
769
|
+
body: JSON.stringify({
|
|
770
|
+
expected_agent_id: expectedAgentId,
|
|
771
|
+
actor: `factory:${this.#agentName}`,
|
|
772
|
+
reason: 'factory bootstrap reclaiming its own workspace identity after a registration conflict (factory#316)',
|
|
773
|
+
session_ref: `factory-bootstrap-${this.#now()}`,
|
|
774
|
+
node_id: this.#options.nodeId ?? 'factory-cloud-container',
|
|
775
|
+
}),
|
|
776
|
+
});
|
|
777
|
+
const payload = asRecord(await response.json().catch(() => undefined));
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
const failure = asRecord(payload?.error);
|
|
780
|
+
const error = new Error(readString(failure, 'message') ?? `takeover failed with HTTP ${response.status}`);
|
|
781
|
+
error.code = readString(failure, 'code');
|
|
782
|
+
error.statusCode = response.status;
|
|
783
|
+
throw error;
|
|
784
|
+
}
|
|
785
|
+
const data = asRecord(payload?.data) ?? payload;
|
|
786
|
+
const token = readString(data, 'token');
|
|
787
|
+
if (!token)
|
|
788
|
+
throw new Error('takeover succeeded but returned no agent token');
|
|
789
|
+
this.#registrationAttempts = 0;
|
|
790
|
+
return token;
|
|
791
|
+
}
|
|
629
792
|
/**
|
|
630
793
|
* Poll an invocation to a terminal status inside `deadlineAtMs`.
|
|
631
794
|
*
|
|
@@ -1077,6 +1240,44 @@ function previewReference(value, placementNode) {
|
|
|
1077
1240
|
...(node ? { node } : {}),
|
|
1078
1241
|
};
|
|
1079
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Registration could not converge on a usable agent identity. Named so a
|
|
1245
|
+
* six-day silent 409 loop surfaces as one actionable failure instead.
|
|
1246
|
+
*/
|
|
1247
|
+
export class FactoryAgentRegistrationError extends Error {
|
|
1248
|
+
agentName;
|
|
1249
|
+
constructor(agentName, detail, options) {
|
|
1250
|
+
super(`Factory agent "${agentName}" could not register with the relay workspace: ${detail}`);
|
|
1251
|
+
this.name = 'FactoryAgentRegistrationError';
|
|
1252
|
+
this.agentName = agentName;
|
|
1253
|
+
if (options && 'cause' in options) {
|
|
1254
|
+
;
|
|
1255
|
+
this.cause = options.cause;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// The SDK reports the engine's 409 agent_already_exists as a RelayError with
|
|
1260
|
+
// `code: 'name_conflict'` and `rawCode: 'agent_already_exists'`. Match on the
|
|
1261
|
+
// structured fields first and keep the message probe for stubs and older SDKs.
|
|
1262
|
+
function isAgentNameConflictError(error) {
|
|
1263
|
+
const record = asRecord(error);
|
|
1264
|
+
if (record) {
|
|
1265
|
+
if (record.rawCode === 'agent_already_exists' || record.code === 'name_conflict')
|
|
1266
|
+
return true;
|
|
1267
|
+
if (record.statusCode === 409 || record.status === 409)
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
return /already exists|name[ _]conflict|agent_already_exists/i.test(errorMessage(error));
|
|
1271
|
+
}
|
|
1272
|
+
// 409 agent_identity_conflict means the record moved between our read and the
|
|
1273
|
+
// takeover — retryable once, unlike agent_already_exists which is the conflict
|
|
1274
|
+
// we are already handling.
|
|
1275
|
+
function isIdentityMovedError(error) {
|
|
1276
|
+
const record = asRecord(error);
|
|
1277
|
+
if (record?.code === 'agent_identity_conflict')
|
|
1278
|
+
return true;
|
|
1279
|
+
return /agent_identity_conflict|no longer has expected id/i.test(errorMessage(error));
|
|
1280
|
+
}
|
|
1080
1281
|
function isUnknownRecipientError(error) {
|
|
1081
1282
|
const message = errorMessage(error);
|
|
1082
1283
|
return /not[ _]found|unknown (agent|recipient)|no such (agent|recipient)|unregistered/i.test(message);
|