@agent-relay/factory 0.1.60 → 0.1.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -16
- package/dist/cli/exit-codes.d.ts +81 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +122 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/fleet.d.ts +23 -2
- package/dist/cli/fleet.d.ts.map +1 -1
- package/dist/cli/fleet.js +201 -42
- package/dist/cli/fleet.js.map +1 -1
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/schema.d.ts +175 -52
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +24 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/dispatch/templates.d.ts +7 -0
- package/dist/dispatch/templates.d.ts.map +1 -1
- package/dist/dispatch/templates.js +64 -12
- package/dist/dispatch/templates.js.map +1 -1
- package/dist/dispatch/work-unit-identity.d.ts +19 -0
- package/dist/dispatch/work-unit-identity.d.ts.map +1 -0
- package/dist/dispatch/work-unit-identity.js +40 -0
- package/dist/dispatch/work-unit-identity.js.map +1 -0
- package/dist/environments/stack-descriptor.d.ts +3 -3
- package/dist/fleet/control-plane-circuit.d.ts +57 -0
- package/dist/fleet/control-plane-circuit.d.ts.map +1 -0
- package/dist/fleet/control-plane-circuit.js +219 -0
- package/dist/fleet/control-plane-circuit.js.map +1 -0
- package/dist/fleet/internal-fleet-client.d.ts +2 -1
- package/dist/fleet/internal-fleet-client.d.ts.map +1 -1
- package/dist/fleet/internal-fleet-client.js +11 -7
- package/dist/fleet/internal-fleet-client.js.map +1 -1
- package/dist/fleet/relay-fleet-client.d.ts +3 -0
- package/dist/fleet/relay-fleet-client.d.ts.map +1 -1
- package/dist/fleet/relay-fleet-client.js +97 -18
- package/dist/fleet/relay-fleet-client.js.map +1 -1
- package/dist/github/writeback-paths.d.ts +7 -0
- package/dist/github/writeback-paths.d.ts.map +1 -0
- package/dist/github/writeback-paths.js +28 -0
- package/dist/github/writeback-paths.js.map +1 -0
- package/dist/hosted/orchestrator.d.ts.map +1 -1
- package/dist/hosted/orchestrator.js +2 -0
- package/dist/hosted/orchestrator.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/intake/index.d.ts +2 -1
- package/dist/intake/index.d.ts.map +1 -1
- package/dist/intake/index.js +2 -1
- package/dist/intake/index.js.map +1 -1
- package/dist/intake/notion-manifest.d.ts +48 -0
- package/dist/intake/notion-manifest.d.ts.map +1 -0
- package/dist/intake/notion-manifest.js +258 -0
- package/dist/intake/notion-manifest.js.map +1 -0
- package/dist/intake/notion-relay-claim.d.ts +5 -4
- package/dist/intake/notion-relay-claim.d.ts.map +1 -1
- package/dist/intake/notion-relay-claim.js +48 -7
- package/dist/intake/notion-relay-claim.js.map +1 -1
- package/dist/intake/notion.d.ts +6 -3
- package/dist/intake/notion.d.ts.map +1 -1
- package/dist/intake/notion.js +63 -13
- package/dist/intake/notion.js.map +1 -1
- package/dist/issue-key-match.d.ts +6 -0
- package/dist/issue-key-match.d.ts.map +1 -1
- package/dist/issue-key-match.js +6 -1
- package/dist/issue-key-match.js.map +1 -1
- package/dist/mount/local-mount-preflight.d.ts.map +1 -1
- package/dist/mount/local-mount-preflight.js +6 -2
- package/dist/mount/local-mount-preflight.js.map +1 -1
- package/dist/mount/mount-auth-error.d.ts +16 -3
- package/dist/mount/mount-auth-error.d.ts.map +1 -1
- package/dist/mount/mount-auth-error.js +34 -7
- package/dist/mount/mount-auth-error.js.map +1 -1
- package/dist/mount/relayfile-cloud-mount-client.d.ts +14 -1
- package/dist/mount/relayfile-cloud-mount-client.d.ts.map +1 -1
- package/dist/mount/relayfile-cloud-mount-client.js +107 -6
- package/dist/mount/relayfile-cloud-mount-client.js.map +1 -1
- package/dist/mount/relayfile-github-connection-write.d.ts +24 -2
- package/dist/mount/relayfile-github-connection-write.d.ts.map +1 -1
- package/dist/mount/relayfile-github-connection-write.js +90 -1
- package/dist/mount/relayfile-github-connection-write.js.map +1 -1
- package/dist/node/factory-node.d.ts.map +1 -1
- package/dist/node/factory-node.js +18 -4
- package/dist/node/factory-node.js.map +1 -1
- package/dist/observability/events.d.ts +52 -52
- package/dist/orchestrator/batch-tracker.d.ts +30 -0
- package/dist/orchestrator/batch-tracker.d.ts.map +1 -1
- package/dist/orchestrator/batch-tracker.js +50 -2
- package/dist/orchestrator/batch-tracker.js.map +1 -1
- package/dist/orchestrator/factory.d.ts +36 -2
- package/dist/orchestrator/factory.d.ts.map +1 -1
- package/dist/orchestrator/factory.js +863 -98
- package/dist/orchestrator/factory.js.map +1 -1
- package/dist/orchestrator/index.d.ts +2 -2
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/index.js +2 -2
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/reaper.d.ts +16 -2
- package/dist/orchestrator/reaper.d.ts.map +1 -1
- package/dist/orchestrator/reaper.js +79 -5
- package/dist/orchestrator/reaper.js.map +1 -1
- package/dist/ports/fleet.d.ts +6 -0
- package/dist/ports/fleet.d.ts.map +1 -1
- package/dist/ports/index.d.ts +1 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/mount.d.ts +39 -0
- package/dist/ports/mount.d.ts.map +1 -1
- package/dist/ports/state.d.ts +35 -6
- package/dist/ports/state.d.ts.map +1 -1
- package/dist/state/document-store.d.ts +32 -0
- package/dist/state/document-store.d.ts.map +1 -0
- package/dist/state/document-store.js +7 -0
- package/dist/state/document-store.js.map +1 -0
- package/dist/state/file-state-store.d.ts +20 -3
- package/dist/state/file-state-store.d.ts.map +1 -1
- package/dist/state/file-state-store.js +89 -245
- package/dist/state/file-state-store.js.map +1 -1
- package/dist/state/in-memory-state-store.d.ts +2 -1
- package/dist/state/in-memory-state-store.d.ts.map +1 -1
- package/dist/state/in-memory-state-store.js +12 -3
- package/dist/state/in-memory-state-store.js.map +1 -1
- package/dist/state/watch-state-document.d.ts +5 -0
- package/dist/state/watch-state-document.d.ts.map +1 -0
- package/dist/state/watch-state-document.js +444 -0
- package/dist/state/watch-state-document.js.map +1 -0
- package/dist/testing/deadline.d.ts +11 -0
- package/dist/testing/deadline.d.ts.map +1 -0
- package/dist/testing/deadline.js +23 -0
- package/dist/testing/deadline.js.map +1 -0
- package/dist/testing/fakes.d.ts +2 -0
- package/dist/testing/fakes.d.ts.map +1 -1
- package/dist/testing/fakes.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/triage/heuristic.d.ts +5 -0
- package/dist/triage/heuristic.d.ts.map +1 -1
- package/dist/triage/heuristic.js +42 -3
- package/dist/triage/heuristic.js.map +1 -1
- package/dist/triage/index.d.ts +1 -1
- package/dist/triage/index.d.ts.map +1 -1
- package/dist/triage/index.js +1 -1
- package/dist/triage/index.js.map +1 -1
- package/dist/triage/schema.d.ts +73 -55
- package/dist/triage/schema.d.ts.map +1 -1
- package/dist/triage/schema.js +2 -1
- package/dist/triage/schema.js.map +1 -1
- package/dist/types.d.ts +51 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/writeback/github.d.ts +22 -7
- package/dist/writeback/github.d.ts.map +1 -1
- package/dist/writeback/github.js +86 -16
- package/dist/writeback/github.js.map +1 -1
- package/dist/writeback/index.d.ts +1 -1
- package/dist/writeback/index.d.ts.map +1 -1
- package/dist/writeback/index.js +1 -1
- package/dist/writeback/index.js.map +1 -1
- package/package.json +13 -8
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BabysitterGenerationRecord, BabysitterSessionState, ConversationSessionState, DiscoverySweepState, DispatchLifecycle, GithubIssueCommentWatchState, WaitingClarification } from '../ports/state.js';
|
|
2
|
+
export type PersistedWorkspaceState = {
|
|
3
|
+
githubIssueCommentWatches: Record<string, GithubIssueCommentWatchState>;
|
|
4
|
+
waitingClarifications: Record<string, WaitingClarification>;
|
|
5
|
+
babysitterSessions: Record<string, BabysitterSessionState>;
|
|
6
|
+
babysitterGenerations: Record<string, BabysitterGenerationRecord>;
|
|
7
|
+
conversationSessions: Record<string, ConversationSessionState>;
|
|
8
|
+
dispatchLifecycles: Record<string, DispatchLifecycle>;
|
|
9
|
+
discoverySweep: DiscoverySweepState;
|
|
10
|
+
};
|
|
11
|
+
export type WatchStateDocument = {
|
|
12
|
+
version: 3;
|
|
13
|
+
workspaces: Record<string, PersistedWorkspaceState>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Persistence seam for the document-backed StateStore behavior.
|
|
17
|
+
*
|
|
18
|
+
* Implementations must make each runMutation callback serializable with every
|
|
19
|
+
* other writer. A local file implementation can hold a filesystem lock; a
|
|
20
|
+
* remote implementation can use revisioned compare-and-set and retry the pure
|
|
21
|
+
* callback after a conflict.
|
|
22
|
+
*/
|
|
23
|
+
export interface WatchStateDocumentStore {
|
|
24
|
+
read(): Promise<WatchStateDocument>;
|
|
25
|
+
write(document: WatchStateDocument): Promise<void>;
|
|
26
|
+
runMutation<T>(operation: () => Promise<T>): Promise<T>;
|
|
27
|
+
assertReady(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare class WatchStateDocumentConflictError extends Error {
|
|
30
|
+
constructor(message?: string);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=document-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-store.d.ts","sourceRoot":"","sources":["../../src/state/document-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,4BAA4B,EAC5B,oBAAoB,EACrB,MAAM,gBAAgB,CAAA;AAEvB,MAAM,MAAM,uBAAuB,GAAG;IACpC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAA;IACvE,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;IAC3D,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAA;IAC1D,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAA;IACjE,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAA;IAC9D,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IACrD,cAAc,EAAE,mBAAmB,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,CAAC,CAAA;IACV,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;CACpD,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACnC,KAAK,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACvD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,qBAAa,+BAAgC,SAAQ,KAAK;gBAC5C,OAAO,SAAgD;CAIpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-store.js","sourceRoot":"","sources":["../../src/state/document-store.ts"],"names":[],"mappings":"AAwCA,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YAAY,OAAO,GAAG,6CAA6C;QACjE,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAA;IAC/C,CAAC;CACF"}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import type { BabysitterGenerationRecord, BabysitterSessionState, ClarificationReply, DispatchLifecycle, DispatchLifecycleClaim, GithubIssueCommentWatchState, ConversationMessage, ConversationSessionState, DiscoveryCheckpoint, DiscoverySweepClaim, WaitingClarification } from '../ports/state.js';
|
|
1
|
+
import type { BabysitterGenerationRecord, BabysitterSessionState, ClarificationReply, DispatchLifecycle, DispatchLifecycleClaim, GithubIssueCommentWatchState, ConversationMessage, ConversationSessionState, DiscoveryCheckpoint, DiscoverySweepClaim, DiscoverySweepRenewal, WaitingClarification } from '../ports/state.js';
|
|
2
2
|
import { InMemoryStateStore, type InMemoryStateStoreOptions } from './in-memory-state-store.js';
|
|
3
|
+
import type { WatchStateDocumentStore } from './document-store.js';
|
|
3
4
|
export type FileStateStoreOptions = InMemoryStateStoreOptions & {
|
|
4
5
|
watchStatePath: string;
|
|
6
|
+
/** Injectable for deterministic stale-process lease recovery tests. */
|
|
7
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
8
|
+
};
|
|
9
|
+
export type DocumentStateStoreOptions = InMemoryStateStoreOptions & {
|
|
10
|
+
/** Optional host-defined identifier surfaced by embedded CLI status. */
|
|
11
|
+
backend?: string;
|
|
12
|
+
documentStore: WatchStateDocumentStore;
|
|
13
|
+
/** Injectable for deterministic stale-process lease recovery tests. */
|
|
14
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
5
15
|
};
|
|
6
16
|
export declare const githubWatchStatePath: (registryPath: string) => string;
|
|
7
17
|
/**
|
|
@@ -12,11 +22,14 @@ export declare const githubWatchStatePath: (registryPath: string) => string;
|
|
|
12
22
|
* Mutations reload under an advisory lock so independent processes merge
|
|
13
23
|
* updates instead of publishing divergent cached documents.
|
|
14
24
|
*/
|
|
15
|
-
export declare class
|
|
25
|
+
export declare class DocumentStateStore extends InMemoryStateStore {
|
|
16
26
|
#private;
|
|
17
|
-
|
|
27
|
+
readonly backend?: string;
|
|
28
|
+
constructor(options: DocumentStateStoreOptions);
|
|
29
|
+
assertReady(): Promise<void>;
|
|
18
30
|
claimDiscoverySweep(workspaceId: string, owner: string, nowMs: number, leaseMs: number): Promise<DiscoverySweepClaim>;
|
|
19
31
|
renewDiscoverySweep(workspaceId: string, owner: string, epoch: number, nowMs: number, leaseMs: number): Promise<boolean>;
|
|
32
|
+
renewDiscoverySweepWithDetails(workspaceId: string, owner: string, epoch: number, nowMs: number, leaseMs: number): Promise<DiscoverySweepRenewal>;
|
|
20
33
|
completeDiscoverySweep(workspaceId: string, owner: string, epoch: number, checkpoint?: DiscoveryCheckpoint): Promise<boolean>;
|
|
21
34
|
deferDiscoverySweep(workspaceId: string, owner: string, epoch: number, backoffUntilMs: number, consecutiveOverloads: number): Promise<boolean>;
|
|
22
35
|
releaseDiscoverySweep(workspaceId: string, owner: string, epoch: number): Promise<void>;
|
|
@@ -77,4 +90,8 @@ export declare class FileStateStore extends InMemoryStateStore {
|
|
|
77
90
|
clearConversationSession(workspaceId: string, conversationId: string): Promise<void>;
|
|
78
91
|
rebindConversationSession(workspaceId: string, conversationId: string, agent: ConversationSessionState['agent']): Promise<boolean>;
|
|
79
92
|
}
|
|
93
|
+
/** Node/file-backed state with the exact persistence and locking semantics used before the document seam. */
|
|
94
|
+
export declare class FileStateStore extends DocumentStateStore {
|
|
95
|
+
constructor(options: FileStateStoreOptions);
|
|
96
|
+
}
|
|
80
97
|
//# sourceMappingURL=file-state-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-state-store.d.ts","sourceRoot":"","sources":["../../src/state/file-state-store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,4BAA4B,EAC5B,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EAEnB,oBAAoB,EACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"file-state-store.d.ts","sourceRoot":"","sources":["../../src/state/file-state-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import lockfile from 'proper-lockfile';
|
|
|
5
5
|
import { githubRepositoriesMatch } from '../github/repo-identity.js';
|
|
6
6
|
import { InMemoryStateStore } from './in-memory-state-store.js';
|
|
7
7
|
import { matchingGithubLifecycleEntry } from './github-lifecycle-identity.js';
|
|
8
|
+
import { emptyDiscoverySweepState, parseWatchStateDocument } from './watch-state-document.js';
|
|
8
9
|
export const githubWatchStatePath = (registryPath) => join(dirname(registryPath), 'github-issue-comment-watches.json');
|
|
9
10
|
// proper-lockfile refreshes a live writer's lease at half this interval. If a
|
|
10
11
|
// process crashes, the next writer can reclaim its lock after this TTL. The
|
|
@@ -20,14 +21,21 @@ const WATCH_STATE_LOCK_STALE_MS = 60_000;
|
|
|
20
21
|
* Mutations reload under an advisory lock so independent processes merge
|
|
21
22
|
* updates instead of publishing divergent cached documents.
|
|
22
23
|
*/
|
|
23
|
-
export class
|
|
24
|
-
|
|
24
|
+
export class DocumentStateStore extends InMemoryStateStore {
|
|
25
|
+
backend;
|
|
26
|
+
#documentStore;
|
|
25
27
|
#batchSize;
|
|
28
|
+
#isProcessAlive;
|
|
26
29
|
#operation = Promise.resolve();
|
|
27
30
|
constructor(options) {
|
|
28
31
|
super(options);
|
|
29
|
-
this
|
|
32
|
+
this.backend = options.backend;
|
|
33
|
+
this.#documentStore = options.documentStore;
|
|
30
34
|
this.#batchSize = options.batchSize;
|
|
35
|
+
this.#isProcessAlive = options.isProcessAlive ?? processIsAlive;
|
|
36
|
+
}
|
|
37
|
+
async assertReady() {
|
|
38
|
+
await this.#exclusive(async () => this.#documentStore.assertReady());
|
|
31
39
|
}
|
|
32
40
|
async claimDiscoverySweep(workspaceId, owner, nowMs, leaseMs) {
|
|
33
41
|
return await this.#exclusive(async () => this.#withMutationLock(async () => {
|
|
@@ -37,7 +45,12 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
37
45
|
if (state.backoffUntilMs > nowMs) {
|
|
38
46
|
return { acquired: false, reason: 'backoff', state: cloneDiscoverySweepState(state) };
|
|
39
47
|
}
|
|
40
|
-
|
|
48
|
+
const reclaimedLease = state.lease &&
|
|
49
|
+
state.lease.leaseUntilMs > nowMs &&
|
|
50
|
+
discoveryLeaseOwnerIsOrphaned(state.lease.owner, owner, this.#isProcessAlive)
|
|
51
|
+
? { ...state.lease }
|
|
52
|
+
: undefined;
|
|
53
|
+
if (state.lease && state.lease.leaseUntilMs > nowMs && !reclaimedLease) {
|
|
41
54
|
return { acquired: false, reason: 'in-flight', state: cloneDiscoverySweepState(state) };
|
|
42
55
|
}
|
|
43
56
|
const epoch = state.lastEpoch + 1;
|
|
@@ -48,18 +61,28 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
48
61
|
acquired: true,
|
|
49
62
|
state: cloneDiscoverySweepState(state),
|
|
50
63
|
lease: { ...state.lease },
|
|
64
|
+
...(reclaimedLease ? { reclaimedLease } : {}),
|
|
51
65
|
};
|
|
52
66
|
}));
|
|
53
67
|
}
|
|
54
68
|
async renewDiscoverySweep(workspaceId, owner, epoch, nowMs, leaseMs) {
|
|
69
|
+
return (await this.renewDiscoverySweepWithDetails(workspaceId, owner, epoch, nowMs, leaseMs)).renewed;
|
|
70
|
+
}
|
|
71
|
+
async renewDiscoverySweepWithDetails(workspaceId, owner, epoch, nowMs, leaseMs) {
|
|
55
72
|
return await this.#exclusive(async () => this.#withMutationLock(async () => {
|
|
56
73
|
const document = await this.#loadFromDisk();
|
|
57
74
|
const state = document.workspaces[workspaceId]?.discoverySweep;
|
|
58
|
-
if (!state
|
|
59
|
-
return false;
|
|
75
|
+
if (!state?.lease)
|
|
76
|
+
return { renewed: false, reason: 'missing' };
|
|
77
|
+
if (!discoveryLeaseMatches(state, owner, epoch)) {
|
|
78
|
+
return { renewed: false, reason: 'contended', observedLease: { ...state.lease } };
|
|
79
|
+
}
|
|
80
|
+
if (state.lease.leaseUntilMs <= nowMs) {
|
|
81
|
+
return { renewed: false, reason: 'expired', observedLease: { ...state.lease } };
|
|
82
|
+
}
|
|
60
83
|
state.lease.leaseUntilMs = nowMs + leaseMs;
|
|
61
84
|
await this.#persist(document);
|
|
62
|
-
return true;
|
|
85
|
+
return { renewed: true, lease: { ...state.lease } };
|
|
63
86
|
}));
|
|
64
87
|
}
|
|
65
88
|
async completeDiscoverySweep(workspaceId, owner, epoch, checkpoint) {
|
|
@@ -746,9 +769,40 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
746
769
|
}));
|
|
747
770
|
}
|
|
748
771
|
async #loadFromDisk() {
|
|
772
|
+
return await this.#documentStore.read();
|
|
773
|
+
}
|
|
774
|
+
async #persist(document) {
|
|
775
|
+
await this.#documentStore.write(document);
|
|
776
|
+
}
|
|
777
|
+
async #withMutationLock(operation) {
|
|
778
|
+
return await this.#documentStore.runMutation(operation);
|
|
779
|
+
}
|
|
780
|
+
async #exclusive(operation) {
|
|
781
|
+
const result = this.#operation.then(operation, operation);
|
|
782
|
+
this.#operation = result.then(() => undefined, () => undefined);
|
|
783
|
+
return await result;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/** Node/file-backed state with the exact persistence and locking semantics used before the document seam. */
|
|
787
|
+
export class FileStateStore extends DocumentStateStore {
|
|
788
|
+
constructor(options) {
|
|
789
|
+
super({
|
|
790
|
+
batchSize: options.batchSize,
|
|
791
|
+
agentQuestionDedupeLimit: options.agentQuestionDedupeLimit,
|
|
792
|
+
isProcessAlive: options.isProcessAlive,
|
|
793
|
+
documentStore: new FileWatchStateDocumentStore(options.watchStatePath),
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
class FileWatchStateDocumentStore {
|
|
798
|
+
#watchStatePath;
|
|
799
|
+
constructor(watchStatePath) {
|
|
800
|
+
this.#watchStatePath = watchStatePath;
|
|
801
|
+
}
|
|
802
|
+
async read() {
|
|
749
803
|
try {
|
|
750
804
|
const parsed = JSON.parse(await readFile(this.#watchStatePath, 'utf8'));
|
|
751
|
-
return
|
|
805
|
+
return parseWatchStateDocument(parsed);
|
|
752
806
|
}
|
|
753
807
|
catch (error) {
|
|
754
808
|
if (!isMissingFileError(error))
|
|
@@ -756,7 +810,7 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
756
810
|
return { version: 3, workspaces: {} };
|
|
757
811
|
}
|
|
758
812
|
}
|
|
759
|
-
async
|
|
813
|
+
async write(document) {
|
|
760
814
|
const temporaryPath = `${this.#watchStatePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
761
815
|
try {
|
|
762
816
|
const handle = await open(temporaryPath, 'wx', 0o600);
|
|
@@ -774,7 +828,7 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
774
828
|
await rm(temporaryPath, { force: true });
|
|
775
829
|
}
|
|
776
830
|
}
|
|
777
|
-
async
|
|
831
|
+
async runMutation(operation) {
|
|
778
832
|
await mkdir(dirname(this.#watchStatePath), { recursive: true });
|
|
779
833
|
const release = await lockfile.lock(this.#watchStatePath, {
|
|
780
834
|
realpath: false,
|
|
@@ -795,92 +849,10 @@ export class FileStateStore extends InMemoryStateStore {
|
|
|
795
849
|
await release();
|
|
796
850
|
}
|
|
797
851
|
}
|
|
798
|
-
async
|
|
799
|
-
|
|
800
|
-
this.#operation = result.then(() => undefined, () => undefined);
|
|
801
|
-
return await result;
|
|
852
|
+
async assertReady() {
|
|
853
|
+
await this.read();
|
|
802
854
|
}
|
|
803
855
|
}
|
|
804
|
-
const parseDocument = (value) => {
|
|
805
|
-
if (!isRecord(value) || !isRecord(value.workspaces)) {
|
|
806
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
807
|
-
}
|
|
808
|
-
if (value.version === 3) {
|
|
809
|
-
const workspaces = {};
|
|
810
|
-
for (const [workspaceId, rawWorkspace] of Object.entries(value.workspaces)) {
|
|
811
|
-
if (!isRecord(rawWorkspace))
|
|
812
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
813
|
-
const watches = rawWorkspace.githubIssueCommentWatches;
|
|
814
|
-
const clarifications = rawWorkspace.waitingClarifications;
|
|
815
|
-
const babysitters = rawWorkspace.babysitterSessions;
|
|
816
|
-
const generations = rawWorkspace.babysitterGenerations;
|
|
817
|
-
const conversations = rawWorkspace.conversationSessions;
|
|
818
|
-
const lifecycles = rawWorkspace.dispatchLifecycles;
|
|
819
|
-
const discoverySweep = rawWorkspace.discoverySweep;
|
|
820
|
-
if (!isRecord(watches) ||
|
|
821
|
-
!isRecord(clarifications) ||
|
|
822
|
-
(babysitters !== undefined && !isRecord(babysitters)) ||
|
|
823
|
-
(generations !== undefined && !isRecord(generations)) ||
|
|
824
|
-
(conversations !== undefined && !isRecord(conversations)) ||
|
|
825
|
-
(lifecycles !== undefined && !isRecord(lifecycles)) ||
|
|
826
|
-
(discoverySweep !== undefined && !isRecord(discoverySweep))) {
|
|
827
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
828
|
-
}
|
|
829
|
-
workspaces[workspaceId] = {
|
|
830
|
-
githubIssueCommentWatches: watches,
|
|
831
|
-
waitingClarifications: clarifications,
|
|
832
|
-
babysitterSessions: parseBabysitterSessions(babysitters ?? {}),
|
|
833
|
-
babysitterGenerations: parseBabysitterGenerations(generations ?? {}),
|
|
834
|
-
conversationSessions: parseConversationSessions(conversations ?? {}),
|
|
835
|
-
dispatchLifecycles: (lifecycles ?? {}),
|
|
836
|
-
discoverySweep: parseDiscoverySweepState(discoverySweep),
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
return { version: 3, workspaces };
|
|
840
|
-
}
|
|
841
|
-
if (value.version === 2) {
|
|
842
|
-
const workspaces = {};
|
|
843
|
-
for (const [workspaceId, rawWorkspace] of Object.entries(value.workspaces)) {
|
|
844
|
-
if (!isRecord(rawWorkspace))
|
|
845
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
846
|
-
const watches = rawWorkspace.githubIssueCommentWatches;
|
|
847
|
-
const clarifications = rawWorkspace.waitingClarifications;
|
|
848
|
-
const babysitters = rawWorkspace.babysitterSessions;
|
|
849
|
-
if (!isRecord(watches) || !isRecord(clarifications) || (babysitters !== undefined && !isRecord(babysitters))) {
|
|
850
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
851
|
-
}
|
|
852
|
-
workspaces[workspaceId] = {
|
|
853
|
-
githubIssueCommentWatches: watches,
|
|
854
|
-
waitingClarifications: clarifications,
|
|
855
|
-
babysitterSessions: parseBabysitterSessions(babysitters ?? {}),
|
|
856
|
-
babysitterGenerations: {},
|
|
857
|
-
conversationSessions: {},
|
|
858
|
-
dispatchLifecycles: {},
|
|
859
|
-
discoverySweep: emptyDiscoverySweepState(),
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
return { version: 3, workspaces };
|
|
863
|
-
}
|
|
864
|
-
if (value.version === 1) {
|
|
865
|
-
const workspaces = {};
|
|
866
|
-
for (const [workspaceId, watches] of Object.entries(value.workspaces)) {
|
|
867
|
-
if (!isRecord(watches)) {
|
|
868
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
869
|
-
}
|
|
870
|
-
workspaces[workspaceId] = {
|
|
871
|
-
githubIssueCommentWatches: watches,
|
|
872
|
-
waitingClarifications: {},
|
|
873
|
-
babysitterSessions: {},
|
|
874
|
-
babysitterGenerations: {},
|
|
875
|
-
conversationSessions: {},
|
|
876
|
-
dispatchLifecycles: {},
|
|
877
|
-
discoverySweep: emptyDiscoverySweepState(),
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
return { version: 3, workspaces };
|
|
881
|
-
}
|
|
882
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
883
|
-
};
|
|
884
856
|
const cloneWatch = (watch) => structuredClone(watch);
|
|
885
857
|
const cloneClarification = (record) => structuredClone(record);
|
|
886
858
|
const cloneBabysitterSession = (session) => structuredClone(session);
|
|
@@ -889,30 +861,29 @@ const cloneConversationSession = (session) => structuredClone(session);
|
|
|
889
861
|
const cloneDiscoveryCheckpoint = (checkpoint) => structuredClone(checkpoint);
|
|
890
862
|
const cloneDiscoverySweepState = (state) => structuredClone(state);
|
|
891
863
|
const discoveryLeaseMatches = (state, owner, epoch) => state.lease?.owner === owner && state.lease.epoch === epoch;
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
return structuredClone({ ...value, lastEpoch });
|
|
864
|
+
const discoveryLeaseOwnerIsOrphaned = (incumbentOwner, claimantOwner, isProcessAlive) => {
|
|
865
|
+
if (incumbentOwner === claimantOwner)
|
|
866
|
+
return false;
|
|
867
|
+
const incumbentPid = discoveryLeaseOwnerPid(incumbentOwner);
|
|
868
|
+
if (incumbentPid === undefined)
|
|
869
|
+
return false;
|
|
870
|
+
return !isProcessAlive(incumbentPid);
|
|
871
|
+
};
|
|
872
|
+
const discoveryLeaseOwnerPid = (owner) => {
|
|
873
|
+
const match = /^(\d+):/u.exec(owner);
|
|
874
|
+
if (!match)
|
|
875
|
+
return undefined;
|
|
876
|
+
const pid = Number(match[1]);
|
|
877
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
|
|
878
|
+
};
|
|
879
|
+
const processIsAlive = (pid) => {
|
|
880
|
+
try {
|
|
881
|
+
process.kill(pid, 0);
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
catch (error) {
|
|
885
|
+
return error.code !== 'ESRCH';
|
|
886
|
+
}
|
|
916
887
|
};
|
|
917
888
|
const CONVERSATION_HISTORY_LIMIT = 50;
|
|
918
889
|
const conversationHasMessage = (session, id) => session.processedMessageIds.includes(id) ||
|
|
@@ -921,133 +892,6 @@ const conversationHasMessage = (session, id) => session.processedMessageIds.incl
|
|
|
921
892
|
Boolean(session.delivery?.messages.some((message) => message.id === id));
|
|
922
893
|
const compareConversationMessages = (left, right) => left.receivedAtMs - right.receivedAtMs ||
|
|
923
894
|
(left.providerSequence ?? left.id).localeCompare(right.providerSequence ?? right.id, undefined, { numeric: true });
|
|
924
|
-
const parseConversationSessions = (value) => {
|
|
925
|
-
const sessions = {};
|
|
926
|
-
for (const [conversationId, candidate] of Object.entries(value)) {
|
|
927
|
-
if (!isRecord(candidate) || !isRecord(candidate.issue) || !isRecord(candidate.agent) || !isRecord(candidate.context)) {
|
|
928
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
929
|
-
}
|
|
930
|
-
const issue = candidate.issue;
|
|
931
|
-
const agent = candidate.agent;
|
|
932
|
-
const delivery = candidate.delivery;
|
|
933
|
-
if (typeof issue.uuid !== 'string' || typeof issue.key !== 'string' || typeof issue.path !== 'string' ||
|
|
934
|
-
typeof candidate.provider !== 'string' || typeof candidate.externalId !== 'string' ||
|
|
935
|
-
!Object.values(candidate.context).every((value) => typeof value === 'string') ||
|
|
936
|
-
typeof agent.name !== 'string' || typeof agent.sessionRef !== 'string' ||
|
|
937
|
-
!validConversationMessages(candidate.history) || !validConversationMessages(candidate.pending) ||
|
|
938
|
-
(candidate.processedMessageIds !== undefined && !validConversationMessageIds(candidate.processedMessageIds)) ||
|
|
939
|
-
(delivery !== undefined && !validConversationDelivery(delivery) && !validLegacyConversationDelivery(delivery))) {
|
|
940
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
941
|
-
}
|
|
942
|
-
const session = structuredClone(candidate);
|
|
943
|
-
if (delivery !== undefined && validLegacyConversationDelivery(delivery)) {
|
|
944
|
-
session.pending = [...structuredClone(delivery.messages), ...session.pending];
|
|
945
|
-
delete session.delivery;
|
|
946
|
-
}
|
|
947
|
-
session.processedMessageIds = candidate.processedMessageIds === undefined
|
|
948
|
-
? [...new Set([
|
|
949
|
-
...session.history,
|
|
950
|
-
...session.pending,
|
|
951
|
-
...(session.delivery?.messages ?? []),
|
|
952
|
-
].map((message) => message.id))]
|
|
953
|
-
: [...candidate.processedMessageIds];
|
|
954
|
-
sessions[conversationId] = session;
|
|
955
|
-
}
|
|
956
|
-
return sessions;
|
|
957
|
-
};
|
|
958
|
-
const validConversationMessages = (value) => Array.isArray(value) && value.every((message) => isRecord(message) &&
|
|
959
|
-
typeof message.id === 'string' && typeof message.text === 'string' &&
|
|
960
|
-
typeof message.receivedAtMs === 'number' &&
|
|
961
|
-
(message.providerSequence === undefined || typeof message.providerSequence === 'string') &&
|
|
962
|
-
(message.author === undefined || typeof message.author === 'string'));
|
|
963
|
-
const validConversationDelivery = (value) => isRecord(value) &&
|
|
964
|
-
typeof value.claimId === 'string' && typeof value.owner === 'string' &&
|
|
965
|
-
typeof value.claimedAtMs === 'number' && typeof value.attempts === 'number' &&
|
|
966
|
-
validConversationMessages(value.messages) && isRecord(value.agent) &&
|
|
967
|
-
typeof value.agent.name === 'string' && typeof value.agent.sessionRef === 'string';
|
|
968
|
-
const validLegacyConversationDelivery = (value) => isRecord(value) && value.claimId === undefined && value.agent === undefined &&
|
|
969
|
-
typeof value.owner === 'string' && typeof value.claimedAtMs === 'number' &&
|
|
970
|
-
typeof value.attempts === 'number' && validConversationMessages(value.messages);
|
|
971
|
-
const validConversationMessageIds = (value) => Array.isArray(value) && value.every((id) => typeof id === 'string');
|
|
972
|
-
const parseBabysitterSessions = (value) => {
|
|
973
|
-
const sessions = {};
|
|
974
|
-
for (const [key, candidate] of Object.entries(value)) {
|
|
975
|
-
if (!isRecord(candidate) || !isRecord(candidate.issue)) {
|
|
976
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
977
|
-
}
|
|
978
|
-
const issue = candidate.issue;
|
|
979
|
-
if (typeof issue.uuid !== 'string' ||
|
|
980
|
-
typeof issue.key !== 'string' ||
|
|
981
|
-
typeof issue.path !== 'string' ||
|
|
982
|
-
typeof candidate.repo !== 'string' ||
|
|
983
|
-
!Number.isSafeInteger(candidate.prNumber) ||
|
|
984
|
-
candidate.prNumber < 1 ||
|
|
985
|
-
typeof candidate.agentName !== 'string' ||
|
|
986
|
-
(candidate.path !== undefined && typeof candidate.path !== 'string') ||
|
|
987
|
-
typeof candidate.critical !== 'boolean' ||
|
|
988
|
-
!Array.isArray(candidate.pendingKinds) ||
|
|
989
|
-
!candidate.pendingKinds.every((kind) => typeof kind === 'string') ||
|
|
990
|
-
(candidate.pendingDeliveryClaims !== undefined && (!Array.isArray(candidate.pendingDeliveryClaims) ||
|
|
991
|
-
!candidate.pendingDeliveryClaims.every((claim) => isRecord(claim) && typeof claim.deliveryId === 'string' && typeof claim.claimToken === 'string'))) ||
|
|
992
|
-
(candidate.resourceSubscription !== undefined && (!isRecord(candidate.resourceSubscription) ||
|
|
993
|
-
typeof candidate.resourceSubscription.subscriptionId !== 'string' ||
|
|
994
|
-
typeof candidate.resourceSubscription.provider !== 'string' ||
|
|
995
|
-
typeof candidate.resourceSubscription.resourceRef !== 'string' ||
|
|
996
|
-
typeof candidate.resourceSubscription.subscriberId !== 'string' ||
|
|
997
|
-
typeof candidate.resourceSubscription.ownerId !== 'string' ||
|
|
998
|
-
typeof candidate.resourceSubscription.expiresAt !== 'string' ||
|
|
999
|
-
(candidate.resourceSubscription.terminal !== undefined && typeof candidate.resourceSubscription.terminal !== 'boolean')))) {
|
|
1000
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
1001
|
-
}
|
|
1002
|
-
sessions[key] = {
|
|
1003
|
-
issue: { uuid: issue.uuid, key: issue.key, path: issue.path },
|
|
1004
|
-
repo: candidate.repo,
|
|
1005
|
-
prNumber: candidate.prNumber,
|
|
1006
|
-
agentName: candidate.agentName,
|
|
1007
|
-
...(candidate.path === undefined ? {} : { path: candidate.path }),
|
|
1008
|
-
critical: candidate.critical,
|
|
1009
|
-
pendingKinds: [...candidate.pendingKinds],
|
|
1010
|
-
...(candidate.resourceSubscription === undefined ? {} : {
|
|
1011
|
-
resourceSubscription: {
|
|
1012
|
-
subscriptionId: candidate.resourceSubscription.subscriptionId,
|
|
1013
|
-
provider: candidate.resourceSubscription.provider,
|
|
1014
|
-
resourceRef: candidate.resourceSubscription.resourceRef,
|
|
1015
|
-
subscriberId: candidate.resourceSubscription.subscriberId,
|
|
1016
|
-
ownerId: candidate.resourceSubscription.ownerId,
|
|
1017
|
-
expiresAt: candidate.resourceSubscription.expiresAt,
|
|
1018
|
-
...(candidate.resourceSubscription.terminal === undefined ? {} : { terminal: candidate.resourceSubscription.terminal }),
|
|
1019
|
-
},
|
|
1020
|
-
}),
|
|
1021
|
-
...(candidate.pendingDeliveryClaims === undefined ? {} : {
|
|
1022
|
-
pendingDeliveryClaims: candidate.pendingDeliveryClaims.map((claim) => ({
|
|
1023
|
-
deliveryId: claim.deliveryId,
|
|
1024
|
-
claimToken: claim.claimToken,
|
|
1025
|
-
})),
|
|
1026
|
-
}),
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
return sessions;
|
|
1030
|
-
};
|
|
1031
|
-
const parseBabysitterGenerations = (value) => {
|
|
1032
|
-
const generations = {};
|
|
1033
|
-
for (const [key, candidate] of Object.entries(value)) {
|
|
1034
|
-
if (!isRecord(candidate) ||
|
|
1035
|
-
typeof candidate.generationId !== 'string' ||
|
|
1036
|
-
typeof candidate.agentName !== 'string' ||
|
|
1037
|
-
!Number.isSafeInteger(candidate.claimedAtMs) ||
|
|
1038
|
-
!Number.isSafeInteger(candidate.leaseUntilMs) ||
|
|
1039
|
-
(candidate.phase !== 'claimed' && candidate.phase !== 'completed'))
|
|
1040
|
-
throw new Error('Factory GitHub watch state file is invalid');
|
|
1041
|
-
generations[key] = {
|
|
1042
|
-
generationId: candidate.generationId,
|
|
1043
|
-
agentName: candidate.agentName,
|
|
1044
|
-
claimedAtMs: candidate.claimedAtMs,
|
|
1045
|
-
leaseUntilMs: candidate.leaseUntilMs,
|
|
1046
|
-
phase: candidate.phase,
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
return generations;
|
|
1050
|
-
};
|
|
1051
895
|
const cloneLifecycle = (record) => structuredClone(record);
|
|
1052
896
|
const dispatchLifecycleLeaseMatches = (current, expected) => current === undefined
|
|
1053
897
|
? expected === undefined
|