@undefineds.co/linx 0.3.18 → 0.3.20
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/lib/auto-mode/pod-persistence.js +0 -2
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/capture/persistence.js +377 -0
- package/dist/lib/capture/persistence.js.map +1 -0
- package/dist/lib/capture/tool.js +242 -0
- package/dist/lib/capture/tool.js.map +1 -0
- package/dist/lib/linx-cloud-errors.js +5 -0
- package/dist/lib/linx-cloud-errors.js.map +1 -1
- package/dist/lib/linx-status-line.js +8 -1
- package/dist/lib/linx-status-line.js.map +1 -1
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +103 -34
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +42 -29
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +102 -4
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +0 -2
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +12 -2
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/status-line-command.js +2 -2
- package/dist/lib/status-line-command.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +104 -58
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +1 -1
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/skills/basic/SKILL.md +46 -0
- package/dist/skills/capture/SKILL.md +165 -0
- package/dist/skills/symphony/SKILL.md +14 -4
- package/dist/skills/xpod-cli/SKILL.md +13 -2
- package/package.json +2 -2
- package/vendor/agent-runtime/dist/coordination.d.ts +93 -0
- package/vendor/agent-runtime/dist/coordination.js +145 -0
- package/vendor/agent-runtime/dist/index.d.ts +1 -0
- package/vendor/agent-runtime/dist/index.js +1 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +11 -0
- package/vendor/agent-runtime/dist/reconciler.js +41 -3
- package/vendor/agent-runtime/dist/symphony.d.ts +11 -9
- package/vendor/agent-runtime/dist/symphony.js +11 -7
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export const CLIENT_RECONCILER_LEASE_TTL_MS = 30_000;
|
|
2
|
+
export const CLIENT_CAPABILITY_HEARTBEAT_TTL_MS = 45_000;
|
|
3
|
+
const CLIENT_KIND_PRIORITY = {
|
|
4
|
+
cli: 40,
|
|
5
|
+
desktop: 30,
|
|
6
|
+
mobile: 20,
|
|
7
|
+
web: 10,
|
|
8
|
+
};
|
|
9
|
+
export function defaultReconcilerOwnerForPolicyKind(policyKind) {
|
|
10
|
+
return policyKind === 'open_group' ? 'server' : 'client';
|
|
11
|
+
}
|
|
12
|
+
export function resolveReconcilerOwnership(input = {}) {
|
|
13
|
+
const humanAuthorityCount = resolveHumanAuthorityCount(input);
|
|
14
|
+
const owner = input.reconcilerOwner === 'client' || input.reconcilerOwner === 'server'
|
|
15
|
+
? input.reconcilerOwner
|
|
16
|
+
: humanAuthorityCount !== undefined && humanAuthorityCount > 1
|
|
17
|
+
? 'server'
|
|
18
|
+
: defaultReconcilerOwnerForPolicyKind(input.policyKind);
|
|
19
|
+
return {
|
|
20
|
+
reconcilerOwner: owner,
|
|
21
|
+
...(humanAuthorityCount !== undefined ? { humanAuthorityCount } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function isSingleHumanAuthority(input) {
|
|
25
|
+
return resolveHumanAuthorityCount(input) === 1;
|
|
26
|
+
}
|
|
27
|
+
export function hasMultipleHumanAuthorities(input) {
|
|
28
|
+
const count = resolveHumanAuthorityCount(input);
|
|
29
|
+
return count !== undefined && count > 1;
|
|
30
|
+
}
|
|
31
|
+
export function defaultSharedWakeAgentJobDedupeKey(input) {
|
|
32
|
+
return [input.thread, input.triggerMessage, input.agent].join('|');
|
|
33
|
+
}
|
|
34
|
+
export function createSharedWakeAgentJob(input) {
|
|
35
|
+
const job = {
|
|
36
|
+
...input,
|
|
37
|
+
id: input.id ?? sharedWakeAgentJobId(input),
|
|
38
|
+
};
|
|
39
|
+
return job;
|
|
40
|
+
}
|
|
41
|
+
export function sharedWakeAgentJobId(input) {
|
|
42
|
+
return `wake_${hashString(defaultSharedWakeAgentJobDedupeKey(input))}`;
|
|
43
|
+
}
|
|
44
|
+
export function isClientReconcilerLeaseActive(lease, now = Date.now()) {
|
|
45
|
+
if (!lease?.fencingToken || !lease.thread || !lease.ownerClientId || !lease.ownerUser || !lease.expiresAt) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const expiresAt = Date.parse(lease.expiresAt);
|
|
49
|
+
const nowMs = toEpochMs(now);
|
|
50
|
+
return Number.isFinite(expiresAt) && Number.isFinite(nowMs) && expiresAt > nowMs;
|
|
51
|
+
}
|
|
52
|
+
export function canClientCoordinateThread(input) {
|
|
53
|
+
const clientId = normalizeText(input.clientId);
|
|
54
|
+
if (!clientId || !input.thread || !isClientReconcilerLeaseActive(input.lease, input.now)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return input.lease.thread === input.thread && input.lease.ownerClientId === clientId;
|
|
58
|
+
}
|
|
59
|
+
export function isClientCapabilityAlive(client, now = Date.now(), heartbeatTtlMs = CLIENT_CAPABILITY_HEARTBEAT_TTL_MS) {
|
|
60
|
+
const heartbeatAt = Date.parse(client.heartbeatAt);
|
|
61
|
+
const nowMs = toEpochMs(now);
|
|
62
|
+
return Number.isFinite(heartbeatAt) && Number.isFinite(nowMs) && nowMs - heartbeatAt <= heartbeatTtlMs;
|
|
63
|
+
}
|
|
64
|
+
export function selectClientReconciler(clients, options = {}) {
|
|
65
|
+
const now = options.now ?? Date.now();
|
|
66
|
+
const eligible = clients
|
|
67
|
+
.filter((client) => client.canCoordinateClientOwned)
|
|
68
|
+
.filter((client) => !options.ownerUser || client.user === options.ownerUser)
|
|
69
|
+
.filter((client) => isClientCapabilityAlive(client, now, options.heartbeatTtlMs))
|
|
70
|
+
.sort(compareClientCapabilityForCoordination);
|
|
71
|
+
return eligible[0] ?? null;
|
|
72
|
+
}
|
|
73
|
+
export function grantClientReconcilerLease(options) {
|
|
74
|
+
const now = options.now ?? Date.now();
|
|
75
|
+
const ttl = options.leaseTtlMs ?? CLIENT_RECONCILER_LEASE_TTL_MS;
|
|
76
|
+
const previousOwner = options.previousLease && isClientReconcilerLeaseActive(options.previousLease, now)
|
|
77
|
+
? options.clients.find((client) => (client.clientId === options.previousLease?.ownerClientId
|
|
78
|
+
&& client.user === options.ownerUser
|
|
79
|
+
&& client.canCoordinateClientOwned
|
|
80
|
+
&& isClientCapabilityAlive(client, now, options.heartbeatTtlMs)))
|
|
81
|
+
: undefined;
|
|
82
|
+
const selected = previousOwner ?? selectClientReconciler(options.clients, {
|
|
83
|
+
ownerUser: options.ownerUser,
|
|
84
|
+
now,
|
|
85
|
+
heartbeatTtlMs: options.heartbeatTtlMs,
|
|
86
|
+
});
|
|
87
|
+
if (!selected) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const nowMs = toEpochMs(now);
|
|
91
|
+
const expiresAt = new Date(nowMs + ttl).toISOString();
|
|
92
|
+
return {
|
|
93
|
+
thread: options.thread,
|
|
94
|
+
ownerClientId: selected.clientId,
|
|
95
|
+
ownerUser: selected.user,
|
|
96
|
+
fencingToken: options.fencingToken ?? createClientReconcilerFencingToken(options.thread, selected.clientId, nowMs, options.randomId),
|
|
97
|
+
expiresAt,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function createClientReconcilerFencingToken(thread, clientId, now = Date.now(), randomId) {
|
|
101
|
+
const epoch = toEpochMs(now);
|
|
102
|
+
const entropy = normalizeText(randomId) ?? Math.random().toString(36).slice(2, 10);
|
|
103
|
+
return `client_${hashString(`${thread}|${clientId}|${epoch}|${entropy}`)}`;
|
|
104
|
+
}
|
|
105
|
+
function compareClientCapabilityForCoordination(a, b) {
|
|
106
|
+
const priority = CLIENT_KIND_PRIORITY[b.kind] - CLIENT_KIND_PRIORITY[a.kind];
|
|
107
|
+
if (priority !== 0)
|
|
108
|
+
return priority;
|
|
109
|
+
const heartbeat = Date.parse(b.heartbeatAt) - Date.parse(a.heartbeatAt);
|
|
110
|
+
if (heartbeat !== 0)
|
|
111
|
+
return heartbeat;
|
|
112
|
+
return a.clientId.localeCompare(b.clientId);
|
|
113
|
+
}
|
|
114
|
+
function resolveHumanAuthorityCount(input) {
|
|
115
|
+
if (typeof input.humanAuthorityCount === 'number' && Number.isFinite(input.humanAuthorityCount)) {
|
|
116
|
+
return Math.max(0, Math.floor(input.humanAuthorityCount));
|
|
117
|
+
}
|
|
118
|
+
if (!input.humanAuthorities)
|
|
119
|
+
return undefined;
|
|
120
|
+
const unique = new Set();
|
|
121
|
+
for (const value of input.humanAuthorities) {
|
|
122
|
+
const normalized = normalizeText(value);
|
|
123
|
+
if (normalized)
|
|
124
|
+
unique.add(normalized);
|
|
125
|
+
}
|
|
126
|
+
return unique.size;
|
|
127
|
+
}
|
|
128
|
+
function toEpochMs(value) {
|
|
129
|
+
if (typeof value === 'number')
|
|
130
|
+
return value;
|
|
131
|
+
if (value instanceof Date)
|
|
132
|
+
return value.getTime();
|
|
133
|
+
return Date.parse(value);
|
|
134
|
+
}
|
|
135
|
+
function normalizeText(value) {
|
|
136
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
137
|
+
}
|
|
138
|
+
function hashString(value) {
|
|
139
|
+
let hash = 0x811c9dc5;
|
|
140
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
141
|
+
hash ^= value.charCodeAt(index);
|
|
142
|
+
hash = Math.imul(hash, 0x01000193);
|
|
143
|
+
}
|
|
144
|
+
return (hash >>> 0).toString(36);
|
|
145
|
+
}
|
|
@@ -4,6 +4,7 @@ export * from './auto-mode.js';
|
|
|
4
4
|
export * from './companion-model.js';
|
|
5
5
|
export * from './client-inbox-subscription.js';
|
|
6
6
|
export * from './control-plane.js';
|
|
7
|
+
export * from './coordination.js';
|
|
7
8
|
export * from './file-sync.js';
|
|
8
9
|
export * from './reconciler.js';
|
|
9
10
|
export * from './runtime.js';
|
|
@@ -4,6 +4,7 @@ export * from './auto-mode.js';
|
|
|
4
4
|
export * from './companion-model.js';
|
|
5
5
|
export * from './client-inbox-subscription.js';
|
|
6
6
|
export * from './control-plane.js';
|
|
7
|
+
export * from './coordination.js';
|
|
7
8
|
export * from './file-sync.js';
|
|
8
9
|
export * from './reconciler.js';
|
|
9
10
|
export * from './runtime.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentParticipantRole } from './turn-controller.js';
|
|
2
|
+
import { type ClientReconcilerLease, type ReconcilerOwner } from './coordination.js';
|
|
2
3
|
export type ThreadPolicyKind = 'direct' | 'auto' | 'symphony' | 'open_group' | 'review';
|
|
3
4
|
export type ThreadKind = 'main' | 'control' | 'worker' | 'review' | 'schedule' | 'schedule_run';
|
|
4
5
|
export type ReconcilerEventType = 'message.appended' | 'input.required' | 'approval.required' | 'inbox.notification.created' | 'inbox.notification.updated' | 'delivery.submitted' | 'delivery.completed' | 'delivery.failed' | 'schedule.tick' | 'worker.blocked' | 'change.requested' | 'issue.updated' | 'task.updated' | 'run.updated' | (string & {});
|
|
@@ -62,6 +63,9 @@ export interface ThreadControlEvent<TData extends Record<string, unknown> = Reco
|
|
|
62
63
|
}
|
|
63
64
|
export interface ThreadPolicy {
|
|
64
65
|
kind: ThreadPolicyKind;
|
|
66
|
+
reconcilerOwner?: ReconcilerOwner;
|
|
67
|
+
humanAuthorities?: string[];
|
|
68
|
+
humanAuthorityCount?: number;
|
|
65
69
|
secretaryAgent?: string;
|
|
66
70
|
defaultAssistantAgent?: string;
|
|
67
71
|
assignedWorkerAgent?: string;
|
|
@@ -114,6 +118,7 @@ export interface ReconcilerNotificationEvent {
|
|
|
114
118
|
export interface ReconcileDecision {
|
|
115
119
|
id: string;
|
|
116
120
|
policyKind: ThreadPolicyKind;
|
|
121
|
+
reconcilerOwner: ReconcilerOwner;
|
|
117
122
|
event: ThreadControlEvent;
|
|
118
123
|
placement: ThreadPlacement;
|
|
119
124
|
wakeJobs: WakeJob[];
|
|
@@ -140,6 +145,7 @@ export interface WakeJobSummary {
|
|
|
140
145
|
export interface ReconcileDecisionSummary {
|
|
141
146
|
id: string;
|
|
142
147
|
policyKind: ThreadPolicyKind;
|
|
148
|
+
reconcilerOwner: ReconcilerOwner;
|
|
143
149
|
eventType: ReconcilerEventType;
|
|
144
150
|
thread: string;
|
|
145
151
|
chat?: string;
|
|
@@ -151,6 +157,11 @@ export interface ReconcileDecisionSummary {
|
|
|
151
157
|
export interface ReconcileThreadEventInput {
|
|
152
158
|
policy: ThreadPolicyKind | ThreadPolicy;
|
|
153
159
|
event: ThreadControlEvent;
|
|
160
|
+
reconcilerOwner?: ReconcilerOwner;
|
|
161
|
+
humanAuthorities?: string[];
|
|
162
|
+
humanAuthorityCount?: number;
|
|
163
|
+
clientReconcilerLease?: ClientReconcilerLease | null;
|
|
164
|
+
requireClientReconcilerLease?: boolean;
|
|
154
165
|
chat?: string;
|
|
155
166
|
thread?: string;
|
|
156
167
|
now?: Date;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canClientCoordinateThread, resolveReconcilerOwnership, } from './coordination.js';
|
|
1
2
|
const DEFAULT_SECRETARY_AGENT = '__secretary__';
|
|
2
3
|
const DEFAULT_ASSISTANT_AGENT = 'primary-agent';
|
|
3
4
|
const DEFAULT_REVIEWER_AGENT = 'ai-reviewer';
|
|
@@ -24,12 +25,27 @@ export function reconcileThreadEvent(input) {
|
|
|
24
25
|
thread: input.thread,
|
|
25
26
|
randomId: input.randomId,
|
|
26
27
|
});
|
|
27
|
-
const
|
|
28
|
+
const ownership = resolveReconcilerOwnership({
|
|
29
|
+
policyKind: policy.kind,
|
|
30
|
+
humanAuthorities: input.humanAuthorities ?? policy.humanAuthorities,
|
|
31
|
+
humanAuthorityCount: input.humanAuthorityCount ?? policy.humanAuthorityCount,
|
|
32
|
+
reconcilerOwner: input.reconcilerOwner ?? policy.reconcilerOwner,
|
|
33
|
+
});
|
|
34
|
+
const clientCoordinationSkip = resolveClientCoordinationSkip({
|
|
35
|
+
ownership,
|
|
36
|
+
input,
|
|
37
|
+
placement,
|
|
38
|
+
createdAt,
|
|
39
|
+
});
|
|
40
|
+
const jobs = clientCoordinationSkip
|
|
41
|
+
? []
|
|
42
|
+
: selectWakeJobs(policy, event, placement, createdAt, input.randomId, input.client);
|
|
28
43
|
const notificationEvents = selectNotificationEvents(event, placement, createdAt, input.randomId);
|
|
29
|
-
const skippedReason = jobs.length === 0 ? skipReasonFor(policy, event, input.client) : undefined;
|
|
44
|
+
const skippedReason = clientCoordinationSkip ?? (jobs.length === 0 ? skipReasonFor(policy, event, input.client) : undefined);
|
|
30
45
|
return {
|
|
31
46
|
id: createReconcilerId('decision', input.randomId),
|
|
32
47
|
policyKind: policy.kind,
|
|
48
|
+
reconcilerOwner: ownership.reconcilerOwner,
|
|
33
49
|
event,
|
|
34
50
|
placement,
|
|
35
51
|
wakeJobs: jobs,
|
|
@@ -42,6 +58,7 @@ export function summarizeReconcileDecision(decision) {
|
|
|
42
58
|
return {
|
|
43
59
|
id: decision.id,
|
|
44
60
|
policyKind: decision.policyKind,
|
|
61
|
+
reconcilerOwner: decision.reconcilerOwner,
|
|
45
62
|
eventType: decision.event.type,
|
|
46
63
|
thread: decision.placement.thread,
|
|
47
64
|
...(decision.placement.chat ? { chat: decision.placement.chat } : {}),
|
|
@@ -120,6 +137,27 @@ export function resolveThreadPlacement(input) {
|
|
|
120
137
|
kind: 'control',
|
|
121
138
|
};
|
|
122
139
|
}
|
|
140
|
+
function resolveClientCoordinationSkip(input) {
|
|
141
|
+
if (input.ownership.reconcilerOwner !== 'client') {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
if (!input.input.requireClientReconcilerLease && !input.input.clientReconcilerLease) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const clientId = input.input.client?.id;
|
|
148
|
+
if (canClientCoordinateThread({
|
|
149
|
+
clientId,
|
|
150
|
+
thread: input.placement.thread,
|
|
151
|
+
lease: input.input.clientReconcilerLease,
|
|
152
|
+
now: input.createdAt,
|
|
153
|
+
})) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const owner = input.input.clientReconcilerLease?.ownerClientId;
|
|
157
|
+
return owner
|
|
158
|
+
? `Client-owned Reconciler is leased by ${owner}; client ${clientId ?? 'unknown'} must not reconcile ${input.placement.thread}.`
|
|
159
|
+
: `Client-owned Reconciler requires an active client coordinator lease for ${input.placement.thread}.`;
|
|
160
|
+
}
|
|
123
161
|
function selectWakeJobs(policy, event, placement, createdAt, randomId, client) {
|
|
124
162
|
if (policy.kind === 'direct') {
|
|
125
163
|
return isUserMessage(event)
|
|
@@ -127,7 +165,7 @@ function selectWakeJobs(policy, event, placement, createdAt, randomId, client) {
|
|
|
127
165
|
targetAgent: policy.defaultAssistantAgent ?? DEFAULT_ASSISTANT_AGENT,
|
|
128
166
|
targetRole: 'primary-agent',
|
|
129
167
|
priority: 'normal',
|
|
130
|
-
reason: '
|
|
168
|
+
reason: 'Single-human assistant surfaces route user messages to the default assistant.',
|
|
131
169
|
createdAt,
|
|
132
170
|
randomId,
|
|
133
171
|
})]
|
|
@@ -11,7 +11,7 @@ export declare const SYMPHONY_ISSUE_FILE_NAME = "issue.json";
|
|
|
11
11
|
export declare const SYMPHONY_TASK_FILE_NAME = "task.json";
|
|
12
12
|
export declare const SYMPHONY_DELIVERY_FILE_NAME = "delivery.json";
|
|
13
13
|
export declare const SYMPHONY_SESSION_FILE_NAME = "session.json";
|
|
14
|
-
export type
|
|
14
|
+
export type WorkerWorkspaceKind = 'git' | 'folder';
|
|
15
15
|
export type SymphonyIdeaStatus = 'captured' | 'exploring' | 'candidate' | 'promoted' | 'deferred' | 'rejected' | 'superseded';
|
|
16
16
|
export type SymphonyIdeaCommitment = 'thought' | 'direction' | 'tentative_decision' | 'committed';
|
|
17
17
|
export type SymphonyIssueStatus = 'open' | 'triaging' | 'in_progress' | 'blocked' | 'resolved' | 'closed';
|
|
@@ -23,13 +23,13 @@ export type SymphonyResourceKind = 'idea' | 'issue' | 'task' | 'delivery' | 'ses
|
|
|
23
23
|
export interface SymphonyReconcilerState {
|
|
24
24
|
decisions: ReconcileDecisionSummary[];
|
|
25
25
|
}
|
|
26
|
-
export interface
|
|
26
|
+
export interface WorkerWorkspaceRef {
|
|
27
27
|
path: string;
|
|
28
|
-
kind:
|
|
28
|
+
kind: WorkerWorkspaceKind;
|
|
29
29
|
repository?: string;
|
|
30
30
|
branch?: string;
|
|
31
31
|
worktree?: string;
|
|
32
|
-
|
|
32
|
+
workspace?: string;
|
|
33
33
|
baseRevision?: string;
|
|
34
34
|
environment?: SymphonyWorkerEnvironmentRef;
|
|
35
35
|
}
|
|
@@ -59,6 +59,7 @@ export type SymphonyDelegationTargetSource = 'active-session' | 'group-chat' | '
|
|
|
59
59
|
export interface SymphonyDelegationTarget extends SymphonyChatThreadRef {
|
|
60
60
|
source: SymphonyDelegationTargetSource;
|
|
61
61
|
backend: AutoModeWorkerBackend;
|
|
62
|
+
contact?: string;
|
|
62
63
|
agent?: string;
|
|
63
64
|
label?: string;
|
|
64
65
|
}
|
|
@@ -104,6 +105,7 @@ export interface SymphonyTaskRecord extends SymphonyChatThreadRef {
|
|
|
104
105
|
status: SymphonyTaskStatus;
|
|
105
106
|
target: SymphonyDelegationTarget;
|
|
106
107
|
backend: AutoModeWorkerBackend;
|
|
108
|
+
contact?: string;
|
|
107
109
|
agent?: string;
|
|
108
110
|
delivery: string;
|
|
109
111
|
session: string;
|
|
@@ -145,7 +147,7 @@ export interface SymphonySessionRecord extends SymphonyChatThreadRef {
|
|
|
145
147
|
secretaryAutoEnabled?: boolean;
|
|
146
148
|
status: SymphonySessionStatus;
|
|
147
149
|
cwd: string;
|
|
148
|
-
|
|
150
|
+
workspaceRef?: WorkerWorkspaceRef;
|
|
149
151
|
target: SymphonyDelegationTarget;
|
|
150
152
|
model?: string;
|
|
151
153
|
supervisor?: SymphonySupervisorPolicy;
|
|
@@ -178,18 +180,18 @@ export interface SymphonyWorkerSpec extends Partial<SymphonyDelegationTarget> {
|
|
|
178
180
|
acceptanceCriteria?: string[];
|
|
179
181
|
model?: string;
|
|
180
182
|
supervisorIntervalMs?: number;
|
|
181
|
-
workspace?: Partial<
|
|
183
|
+
workspace?: Partial<WorkerWorkspaceRef>;
|
|
182
184
|
}
|
|
183
185
|
export interface CreateSymphonyRunPlanInput {
|
|
184
186
|
objective: string;
|
|
185
187
|
title?: string;
|
|
186
188
|
acceptanceCriteria?: string[];
|
|
187
189
|
workspacePath: string;
|
|
188
|
-
workspaceKind?:
|
|
190
|
+
workspaceKind?: WorkerWorkspaceKind;
|
|
189
191
|
repository?: string;
|
|
190
192
|
branch?: string;
|
|
191
193
|
worktree?: string;
|
|
192
|
-
|
|
194
|
+
workspace?: string;
|
|
193
195
|
baseRevision?: string;
|
|
194
196
|
environment?: Partial<SymphonyWorkerEnvironmentRef>;
|
|
195
197
|
backend: AutoModeWorkerBackend;
|
|
@@ -240,7 +242,7 @@ export declare function renderSymphonyRuntimePrompt(input: {
|
|
|
240
242
|
task: string;
|
|
241
243
|
objective: string;
|
|
242
244
|
acceptanceCriteria?: string[];
|
|
243
|
-
workspace:
|
|
245
|
+
workspace: WorkerWorkspaceRef;
|
|
244
246
|
backend: AutoModeWorkerBackend;
|
|
245
247
|
mode: AutoModeMode;
|
|
246
248
|
secretaryAutoEnabled?: boolean;
|
|
@@ -72,7 +72,7 @@ export function createRunPlan(input) {
|
|
|
72
72
|
...(normalizeOptionalText(input.repository) ? { repository: normalizeOptionalText(input.repository) } : {}),
|
|
73
73
|
...(normalizeOptionalText(input.branch) ? { branch: normalizeOptionalText(input.branch) } : {}),
|
|
74
74
|
...(normalizeOptionalText(input.worktree) ? { worktree: normalizeOptionalText(input.worktree) } : {}),
|
|
75
|
-
...(normalizeOptionalText(input.
|
|
75
|
+
...(normalizeOptionalText(input.workspace) ? { workspace: normalizeOptionalText(input.workspace) } : {}),
|
|
76
76
|
...(normalizeOptionalText(input.baseRevision) ? { baseRevision: normalizeOptionalText(input.baseRevision) } : {}),
|
|
77
77
|
environment: normalizeSymphonyWorkerEnvironment(input.environment, input.backend),
|
|
78
78
|
};
|
|
@@ -107,7 +107,7 @@ export function createRunPlan(input) {
|
|
|
107
107
|
thread: target.thread ?? chatThread.thread,
|
|
108
108
|
messages: target.messages ?? chatThread.messages,
|
|
109
109
|
});
|
|
110
|
-
const targetAgent = target.agent ??
|
|
110
|
+
const targetAgent = target.agent ?? target.contact ?? target.backend;
|
|
111
111
|
const dispatchReconciler = createSymphonyDispatchReconcilerState({
|
|
112
112
|
issue: issueUri,
|
|
113
113
|
task: uris.task,
|
|
@@ -146,7 +146,7 @@ export function createRunPlan(input) {
|
|
|
146
146
|
...(input.secretaryAutoEnabled !== undefined ? { secretaryAutoEnabled: input.secretaryAutoEnabled } : {}),
|
|
147
147
|
status: 'planned',
|
|
148
148
|
cwd: workerWorkspace.path,
|
|
149
|
-
|
|
149
|
+
workspaceRef: workerWorkspace,
|
|
150
150
|
target,
|
|
151
151
|
...(spec.model ? { model: spec.model } : {}),
|
|
152
152
|
...(spec.supervisor ? { supervisor: spec.supervisor } : {}),
|
|
@@ -233,12 +233,13 @@ export function renderSymphonyRuntimePrompt(input) {
|
|
|
233
233
|
...(input.issuer?.thread ? [`Issuer thread: ${input.issuer.thread}`] : []),
|
|
234
234
|
...(input.target?.chat ? [`Target chat: ${input.target.chat}`] : []),
|
|
235
235
|
...(input.target?.thread ? [`Target thread: ${input.target.thread}`] : []),
|
|
236
|
+
...(input.target?.contact ? [`Target contact: ${input.target.contact}`] : []),
|
|
236
237
|
...(input.target?.agent ? [`Target agent: ${input.target.agent}`] : []),
|
|
237
238
|
...(input.workerIndex && input.workerCount ? [`Worker: ${input.workerIndex}/${input.workerCount}`] : []),
|
|
238
239
|
...(workThread ? [`Work thread: ${workThread}`] : []),
|
|
239
240
|
`Workspace: ${input.workspace.path}`,
|
|
240
241
|
`Workspace kind: ${input.workspace.kind}`,
|
|
241
|
-
...(input.workspace.
|
|
242
|
+
...(input.workspace.workspace ? [`Workspace resource: ${input.workspace.workspace}`] : []),
|
|
242
243
|
...(input.workspace.repository ? [`Workspace repository: ${input.workspace.repository}`] : []),
|
|
243
244
|
...(input.workspace.branch ? [`Workspace branch: ${input.workspace.branch}`] : []),
|
|
244
245
|
...(input.workspace.baseRevision ? [`Workspace base revision: ${input.workspace.baseRevision}`] : []),
|
|
@@ -405,7 +406,7 @@ function normalizeSymphonyWorkerWorkspace(root, override, backend) {
|
|
|
405
406
|
...(normalizeOptionalText(override?.repository ?? root.repository) ? { repository: normalizeOptionalText(override?.repository ?? root.repository) } : {}),
|
|
406
407
|
...(normalizeOptionalText(override?.branch ?? root.branch) ? { branch: normalizeOptionalText(override?.branch ?? root.branch) } : {}),
|
|
407
408
|
...(normalizeOptionalText(override?.worktree ?? root.worktree) ? { worktree: normalizeOptionalText(override?.worktree ?? root.worktree) } : {}),
|
|
408
|
-
...(normalizeOptionalText(override?.
|
|
409
|
+
...(normalizeOptionalText(override?.workspace ?? root.workspace) ? { workspace: normalizeOptionalText(override?.workspace ?? root.workspace) } : {}),
|
|
409
410
|
...(normalizeOptionalText(override?.baseRevision ?? root.baseRevision) ? { baseRevision: normalizeOptionalText(override?.baseRevision ?? root.baseRevision) } : {}),
|
|
410
411
|
environment: normalizeSymphonyWorkerEnvironment(override?.environment ?? root.environment, backend),
|
|
411
412
|
};
|
|
@@ -488,12 +489,14 @@ function createSymphonySupervisorPolicy(intervalMs) {
|
|
|
488
489
|
}
|
|
489
490
|
function normalizeSymphonyDelegationTarget(input) {
|
|
490
491
|
const explicit = input.target ?? {};
|
|
492
|
+
const backend = explicit.backend ?? input.backend;
|
|
491
493
|
const chatThread = normalizeSymphonyChatThreadRef({
|
|
492
494
|
chat: explicit.chat ?? input.chat,
|
|
493
495
|
thread: explicit.thread ?? input.thread,
|
|
494
496
|
messages: explicit.messages ?? input.messages,
|
|
495
497
|
});
|
|
496
|
-
const
|
|
498
|
+
const contact = normalizeOptionalText(explicit.contact) ?? normalizeOptionalText(explicit.agent) ?? backend;
|
|
499
|
+
const agent = normalizeOptionalText(explicit.agent) ?? contact;
|
|
497
500
|
const label = normalizeOptionalText(explicit.label);
|
|
498
501
|
const source = explicit.source
|
|
499
502
|
?? (chatThread.chat || chatThread.thread
|
|
@@ -503,7 +506,8 @@ function normalizeSymphonyDelegationTarget(input) {
|
|
|
503
506
|
: 'default');
|
|
504
507
|
return {
|
|
505
508
|
source,
|
|
506
|
-
backend
|
|
509
|
+
backend,
|
|
510
|
+
contact,
|
|
507
511
|
agent,
|
|
508
512
|
...(label ? { label } : {}),
|
|
509
513
|
...chatThread,
|