praxis-agent 0.35.0 → 0.37.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/README.md +20 -2
- package/dist/application/background-agent-manager.d.ts +16 -6
- package/dist/application/background-agent-manager.js +139 -97
- package/dist/application/context-engine.d.ts +56 -0
- package/dist/application/context-engine.js +129 -0
- package/dist/application/durable-follow-up.d.ts +14 -0
- package/dist/application/durable-follow-up.js +52 -0
- package/dist/application/native-session-transcript.d.ts +72 -0
- package/dist/application/native-session-transcript.js +351 -0
- package/dist/application/native-sidechain-transcript.d.ts +34 -0
- package/dist/application/native-sidechain-transcript.js +156 -0
- package/dist/application/session-service.d.ts +23 -4
- package/dist/application/session-service.js +1130 -494
- package/dist/application/subagent-service.d.ts +5 -1
- package/dist/application/subagent-service.js +749 -320
- package/dist/application/team-agent-runtime.d.ts +29 -0
- package/dist/application/team-agent-runtime.js +169 -0
- package/dist/application/team-capability.d.ts +27 -0
- package/dist/application/team-capability.js +41 -0
- package/dist/application/team-lead-operations.d.ts +38 -0
- package/dist/application/team-lead-operations.js +155 -0
- package/dist/application/team-mailbox.d.ts +54 -0
- package/dist/application/team-mailbox.js +196 -0
- package/dist/application/team-manager.d.ts +113 -0
- package/dist/application/team-manager.js +681 -0
- package/dist/application/team-workspace.d.ts +29 -0
- package/dist/application/team-workspace.js +197 -0
- package/dist/application/top-level-agent-manager.d.ts +1 -0
- package/dist/application/top-level-agent-manager.js +1025 -403
- package/dist/application/transcript-projection.d.ts +35 -0
- package/dist/application/transcript-projection.js +139 -0
- package/dist/application/turn-lifecycle.d.ts +2 -0
- package/dist/application/turn-memory-coordinator.d.ts +43 -0
- package/dist/application/turn-memory-coordinator.js +98 -0
- package/dist/cli/agents-dashboard.js +10 -5
- package/dist/cli/interactive.d.ts +7 -0
- package/dist/cli/interactive.js +645 -334
- package/dist/cli/tui/claude-style.d.ts +8 -25
- package/dist/cli/tui/claude-style.js +299 -277
- package/dist/cli/tui/decision-surface-model.d.ts +71 -0
- package/dist/cli/tui/decision-surface-model.js +131 -0
- package/dist/cli/tui/decision-surface.d.ts +7 -0
- package/dist/cli/tui/decision-surface.js +41 -0
- package/dist/cli/tui/help-surface-model.d.ts +68 -0
- package/dist/cli/tui/help-surface-model.js +131 -0
- package/dist/cli/tui/mcp-panel.js +1 -2
- package/dist/cli/tui/permission-surface-model.d.ts +195 -0
- package/dist/cli/tui/permission-surface-model.js +360 -0
- package/dist/cli/tui/permission-surface.d.ts +7 -0
- package/dist/cli/tui/permission-surface.js +69 -0
- package/dist/cli/tui/presentation-environment.d.ts +32 -0
- package/dist/cli/tui/presentation-environment.js +149 -0
- package/dist/cli/tui/task-panel.js +10 -1
- package/dist/cli/tui/theme.d.ts +46 -3
- package/dist/cli/tui/theme.js +414 -56
- package/dist/cli/tui/tool-permission.js +16 -8
- package/dist/cli/tui/transcript-presentation.d.ts +19 -1
- package/dist/cli/tui/transcript-presentation.js +26 -6
- package/dist/cli/tui/transcript-viewport.d.ts +18 -0
- package/dist/cli/tui/transcript-viewport.js +517 -111
- package/dist/cli/tui/transcript-window-model.d.ts +108 -0
- package/dist/cli/tui/transcript-window-model.js +772 -0
- package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli/tui/tui-screen-model.d.ts +114 -0
- package/dist/cli/tui/tui-screen-model.js +210 -0
- package/dist/cli/tui/tui-view-model.d.ts +2 -49
- package/dist/cli/tui/tui-view-model.js +1 -68
- package/dist/cli-runtime.d.ts +4 -2
- package/dist/cli-runtime.js +397 -32
- package/dist/compatibility/claude/transcript-codec.js +6 -0
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/permission-policy.d.ts +4 -0
- package/dist/core/permission-policy.js +21 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +20 -2
- package/dist/core/team-mailbox.d.ts +54 -0
- package/dist/core/team-mailbox.js +212 -0
- package/dist/core/team-ownership.d.ts +84 -0
- package/dist/core/team-ownership.js +505 -0
- package/dist/core/tool-execution-scheduler.js +6 -1
- package/dist/core/transcript-event.d.ts +5 -1
- package/dist/core/transcript-event.js +12 -0
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- package/dist/persistence/claude-transcript-store.d.ts +2 -1
- package/dist/persistence/claude-transcript-store.js +2 -10
- package/dist/persistence/native-team-mailbox.d.ts +68 -0
- package/dist/persistence/native-team-mailbox.js +542 -0
- package/dist/persistence/native-team-store.d.ts +34 -0
- package/dist/persistence/native-team-store.js +257 -0
- package/dist/persistence/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/native-transcript-store.d.ts +69 -0
- package/dist/persistence/native-transcript-store.js +321 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/persistence/transcript-file-append.d.ts +2 -0
- package/dist/persistence/transcript-file-append.js +10 -0
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -0
- package/dist/tools/claude-capabilities.d.ts +4 -0
- package/dist/tools/claude-capabilities.js +18 -2
- package/dist/tools/team-lead-tools.d.ts +19 -0
- package/dist/tools/team-lead-tools.js +479 -0
- package/dist/tools/team-member-tools.d.ts +21 -0
- package/dist/tools/team-member-tools.js +259 -0
- package/package.json +1 -1
- package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
- package/dist/cli/tui/fullscreen-renderer.js +0 -13
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ModelProvider, ModelToolCall, PermissionApproval, PermissionDecision, PermissionResolver, RuntimeEventSink } from '../core/runtime.js';
|
|
2
|
+
import type { ContextAssembler } from '../core/context.js';
|
|
3
|
+
import type { ClaudeExtensionCatalog } from '../extensions/claude-extensions.js';
|
|
4
|
+
import type { ClaudeHookRunner } from '../hooks/claude-hooks.js';
|
|
5
|
+
import { type AgentPermissionMode } from './subagent-service.js';
|
|
6
|
+
import type { TeamAgentRuntime } from './team-manager.js';
|
|
7
|
+
export interface ClaudeTeamAgentRuntimeOptions {
|
|
8
|
+
readonly nativeRoot: string;
|
|
9
|
+
readonly configRoot: string;
|
|
10
|
+
readonly claudeVersion: string;
|
|
11
|
+
readonly provider: ModelProvider;
|
|
12
|
+
readonly extensions?: ClaudeExtensionCatalog;
|
|
13
|
+
readonly hooks?: ClaudeHookRunner;
|
|
14
|
+
readonly contextAssembler?: ContextAssembler;
|
|
15
|
+
readonly providerForModel?: (model: string) => ModelProvider;
|
|
16
|
+
readonly permissionResolverForMode?: (mode: AgentPermissionMode) => PermissionResolver;
|
|
17
|
+
readonly eventSink?: RuntimeEventSink;
|
|
18
|
+
readonly approveTool?: (call: ModelToolCall, originalCall?: ModelToolCall, decision?: PermissionDecision) => PermissionApproval | Promise<PermissionApproval>;
|
|
19
|
+
}
|
|
20
|
+
export declare class ClaudeTeamAgentRuntime implements TeamAgentRuntime {
|
|
21
|
+
private readonly options;
|
|
22
|
+
constructor(options: ClaudeTeamAgentRuntimeOptions);
|
|
23
|
+
run(input: Parameters<TeamAgentRuntime['run']>[0]): Promise<{
|
|
24
|
+
status: 'completed' | 'failed' | 'orphaned';
|
|
25
|
+
totalTokens: number;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=team-agent-runtime.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { resolveProjectIdentity } from '../platform/project-identity.js';
|
|
5
|
+
import { sanitizeProjectPath } from '../platform/project-path-key.js';
|
|
6
|
+
import { ClaudeSubagentExecutor, } from './subagent-service.js';
|
|
7
|
+
import { teamMailboxMessageId } from './team-mailbox.js';
|
|
8
|
+
function digest(value) {
|
|
9
|
+
return createHash('sha256').update(value).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
function uuidFromHex(hex) {
|
|
12
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
13
|
+
}
|
|
14
|
+
function executionIdentity(projectIdentity, teamId, taskId, generation) {
|
|
15
|
+
const value = `${projectIdentity}\0${teamId}\0${taskId}\0${generation}`;
|
|
16
|
+
const hash = digest(value);
|
|
17
|
+
return {
|
|
18
|
+
sessionId: uuidFromHex(hash),
|
|
19
|
+
promptId: uuidFromHex(digest(`${value}\0prompt`)),
|
|
20
|
+
runId: `wf_${hash.slice(0, 8)}${hash.slice(8, 14)}-${hash.slice(14, 17)}`,
|
|
21
|
+
agentId: `a${hash.slice(0, 16)}`,
|
|
22
|
+
taskDirectory: `${sanitizeProjectPath(taskId)}-${hash.slice(16, 28)}/generation-${generation}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function taskPrompt(input) {
|
|
26
|
+
return [
|
|
27
|
+
`Assigned task ID: ${input.task.id}`,
|
|
28
|
+
`Assigned Team task: ${input.task.description}`,
|
|
29
|
+
`Team member: ${input.member.name}`,
|
|
30
|
+
`Agent type: ${input.member.agentType}`,
|
|
31
|
+
`Access: ${input.member.access}`,
|
|
32
|
+
`Assigned cwd: ${input.cwd}`,
|
|
33
|
+
`Assigned branch: ${input.branch ?? '(shared invocation checkout)'}`,
|
|
34
|
+
`Claimed files: ${input.task.claims.files.join(', ') || '(none)'}`,
|
|
35
|
+
`Claimed public contracts: ${input.task.claims.publicContracts.join(', ') || '(none)'}`,
|
|
36
|
+
`Claimed generated artifacts: ${input.task.claims.generatedArtifacts.join(', ') || '(none)'}`,
|
|
37
|
+
`Claimed migrations: ${input.task.claims.migrations.join(', ') || '(none)'}`,
|
|
38
|
+
`Claimed merge targets: ${input.task.claims.mergeTargets.join(', ') || '(none)'}`,
|
|
39
|
+
'Use only the tools provided for this Team member and work only in the assigned cwd.',
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
export class ClaudeTeamAgentRuntime {
|
|
43
|
+
options;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
async run(input) {
|
|
48
|
+
if (this.options.provider.capabilities.usage !== true)
|
|
49
|
+
throw new Error('Team agent runtime requires provider usage capability');
|
|
50
|
+
const projectIdentity = await resolveProjectIdentity(input.cwd);
|
|
51
|
+
const identity = executionIdentity(projectIdentity, input.teamId, input.task.id, input.generation);
|
|
52
|
+
const transcriptDirectory = join(this.options.nativeRoot, 'state', 'team-executions', sanitizeProjectPath(projectIdentity), `team-${digest(input.teamId).slice(0, 16)}`, identity.taskDirectory);
|
|
53
|
+
await mkdir(transcriptDirectory, { recursive: true });
|
|
54
|
+
const approveTool = this.options.approveTool;
|
|
55
|
+
const executor = new ClaudeSubagentExecutor({
|
|
56
|
+
configRoot: this.options.configRoot,
|
|
57
|
+
dataPlane: 'native',
|
|
58
|
+
cwd: input.cwd,
|
|
59
|
+
cwdProvider: () => input.cwd,
|
|
60
|
+
claudeVersion: this.options.claudeVersion,
|
|
61
|
+
provider: this.options.provider,
|
|
62
|
+
persistence: 'disk',
|
|
63
|
+
baseTools: input.tools,
|
|
64
|
+
permissions: input.permissions,
|
|
65
|
+
...(this.options.extensions
|
|
66
|
+
? { extensions: this.options.extensions }
|
|
67
|
+
: {}),
|
|
68
|
+
...(this.options.hooks ? { hooks: this.options.hooks } : {}),
|
|
69
|
+
...(this.options.contextAssembler
|
|
70
|
+
? { contextAssembler: this.options.contextAssembler }
|
|
71
|
+
: {}),
|
|
72
|
+
...(this.options.providerForModel
|
|
73
|
+
? { providerForModel: this.options.providerForModel }
|
|
74
|
+
: {}),
|
|
75
|
+
...(this.options.permissionResolverForMode
|
|
76
|
+
? {
|
|
77
|
+
permissionResolverForMode: this.options.permissionResolverForMode,
|
|
78
|
+
}
|
|
79
|
+
: {}),
|
|
80
|
+
eventSink: (event) => {
|
|
81
|
+
if (event.type === 'task-progress')
|
|
82
|
+
input.reportProgress({
|
|
83
|
+
generation: input.generation,
|
|
84
|
+
totalTokens: event.usage.totalTokens,
|
|
85
|
+
durationMs: event.usage.durationMs,
|
|
86
|
+
});
|
|
87
|
+
this.options.eventSink?.(event);
|
|
88
|
+
},
|
|
89
|
+
...(approveTool
|
|
90
|
+
? {
|
|
91
|
+
approveTool: (call, originalCall, decision) => {
|
|
92
|
+
const wrapped = decision?.behavior === 'ask'
|
|
93
|
+
? {
|
|
94
|
+
...decision,
|
|
95
|
+
reason: `${decision.reason ?? 'Team permission request'} [team=${input.teamId} member=${input.member.name} task=${input.task.id} generation=${input.generation}]`,
|
|
96
|
+
metadata: {
|
|
97
|
+
...(decision.metadata ?? {}),
|
|
98
|
+
teamId: input.teamId,
|
|
99
|
+
member: input.member.name,
|
|
100
|
+
taskId: input.task.id,
|
|
101
|
+
generation: input.generation,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
: decision;
|
|
105
|
+
return approveTool(call, originalCall, wrapped);
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
109
|
+
sendOwnedBackgroundAgent: async (_sessionId, _agentId, message, summary, toolUseId) => {
|
|
110
|
+
const recipient = _agentId;
|
|
111
|
+
const to = recipient === 'broadcast' ? 'broadcast' : recipient;
|
|
112
|
+
const endpoint = input.mailbox;
|
|
113
|
+
await endpoint.send({
|
|
114
|
+
messageId: teamMailboxMessageId(input.teamId, input.member.name, toolUseId),
|
|
115
|
+
to,
|
|
116
|
+
payload: {
|
|
117
|
+
kind: 'text',
|
|
118
|
+
text: message,
|
|
119
|
+
...(summary === undefined ? {} : { summary }),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return 'Message sent successfully.';
|
|
123
|
+
},
|
|
124
|
+
durableFollowUpSource: () => input.mailbox.project(),
|
|
125
|
+
});
|
|
126
|
+
let failure;
|
|
127
|
+
let workflowResult;
|
|
128
|
+
try {
|
|
129
|
+
workflowResult = await executor.runWorkflowAgent({
|
|
130
|
+
sessionId: identity.sessionId,
|
|
131
|
+
promptId: identity.promptId,
|
|
132
|
+
runId: identity.runId,
|
|
133
|
+
agentId: identity.agentId,
|
|
134
|
+
transcriptDirectory,
|
|
135
|
+
prompt: taskPrompt(input),
|
|
136
|
+
agentType: input.member.agentType,
|
|
137
|
+
signal: input.signal,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
failure = error;
|
|
142
|
+
}
|
|
143
|
+
let closeFailure;
|
|
144
|
+
try {
|
|
145
|
+
await executor.close();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
closeFailure = error;
|
|
149
|
+
}
|
|
150
|
+
if (failure !== undefined && closeFailure !== undefined) {
|
|
151
|
+
throw new AggregateError([failure, closeFailure], 'Team agent execution and cleanup failed', { cause: failure });
|
|
152
|
+
}
|
|
153
|
+
if (failure !== undefined)
|
|
154
|
+
throw failure;
|
|
155
|
+
if (closeFailure !== undefined)
|
|
156
|
+
throw closeFailure;
|
|
157
|
+
if (!workflowResult)
|
|
158
|
+
throw new Error('Team agent workflow completed without a result');
|
|
159
|
+
const totalTokens = workflowResult.usage.inputTokens + workflowResult.usage.outputTokens;
|
|
160
|
+
const durationMs = workflowResult.durationMs;
|
|
161
|
+
input.reportProgress({
|
|
162
|
+
generation: input.generation,
|
|
163
|
+
totalTokens,
|
|
164
|
+
durationMs,
|
|
165
|
+
});
|
|
166
|
+
return { status: 'completed', totalTokens, durationMs };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=team-agent-runtime.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PermissionResolver, ToolRegistry } from '../core/runtime.js';
|
|
2
|
+
import type { ClaudeTeamAgentRuntime } from './team-agent-runtime.js';
|
|
3
|
+
import { LocalTeamManager } from './team-manager.js';
|
|
4
|
+
/** The local Team manager's conservative default parallelism. */
|
|
5
|
+
export declare const DEFAULT_LOCAL_TEAM_CONCURRENCY = 4;
|
|
6
|
+
export interface LocalTeamCapabilityOptions {
|
|
7
|
+
readonly nativeRoot: string;
|
|
8
|
+
/** Resolves the active workspace at the moment the Team capability opens. */
|
|
9
|
+
readonly cwd: () => string;
|
|
10
|
+
readonly maxConcurrent: number;
|
|
11
|
+
readonly baseTools: ToolRegistry;
|
|
12
|
+
readonly permissions: PermissionResolver;
|
|
13
|
+
readonly createRuntime: () => ClaudeTeamAgentRuntime;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Lazy internal seam for the future Team lead tools.
|
|
17
|
+
*
|
|
18
|
+
* Keeping this port private to composition means enabling the experimental
|
|
19
|
+
* capability cannot perform Team discovery during ordinary session startup.
|
|
20
|
+
*/
|
|
21
|
+
export declare class LocalTeamCapability {
|
|
22
|
+
private readonly options;
|
|
23
|
+
private openPromise;
|
|
24
|
+
constructor(options: LocalTeamCapabilityOptions);
|
|
25
|
+
open(): Promise<LocalTeamManager>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=team-capability.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { LocalTeamManager } from './team-manager.js';
|
|
2
|
+
/** The local Team manager's conservative default parallelism. */
|
|
3
|
+
export const DEFAULT_LOCAL_TEAM_CONCURRENCY = 4;
|
|
4
|
+
/**
|
|
5
|
+
* Lazy internal seam for the future Team lead tools.
|
|
6
|
+
*
|
|
7
|
+
* Keeping this port private to composition means enabling the experimental
|
|
8
|
+
* capability cannot perform Team discovery during ordinary session startup.
|
|
9
|
+
*/
|
|
10
|
+
export class LocalTeamCapability {
|
|
11
|
+
options;
|
|
12
|
+
openPromise;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
open() {
|
|
17
|
+
if (this.openPromise)
|
|
18
|
+
return this.openPromise;
|
|
19
|
+
const promise = (async () => {
|
|
20
|
+
const cwd = this.options.cwd();
|
|
21
|
+
if (!cwd)
|
|
22
|
+
throw new Error('A Team workspace cwd is required');
|
|
23
|
+
const runtime = this.options.createRuntime();
|
|
24
|
+
return LocalTeamManager.open({
|
|
25
|
+
nativeRoot: this.options.nativeRoot,
|
|
26
|
+
cwd,
|
|
27
|
+
maxConcurrent: this.options.maxConcurrent,
|
|
28
|
+
baseTools: this.options.baseTools,
|
|
29
|
+
permissions: this.options.permissions,
|
|
30
|
+
runtime,
|
|
31
|
+
});
|
|
32
|
+
})();
|
|
33
|
+
this.openPromise = promise;
|
|
34
|
+
void promise.catch(() => {
|
|
35
|
+
if (this.openPromise === promise)
|
|
36
|
+
this.openPromise = undefined;
|
|
37
|
+
});
|
|
38
|
+
return promise;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=team-capability.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TeamMailboxMessage } from '../core/team-mailbox.js';
|
|
2
|
+
import type { TeamSnapshot } from '../core/team-ownership.js';
|
|
3
|
+
import type { LocalTeamManager } from './team-manager.js';
|
|
4
|
+
import type { LocalTeamCapability } from './team-capability.js';
|
|
5
|
+
import type { DurableTeamMailboxBatch, TeamMailboxProjectBounds, TeamMailboxSendInput } from './team-mailbox.js';
|
|
6
|
+
export type TeamCreateRequest = Omit<Parameters<LocalTeamManager['create']>[0], 'leadSessionId'>;
|
|
7
|
+
export interface TeamLeadSendInput extends Omit<TeamMailboxSendInput, 'messageId'> {
|
|
8
|
+
readonly teamId: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class TeamLeadOperations {
|
|
11
|
+
private readonly capability;
|
|
12
|
+
private readonly owned;
|
|
13
|
+
private closed;
|
|
14
|
+
constructor(capability: Pick<LocalTeamCapability, 'open'>);
|
|
15
|
+
create(input: TeamCreateRequest, leadSessionId: string): Promise<TeamSnapshot>;
|
|
16
|
+
resume(teamId: string, leadSessionId: string): Promise<TeamSnapshot>;
|
|
17
|
+
activeLeadPolicy(leadSessionId: string): 'hybrid' | 'coordinator';
|
|
18
|
+
list(): Promise<readonly TeamSnapshot[]>;
|
|
19
|
+
accept(input: {
|
|
20
|
+
teamId: string;
|
|
21
|
+
taskId: string;
|
|
22
|
+
generation?: number;
|
|
23
|
+
decision?: 'accepted' | 'rejected';
|
|
24
|
+
}, leadSessionId: string): Promise<TeamSnapshot>;
|
|
25
|
+
send(input: TeamLeadSendInput, leadSessionId: string, operationId: string): Promise<TeamMailboxMessage>;
|
|
26
|
+
projectInbox(leadSessionId: string, bounds?: TeamMailboxProjectBounds): Promise<DurableTeamMailboxBatch | null>;
|
|
27
|
+
stop(input: {
|
|
28
|
+
teamId: string;
|
|
29
|
+
drainMs?: number;
|
|
30
|
+
}, leadSessionId: string): Promise<TeamSnapshot>;
|
|
31
|
+
waitForIdle(teamId: string, leadSessionId: string): Promise<TeamSnapshot>;
|
|
32
|
+
detach(teamId: string, leadSessionId: string): Promise<TeamSnapshot>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
private manager;
|
|
35
|
+
private require;
|
|
36
|
+
private forLead;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=team-lead-operations.d.ts.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { teamMailboxMessageId } from './team-mailbox.js';
|
|
3
|
+
export class TeamLeadOperations {
|
|
4
|
+
capability;
|
|
5
|
+
owned = new Map();
|
|
6
|
+
closed = false;
|
|
7
|
+
constructor(capability) {
|
|
8
|
+
this.capability = capability;
|
|
9
|
+
}
|
|
10
|
+
async create(input, leadSessionId) {
|
|
11
|
+
const manager = await this.manager();
|
|
12
|
+
if (this.owned.has(input.teamId))
|
|
13
|
+
throw new Error(`Team ${input.teamId} is already active`);
|
|
14
|
+
const team = await manager.create({ ...input, leadSessionId });
|
|
15
|
+
const snapshot = await team.snapshot();
|
|
16
|
+
this.owned.set(input.teamId, {
|
|
17
|
+
leadSessionId,
|
|
18
|
+
team,
|
|
19
|
+
leadPolicy: snapshot.policy?.lead ?? 'hybrid',
|
|
20
|
+
});
|
|
21
|
+
return snapshot;
|
|
22
|
+
}
|
|
23
|
+
async resume(teamId, leadSessionId) {
|
|
24
|
+
const current = this.owned.get(teamId);
|
|
25
|
+
if (current) {
|
|
26
|
+
if (current.leadSessionId !== leadSessionId)
|
|
27
|
+
throw new Error(`Team ${teamId} is active under another lead session`);
|
|
28
|
+
return current.team.snapshot();
|
|
29
|
+
}
|
|
30
|
+
const team = await (await this.manager()).resume({ teamId, leadSessionId });
|
|
31
|
+
const snapshot = await team.snapshot();
|
|
32
|
+
this.owned.set(teamId, {
|
|
33
|
+
leadSessionId,
|
|
34
|
+
team,
|
|
35
|
+
leadPolicy: snapshot.policy?.lead ?? 'hybrid',
|
|
36
|
+
});
|
|
37
|
+
return snapshot;
|
|
38
|
+
}
|
|
39
|
+
activeLeadPolicy(leadSessionId) {
|
|
40
|
+
for (const owned of this.owned.values())
|
|
41
|
+
if (owned.leadSessionId === leadSessionId &&
|
|
42
|
+
owned.leadPolicy === 'coordinator')
|
|
43
|
+
return 'coordinator';
|
|
44
|
+
return 'hybrid';
|
|
45
|
+
}
|
|
46
|
+
async list() {
|
|
47
|
+
return (await this.manager()).list();
|
|
48
|
+
}
|
|
49
|
+
async accept(input, leadSessionId) {
|
|
50
|
+
return this.forLead(input.teamId, leadSessionId, (team) => team.accept(input.taskId, input.generation, input.decision));
|
|
51
|
+
}
|
|
52
|
+
async send(input, leadSessionId, operationId) {
|
|
53
|
+
const owned = this.require(input.teamId, leadSessionId);
|
|
54
|
+
return owned.team.mailboxEndpoint('lead').send({
|
|
55
|
+
messageId: teamMailboxMessageId(input.teamId, 'lead', operationId),
|
|
56
|
+
to: input.to,
|
|
57
|
+
payload: input.payload,
|
|
58
|
+
...(input.createdAt === undefined ? {} : { createdAt: input.createdAt }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async projectInbox(leadSessionId, bounds = {}) {
|
|
62
|
+
const maxBytes = bounds.maxBytes ?? 64 * 1024;
|
|
63
|
+
const maxMessages = bounds.maxMessages ?? 32;
|
|
64
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)
|
|
65
|
+
throw new Error('Invalid Team inbox maxBytes');
|
|
66
|
+
if (!Number.isSafeInteger(maxMessages) || maxMessages <= 0)
|
|
67
|
+
throw new Error('Invalid Team inbox maxMessages');
|
|
68
|
+
const batches = [];
|
|
69
|
+
let bytes = 0;
|
|
70
|
+
let messageCount = 0;
|
|
71
|
+
for (const owned of [...this.owned.entries()]
|
|
72
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
73
|
+
.map(([, value]) => value)) {
|
|
74
|
+
if (owned.leadSessionId !== leadSessionId)
|
|
75
|
+
continue;
|
|
76
|
+
const remainingMessages = maxMessages - messageCount;
|
|
77
|
+
if (remainingMessages <= 0)
|
|
78
|
+
break;
|
|
79
|
+
const batch = await owned.team.mailboxEndpoint('lead').project({
|
|
80
|
+
maxBytes: maxBytes - bytes,
|
|
81
|
+
maxMessages: remainingMessages,
|
|
82
|
+
});
|
|
83
|
+
if (!batch)
|
|
84
|
+
continue;
|
|
85
|
+
batches.push(batch);
|
|
86
|
+
bytes += batch.messages.reduce((total, message) => total + Buffer.byteLength(message, 'utf8'), 0);
|
|
87
|
+
messageCount += batch.messages.length;
|
|
88
|
+
if (bytes >= maxBytes || messageCount >= maxMessages)
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (batches.length === 0)
|
|
92
|
+
return null;
|
|
93
|
+
let acknowledged = false;
|
|
94
|
+
return {
|
|
95
|
+
id: `lead:${leadSessionId}:${batches.map((batch) => batch.id).join('|')}`,
|
|
96
|
+
messages: Object.freeze(batches.flatMap((batch) => batch.messages)),
|
|
97
|
+
acknowledge: async () => {
|
|
98
|
+
if (acknowledged)
|
|
99
|
+
return;
|
|
100
|
+
for (const batch of batches)
|
|
101
|
+
await batch.acknowledge();
|
|
102
|
+
acknowledged = true;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async stop(input, leadSessionId) {
|
|
107
|
+
const owned = this.require(input.teamId, leadSessionId);
|
|
108
|
+
const snapshot = await owned.team.stop(input.drainMs === undefined ? {} : { drainMs: input.drainMs });
|
|
109
|
+
this.owned.delete(input.teamId);
|
|
110
|
+
return snapshot;
|
|
111
|
+
}
|
|
112
|
+
async waitForIdle(teamId, leadSessionId) {
|
|
113
|
+
return this.forLead(teamId, leadSessionId, (team) => team.waitForIdle());
|
|
114
|
+
}
|
|
115
|
+
async detach(teamId, leadSessionId) {
|
|
116
|
+
const owned = this.require(teamId, leadSessionId);
|
|
117
|
+
const snapshot = await owned.team.detach();
|
|
118
|
+
this.owned.delete(teamId);
|
|
119
|
+
return snapshot;
|
|
120
|
+
}
|
|
121
|
+
async close() {
|
|
122
|
+
if (this.closed)
|
|
123
|
+
return;
|
|
124
|
+
this.closed = true;
|
|
125
|
+
const failures = [];
|
|
126
|
+
for (const [teamId, owned] of this.owned) {
|
|
127
|
+
try {
|
|
128
|
+
await owned.team.stop();
|
|
129
|
+
this.owned.delete(teamId);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
failures.push(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (failures.length)
|
|
136
|
+
throw new AggregateError(failures, 'Failed to stop Teams');
|
|
137
|
+
}
|
|
138
|
+
async manager() {
|
|
139
|
+
if (this.closed)
|
|
140
|
+
throw new Error('Team lead operations are closed');
|
|
141
|
+
return this.capability.open();
|
|
142
|
+
}
|
|
143
|
+
require(teamId, leadSessionId) {
|
|
144
|
+
const owned = this.owned.get(teamId);
|
|
145
|
+
if (!owned)
|
|
146
|
+
throw new Error(`Team ${teamId} is not active; resume it first`);
|
|
147
|
+
if (owned.leadSessionId !== leadSessionId)
|
|
148
|
+
throw new Error(`Team ${teamId} is active under another lead session`);
|
|
149
|
+
return owned;
|
|
150
|
+
}
|
|
151
|
+
async forLead(teamId, leadSessionId, operation) {
|
|
152
|
+
return operation(this.require(teamId, leadSessionId).team);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=team-lead-operations.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type TeamMailboxMessage, type TeamMailboxPayload } from '../core/team-mailbox.js';
|
|
2
|
+
import { NativeTeamMailbox, type NativeTeamMailboxRetention } from '../persistence/native-team-mailbox.js';
|
|
3
|
+
export interface TeamMailboxWakeMetadata {
|
|
4
|
+
readonly teamId: string;
|
|
5
|
+
readonly messageId: string;
|
|
6
|
+
readonly sequence: number;
|
|
7
|
+
readonly recipients: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface DurableTeamMailboxBatch {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly messages: readonly string[];
|
|
12
|
+
acknowledge(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface TeamMailboxSendInput {
|
|
15
|
+
readonly messageId: string;
|
|
16
|
+
readonly to: string | readonly string[] | 'broadcast';
|
|
17
|
+
readonly payload: TeamMailboxPayload;
|
|
18
|
+
readonly createdAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TeamMailboxProjectBounds {
|
|
21
|
+
readonly maxBytes?: number;
|
|
22
|
+
readonly maxMessages?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface TeamMailboxOptions {
|
|
25
|
+
readonly nativeRoot?: string;
|
|
26
|
+
readonly projectIdentity?: string;
|
|
27
|
+
readonly teamId: string;
|
|
28
|
+
readonly participants: readonly string[];
|
|
29
|
+
readonly store?: NativeTeamMailbox;
|
|
30
|
+
readonly retention?: NativeTeamMailboxRetention;
|
|
31
|
+
readonly wake?: (metadata: TeamMailboxWakeMetadata) => void | Promise<void>;
|
|
32
|
+
readonly warn?: (message: string) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function teamMailboxMessageId(teamId: string, sender: string, operationId: string): string;
|
|
35
|
+
export declare class TeamMailboxService {
|
|
36
|
+
private readonly options;
|
|
37
|
+
readonly store: NativeTeamMailbox;
|
|
38
|
+
private readonly participants;
|
|
39
|
+
private readonly participantSet;
|
|
40
|
+
constructor(options: TeamMailboxOptions);
|
|
41
|
+
endpoint(sender: string): TeamMailboxEndpoint;
|
|
42
|
+
send(input: TeamMailboxSendInput, sender: string): Promise<TeamMailboxMessage>;
|
|
43
|
+
project(sender: string, bounds?: TeamMailboxProjectBounds): Promise<DurableTeamMailboxBatch | null>;
|
|
44
|
+
private recipients;
|
|
45
|
+
private assertParticipant;
|
|
46
|
+
}
|
|
47
|
+
export declare class TeamMailboxEndpoint {
|
|
48
|
+
private readonly service;
|
|
49
|
+
readonly participant: string;
|
|
50
|
+
constructor(service: TeamMailboxService, participant: string);
|
|
51
|
+
send(input: TeamMailboxSendInput): Promise<TeamMailboxMessage>;
|
|
52
|
+
project(bounds?: TeamMailboxProjectBounds): Promise<DurableTeamMailboxBatch | null>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=team-mailbox.d.ts.map
|