scene-capability-engine 3.6.65 → 3.6.67
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/CHANGELOG.md +16 -0
- package/README.md +17 -6
- package/README.zh.md +18 -6
- package/bin/scene-capability-engine.js +4 -0
- package/docs/README.md +2 -2
- package/docs/command-reference.md +382 -6
- package/docs/document-governance.md +3 -2
- package/docs/integration-modes.md +62 -478
- package/docs/integration-philosophy.md +56 -263
- package/docs/magicball-project-portfolio-contract.md +114 -2
- package/docs/project-management/README.md +14 -0
- package/docs/project-management/assurance/backup.md +3 -0
- package/docs/project-management/assurance/config.md +3 -0
- package/docs/project-management/assurance/evidence/README.md +3 -0
- package/docs/project-management/assurance/incidents/README.md +3 -0
- package/docs/project-management/assurance/logs.md +3 -0
- package/docs/project-management/assurance/overview.md +3 -0
- package/docs/project-management/assurance/recovery/README.md +3 -0
- package/docs/project-management/assurance/resource.md +3 -0
- package/docs/project-management/assurance/runbooks/README.md +3 -0
- package/docs/project-management/delivery/acceptance/README.md +3 -0
- package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
- package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
- package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
- package/docs/project-management/delivery/documents/changes.md +3 -0
- package/docs/project-management/delivery/documents/issues.md +3 -0
- package/docs/project-management/delivery/documents/overview.md +3 -0
- package/docs/project-management/delivery/documents/planning.md +3 -0
- package/docs/project-management/delivery/documents/requirements.md +3 -0
- package/docs/project-management/delivery/documents/tracking.md +3 -0
- package/docs/project-management/delivery/handoffs/README.md +3 -0
- package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
- package/docs/project-management/delivery/handoffs/records/README.md +3 -0
- package/docs/project-management/delivery/overview.md +10 -0
- package/docs/project-management/delivery/releases/README.md +3 -0
- package/docs/project-management/delivery/releases/baselines/README.md +3 -0
- package/docs/project-management/delivery/releases/evidence/README.md +3 -0
- package/docs/project-management/delivery/tables/changes.md +3 -0
- package/docs/project-management/delivery/tables/issues.md +3 -0
- package/docs/project-management/delivery/tables/planning.md +3 -0
- package/docs/project-management/delivery/tables/requirements.md +3 -0
- package/docs/project-management/delivery/tables/tracking.md +3 -0
- package/docs/project-management/environment/agent-discovery.md +3 -0
- package/docs/project-management/environment/development.md +3 -0
- package/docs/project-management/environment/overview.md +10 -0
- package/docs/project-management/environment/testing.md +3 -0
- package/docs/project-management/environment/version-alignment.md +3 -0
- package/docs/quick-start-with-ai-tools.md +68 -308
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.66.md +23 -0
- package/docs/releases/v3.6.67.md +23 -0
- package/docs/steering-governance.md +64 -2
- package/docs/zh/README.md +2 -2
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.66.md +23 -0
- package/docs/zh/releases/v3.6.67.md +23 -0
- package/lib/commands/adopt.js +24 -0
- package/lib/commands/native.js +158 -0
- package/lib/commands/project.js +95 -0
- package/lib/commands/semantic.js +1459 -0
- package/lib/commands/session.js +74 -3
- package/lib/commands/spec-bootstrap.js +10 -1
- package/lib/commands/spec-gate.js +10 -1
- package/lib/commands/spec-pipeline.js +10 -1
- package/lib/commands/studio.js +405 -30
- package/lib/commands/task.js +141 -7
- package/lib/governance/supreme-principles.js +530 -0
- package/lib/problem/problem-evaluator.js +4 -0
- package/lib/project/candidate-inspection-service.js +24 -1
- package/lib/project/portfolio-projection-service.js +315 -5
- package/lib/project/project-channel-output.js +94 -0
- package/lib/project/project-channel-projection.js +181 -0
- package/lib/project/root-onboarding-service.js +60 -8
- package/lib/project/semantic-shared-source-projection.js +150 -0
- package/lib/project/supervision-action-model.js +277 -0
- package/lib/project/supervision-projection-service.js +305 -5
- package/lib/project/target-resolution-service.js +70 -5
- package/lib/project/visibility-policy.js +93 -0
- package/lib/runtime/multi-spec-scene-session.js +8 -1
- package/lib/runtime/project-channel-context-store.js +387 -0
- package/lib/runtime/project-channel-context.js +406 -0
- package/lib/runtime/scene-session-binding.js +46 -0
- package/lib/runtime/session-store.js +186 -0
- package/lib/runtime/steering-contract.js +7 -1
- package/lib/semantic/archive-report.js +283 -0
- package/lib/semantic/archive-routing.js +67 -0
- package/lib/semantic/backflow-report.js +245 -0
- package/lib/semantic/capability-contract.js +30 -0
- package/lib/semantic/delta-export.js +145 -0
- package/lib/semantic/interaction-observer.js +254 -0
- package/lib/semantic/kernel-loader.js +881 -0
- package/lib/semantic/native-runtime.js +359 -0
- package/lib/semantic/progress-ledger.js +433 -0
- package/lib/semantic/replay-evaluator.js +382 -0
- package/lib/semantic/shared-publication.js +592 -0
- package/lib/semantic/shared-source-config.js +183 -0
- package/lib/semantic/shared-source-connect.js +139 -0
- package/lib/semantic/shared-source-discovery.js +98 -0
- package/lib/semantic/shared-sync-export.js +413 -0
- package/lib/semantic/shared-sync-intake.js +592 -0
- package/lib/semantic/shared-sync-merge.js +547 -0
- package/lib/semantic/shared-sync-release.js +463 -0
- package/lib/semantic/supreme-intent-report.js +300 -0
- package/lib/state/sce-state-store.js +1360 -0
- package/lib/steering/context-sync-manager.js +276 -25
- package/lib/studio/spec-intake-governor.js +39 -3
- package/lib/studio/task-envelope.js +35 -2
- package/lib/workspace/takeover-baseline.js +342 -83
- package/package.json +7 -2
- package/scripts/agent-governance-baseline-audit.js +395 -0
- package/scripts/clarification-first-audit.js +9 -9
- package/scripts/deprecated-entry-audit.js +240 -0
- package/scripts/release-posture-report.js +262 -0
- package/template/.sce/README.md +62 -228
- package/template/.sce/config/semantic-shared-sources.json +5 -0
- package/template/.sce/config/supreme-principles-policy.json +105 -0
- package/template/.sce/config/takeover-baseline.json +7 -0
- package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
- package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
- package/template/.sce/steering/RULES_GUIDE.md +17 -9
- package/template/README.md +32 -96
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
const DEFAULT_PROJECT_ID = 'project';
|
|
2
|
+
const DEFAULT_CHANNEL_ID = 'channel-default';
|
|
3
|
+
const DEFAULT_ACTIVE_SCENE = 'scene.default';
|
|
4
|
+
const DEFAULT_ACTIVE_SPEC_ID = 'spec.generic';
|
|
5
|
+
const DEFAULT_ACTIVE_DOC = 'requirements';
|
|
6
|
+
const DEFAULT_RUN_STATE = 'idle';
|
|
7
|
+
const RUN_STATE_VALUES = new Set(['idle', 'running', 'blocked', 'completed', 'failed']);
|
|
8
|
+
|
|
9
|
+
function nowIso() {
|
|
10
|
+
return new Date().toISOString();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeString(value) {
|
|
14
|
+
if (typeof value !== 'string') {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeDraftInput(value) {
|
|
21
|
+
if (value == null) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
return typeof value === 'string' ? value : `${value}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeStringArray(values) {
|
|
28
|
+
if (!Array.isArray(values)) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const normalized = [];
|
|
34
|
+
for (const value of values) {
|
|
35
|
+
const item = normalizeString(value);
|
|
36
|
+
if (!item || seen.has(item)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
seen.add(item);
|
|
40
|
+
normalized.push(item);
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function safePathSegment(value, fallback) {
|
|
46
|
+
const normalized = `${value || ''}`
|
|
47
|
+
.trim()
|
|
48
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
49
|
+
.replace(/^-+|-+$/g, '');
|
|
50
|
+
return normalized || fallback;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeRunState(value, fallback = DEFAULT_RUN_STATE) {
|
|
54
|
+
const normalized = normalizeString(value).toLowerCase();
|
|
55
|
+
if (!RUN_STATE_VALUES.has(normalized)) {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeDateTime(value, fallback = null) {
|
|
62
|
+
const candidate = normalizeString(value);
|
|
63
|
+
if (!candidate) {
|
|
64
|
+
return fallback || nowIso();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const parsed = Date.parse(candidate);
|
|
68
|
+
if (!Number.isFinite(parsed)) {
|
|
69
|
+
return fallback || nowIso();
|
|
70
|
+
}
|
|
71
|
+
return new Date(parsed).toISOString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function pickFirstString(candidates = [], fallback = '') {
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const normalized = normalizeString(candidate);
|
|
77
|
+
if (normalized) {
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeAgentRuntime(agentRuntime) {
|
|
85
|
+
if (!agentRuntime || typeof agentRuntime !== 'object') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const agentId = normalizeString(agentRuntime.agentId);
|
|
90
|
+
const backendProfile = normalizeString(agentRuntime.backendProfile);
|
|
91
|
+
const runtimeSessionId = normalizeString(agentRuntime.runtimeSessionId);
|
|
92
|
+
|
|
93
|
+
if (!agentId || !backendProfile || !runtimeSessionId) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
agentId,
|
|
99
|
+
backendProfile,
|
|
100
|
+
runtimeSessionId
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildDefaultSessionPath(projectId, channelId) {
|
|
105
|
+
const safeProjectId = safePathSegment(projectId, DEFAULT_PROJECT_ID);
|
|
106
|
+
const safeChannelId = safePathSegment(channelId, DEFAULT_CHANNEL_ID);
|
|
107
|
+
return `.sce/sessions/${safeProjectId}/${safeChannelId}/session.json`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createDefaultChannelState(channelId, options = {}) {
|
|
111
|
+
const defaults = options.defaults && typeof options.defaults === 'object'
|
|
112
|
+
? options.defaults
|
|
113
|
+
: {};
|
|
114
|
+
const normalizedChannelId = pickFirstString([channelId, defaults.channelId], DEFAULT_CHANNEL_ID);
|
|
115
|
+
const projectId = pickFirstString([options.projectId, defaults.projectId], DEFAULT_PROJECT_ID);
|
|
116
|
+
const activeSpecId = pickFirstString(
|
|
117
|
+
[options.activeSpecId, defaults.activeSpecId],
|
|
118
|
+
DEFAULT_ACTIVE_SPEC_ID
|
|
119
|
+
);
|
|
120
|
+
const activeDoc = pickFirstString(
|
|
121
|
+
[options.activeDoc, defaults.activeDoc],
|
|
122
|
+
DEFAULT_ACTIVE_DOC
|
|
123
|
+
);
|
|
124
|
+
const updatedAt = normalizeDateTime(
|
|
125
|
+
options.updatedAt || defaults.updatedAt,
|
|
126
|
+
normalizeDateTime(options.now)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const state = {
|
|
130
|
+
channelId: normalizedChannelId,
|
|
131
|
+
activeScene: pickFirstString(
|
|
132
|
+
[options.activeScene, defaults.activeScene],
|
|
133
|
+
DEFAULT_ACTIVE_SCENE
|
|
134
|
+
),
|
|
135
|
+
activeSpecId,
|
|
136
|
+
activeDoc,
|
|
137
|
+
activeSessionPath: pickFirstString(
|
|
138
|
+
[options.activeSessionPath, defaults.activeSessionPath],
|
|
139
|
+
buildDefaultSessionPath(projectId, normalizedChannelId)
|
|
140
|
+
),
|
|
141
|
+
openedTabs: normalizeStringArray(
|
|
142
|
+
options.openedTabs !== undefined ? options.openedTabs : defaults.openedTabs
|
|
143
|
+
),
|
|
144
|
+
selectedTreeNodeId: pickFirstString(
|
|
145
|
+
[options.selectedTreeNodeId, defaults.selectedTreeNodeId],
|
|
146
|
+
`spec:${activeSpecId}`
|
|
147
|
+
),
|
|
148
|
+
draftInput: normalizeDraftInput(
|
|
149
|
+
options.draftInput !== undefined ? options.draftInput : defaults.draftInput
|
|
150
|
+
),
|
|
151
|
+
runState: normalizeRunState(
|
|
152
|
+
options.runState !== undefined ? options.runState : defaults.runState,
|
|
153
|
+
DEFAULT_RUN_STATE
|
|
154
|
+
),
|
|
155
|
+
updatedAt
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const agentRuntime = normalizeAgentRuntime(
|
|
159
|
+
options.agentRuntime !== undefined ? options.agentRuntime : defaults.agentRuntime
|
|
160
|
+
);
|
|
161
|
+
if (agentRuntime) {
|
|
162
|
+
state.agentRuntime = agentRuntime;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return state;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeChannelState(channelId, state = {}, options = {}) {
|
|
169
|
+
const payload = state && typeof state === 'object' ? state : {};
|
|
170
|
+
const defaults = options.defaults && typeof options.defaults === 'object'
|
|
171
|
+
? options.defaults
|
|
172
|
+
: {};
|
|
173
|
+
const normalizedChannelId = pickFirstString(
|
|
174
|
+
[payload.channelId, channelId, defaults.channelId],
|
|
175
|
+
DEFAULT_CHANNEL_ID
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const mergedAgentRuntime = payload.agentRuntime && defaults.agentRuntime
|
|
179
|
+
? {
|
|
180
|
+
...defaults.agentRuntime,
|
|
181
|
+
...payload.agentRuntime
|
|
182
|
+
}
|
|
183
|
+
: (payload.agentRuntime || defaults.agentRuntime);
|
|
184
|
+
|
|
185
|
+
return createDefaultChannelState(normalizedChannelId, {
|
|
186
|
+
projectId: options.projectId,
|
|
187
|
+
now: options.now,
|
|
188
|
+
defaults,
|
|
189
|
+
activeScene: payload.activeScene,
|
|
190
|
+
activeSpecId: payload.activeSpecId,
|
|
191
|
+
activeDoc: payload.activeDoc,
|
|
192
|
+
activeSessionPath: payload.activeSessionPath,
|
|
193
|
+
openedTabs: payload.openedTabs,
|
|
194
|
+
selectedTreeNodeId: payload.selectedTreeNodeId,
|
|
195
|
+
draftInput: payload.draftInput,
|
|
196
|
+
runState: payload.runState,
|
|
197
|
+
updatedAt: payload.updatedAt,
|
|
198
|
+
agentRuntime: mergedAgentRuntime
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function chooseFocusedChannelId(channels, focusedChannelId, preferredChannelId = '') {
|
|
203
|
+
if (!channels || typeof channels !== 'object') {
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const ids = Object.keys(channels).filter(Boolean);
|
|
208
|
+
if (ids.length === 0) {
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const normalizedFocused = normalizeString(focusedChannelId);
|
|
213
|
+
if (normalizedFocused && channels[normalizedFocused]) {
|
|
214
|
+
return normalizedFocused;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const normalizedPreferred = normalizeString(preferredChannelId);
|
|
218
|
+
if (normalizedPreferred && channels[normalizedPreferred]) {
|
|
219
|
+
return normalizedPreferred;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return [...ids].sort((left, right) => left.localeCompare(right))[0];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function normalizeProjectChannelContext(context = {}, options = {}) {
|
|
226
|
+
const payload = context && typeof context === 'object' ? context : {};
|
|
227
|
+
const projectId = pickFirstString(
|
|
228
|
+
[payload.projectId, options.projectId],
|
|
229
|
+
DEFAULT_PROJECT_ID
|
|
230
|
+
);
|
|
231
|
+
const allowDefaultChannelSynthesis = options.allowDefaultChannelSynthesis !== false;
|
|
232
|
+
const defaultChannelId = pickFirstString(
|
|
233
|
+
[options.defaultChannelId, payload.focusedChannelId],
|
|
234
|
+
DEFAULT_CHANNEL_ID
|
|
235
|
+
);
|
|
236
|
+
const channelDefaults = options.channelDefaults && typeof options.channelDefaults === 'object'
|
|
237
|
+
? options.channelDefaults
|
|
238
|
+
: {};
|
|
239
|
+
|
|
240
|
+
const nextChannels = {};
|
|
241
|
+
const sourceChannels = payload.channels && typeof payload.channels === 'object'
|
|
242
|
+
? payload.channels
|
|
243
|
+
: {};
|
|
244
|
+
|
|
245
|
+
for (const [channelKey, rawState] of Object.entries(sourceChannels)) {
|
|
246
|
+
const resolvedDefaults = channelDefaults[channelKey] && typeof channelDefaults[channelKey] === 'object'
|
|
247
|
+
? channelDefaults[channelKey]
|
|
248
|
+
: {};
|
|
249
|
+
const channelState = normalizeChannelState(channelKey, rawState, {
|
|
250
|
+
projectId,
|
|
251
|
+
now: options.now,
|
|
252
|
+
defaults: resolvedDefaults
|
|
253
|
+
});
|
|
254
|
+
nextChannels[channelState.channelId] = channelState;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (Object.keys(nextChannels).length === 0 && allowDefaultChannelSynthesis) {
|
|
258
|
+
const resolvedDefaults = channelDefaults[defaultChannelId] && typeof channelDefaults[defaultChannelId] === 'object'
|
|
259
|
+
? channelDefaults[defaultChannelId]
|
|
260
|
+
: {};
|
|
261
|
+
const defaultState = createDefaultChannelState(defaultChannelId, {
|
|
262
|
+
projectId,
|
|
263
|
+
now: options.now,
|
|
264
|
+
defaults: resolvedDefaults
|
|
265
|
+
});
|
|
266
|
+
nextChannels[defaultState.channelId] = defaultState;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const focusedChannelId = chooseFocusedChannelId(
|
|
270
|
+
nextChannels,
|
|
271
|
+
payload.focusedChannelId,
|
|
272
|
+
defaultChannelId
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
projectId,
|
|
277
|
+
focusedChannelId,
|
|
278
|
+
channels: nextChannels
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function listChannelIds(context = {}) {
|
|
283
|
+
const normalized = normalizeProjectChannelContext(context);
|
|
284
|
+
return Object.keys(normalized.channels).sort((left, right) => left.localeCompare(right));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getChannelState(context = {}, channelId) {
|
|
288
|
+
const normalized = normalizeProjectChannelContext(context);
|
|
289
|
+
const normalizedChannelId = normalizeString(channelId);
|
|
290
|
+
if (!normalizedChannelId || !normalized.channels[normalizedChannelId]) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return normalized.channels[normalizedChannelId];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getFocusedChannelState(context = {}) {
|
|
297
|
+
const normalized = normalizeProjectChannelContext(context);
|
|
298
|
+
return normalized.channels[normalized.focusedChannelId] || null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function setFocusedChannel(context = {}, channelId, options = {}) {
|
|
302
|
+
const requestedChannelId = normalizeString(channelId);
|
|
303
|
+
if (!requestedChannelId) {
|
|
304
|
+
throw new Error('channelId is required');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const normalized = normalizeProjectChannelContext(context, {
|
|
308
|
+
projectId: options.projectId,
|
|
309
|
+
defaultChannelId: options.defaultChannelId,
|
|
310
|
+
now: options.now,
|
|
311
|
+
channelDefaults: options.channelDefaults
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const nextChannels = { ...normalized.channels };
|
|
315
|
+
if (!nextChannels[requestedChannelId]) {
|
|
316
|
+
if (options.createIfMissing !== true) {
|
|
317
|
+
throw new Error(`Channel not found: ${requestedChannelId}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const defaults = options.channelDefaults && options.channelDefaults[requestedChannelId]
|
|
321
|
+
? options.channelDefaults[requestedChannelId]
|
|
322
|
+
: (options.channelState || {});
|
|
323
|
+
nextChannels[requestedChannelId] = normalizeChannelState(requestedChannelId, defaults, {
|
|
324
|
+
projectId: normalized.projectId,
|
|
325
|
+
now: options.now,
|
|
326
|
+
defaults
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
projectId: normalized.projectId,
|
|
332
|
+
focusedChannelId: requestedChannelId,
|
|
333
|
+
channels: nextChannels
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function updateChannelState(context = {}, channelId, patch = {}, options = {}) {
|
|
338
|
+
const requestedChannelId = pickFirstString([channelId, patch && patch.channelId], '');
|
|
339
|
+
if (!requestedChannelId) {
|
|
340
|
+
throw new Error('channelId is required');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const normalized = normalizeProjectChannelContext(context, {
|
|
344
|
+
projectId: options.projectId,
|
|
345
|
+
defaultChannelId: options.defaultChannelId,
|
|
346
|
+
now: options.now,
|
|
347
|
+
channelDefaults: options.channelDefaults
|
|
348
|
+
});
|
|
349
|
+
const currentState = normalized.channels[requestedChannelId] || null;
|
|
350
|
+
const rawPatch = patch && typeof patch === 'object' ? patch : {};
|
|
351
|
+
|
|
352
|
+
const mergedState = currentState
|
|
353
|
+
? {
|
|
354
|
+
...currentState,
|
|
355
|
+
...rawPatch,
|
|
356
|
+
channelId: requestedChannelId,
|
|
357
|
+
updatedAt: rawPatch.updatedAt !== undefined ? rawPatch.updatedAt : (options.now || currentState.updatedAt),
|
|
358
|
+
agentRuntime: rawPatch.agentRuntime && currentState.agentRuntime
|
|
359
|
+
? { ...currentState.agentRuntime, ...rawPatch.agentRuntime }
|
|
360
|
+
: (rawPatch.agentRuntime !== undefined ? rawPatch.agentRuntime : currentState.agentRuntime)
|
|
361
|
+
}
|
|
362
|
+
: {
|
|
363
|
+
...rawPatch,
|
|
364
|
+
channelId: requestedChannelId,
|
|
365
|
+
updatedAt: rawPatch.updatedAt !== undefined ? rawPatch.updatedAt : options.now
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const defaults = options.channelDefaults && options.channelDefaults[requestedChannelId]
|
|
369
|
+
? options.channelDefaults[requestedChannelId]
|
|
370
|
+
: {};
|
|
371
|
+
const nextChannelState = normalizeChannelState(requestedChannelId, mergedState, {
|
|
372
|
+
projectId: normalized.projectId,
|
|
373
|
+
now: options.now,
|
|
374
|
+
defaults
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
projectId: normalized.projectId,
|
|
379
|
+
focusedChannelId: normalized.focusedChannelId,
|
|
380
|
+
channels: {
|
|
381
|
+
...normalized.channels,
|
|
382
|
+
[requestedChannelId]: nextChannelState
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = {
|
|
388
|
+
DEFAULT_PROJECT_ID,
|
|
389
|
+
DEFAULT_CHANNEL_ID,
|
|
390
|
+
DEFAULT_ACTIVE_SCENE,
|
|
391
|
+
DEFAULT_ACTIVE_SPEC_ID,
|
|
392
|
+
DEFAULT_ACTIVE_DOC,
|
|
393
|
+
DEFAULT_RUN_STATE,
|
|
394
|
+
RUN_STATE_VALUES,
|
|
395
|
+
buildDefaultSessionPath,
|
|
396
|
+
chooseFocusedChannelId,
|
|
397
|
+
createDefaultChannelState,
|
|
398
|
+
getChannelState,
|
|
399
|
+
getFocusedChannelState,
|
|
400
|
+
listChannelIds,
|
|
401
|
+
normalizeAgentRuntime,
|
|
402
|
+
normalizeChannelState,
|
|
403
|
+
normalizeProjectChannelContext,
|
|
404
|
+
setFocusedChannel,
|
|
405
|
+
updateChannelState
|
|
406
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { SessionStore } = require('./session-store');
|
|
4
|
+
const { buildProjectChannelOutput } = require('../project/project-channel-output');
|
|
4
5
|
|
|
5
6
|
function normalizeString(value) {
|
|
6
7
|
if (typeof value !== 'string') {
|
|
@@ -44,6 +45,8 @@ async function resolveSpecSceneBinding(options = {}, dependencies = {}) {
|
|
|
44
45
|
const fileSystem = dependencies.fileSystem || fs;
|
|
45
46
|
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
46
47
|
const explicitSceneId = normalizeString(options.sceneId || options.scene);
|
|
48
|
+
const projectId = normalizeString(options.projectId);
|
|
49
|
+
const collabChannel = normalizeString(options.collabChannel || options.channelId || options.channel);
|
|
47
50
|
const allowNoScene = options.allowNoScene !== false;
|
|
48
51
|
|
|
49
52
|
if (explicitSceneId) {
|
|
@@ -60,6 +63,49 @@ async function resolveSpecSceneBinding(options = {}, dependencies = {}) {
|
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
if (projectId || collabChannel) {
|
|
67
|
+
if (!projectId || !collabChannel) {
|
|
68
|
+
throw new Error('Both projectId and collabChannel are required for project-channel scene binding.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const resolved = await sessionStore.getProjectChannelSession(projectId, collabChannel);
|
|
72
|
+
if (!resolved.contextAvailable) {
|
|
73
|
+
throw new Error(`Project channel context unavailable: ${projectId} / ${collabChannel}.`);
|
|
74
|
+
}
|
|
75
|
+
if (!resolved.channel) {
|
|
76
|
+
throw new Error(`Project channel unavailable: ${projectId} / ${collabChannel}.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const activeScene = normalizeString(resolved.channel.activeScene);
|
|
80
|
+
if (!activeScene) {
|
|
81
|
+
throw new Error(`Project channel has no active scene: ${projectId} / ${collabChannel}.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const active = await sessionStore.getActiveSceneSession(activeScene);
|
|
85
|
+
if (!active) {
|
|
86
|
+
throw new Error(`Project channel points to scene "${activeScene}" but no active scene session exists. Run "sce studio plan --project-id ${projectId} --collab-channel ${collabChannel} --from-chat <session>" first.`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
source: 'project-channel',
|
|
91
|
+
scene_id: activeScene,
|
|
92
|
+
scene_cycle: active.scene_cycle,
|
|
93
|
+
scene_session_id: active.session.session_id,
|
|
94
|
+
scene_session: active.session,
|
|
95
|
+
project_channel: buildProjectChannelOutput({
|
|
96
|
+
projectId,
|
|
97
|
+
canonicalProjectId: projectId,
|
|
98
|
+
requestedChannelId: collabChannel,
|
|
99
|
+
storageMode: resolved.storageMode,
|
|
100
|
+
contextAvailable: resolved.contextAvailable,
|
|
101
|
+
context: resolved.context,
|
|
102
|
+
channel: resolved.channel,
|
|
103
|
+
session: resolved.session,
|
|
104
|
+
resolvedChannelId: resolved.resolvedChannelId
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
63
109
|
const studioJob = await loadLatestStudioJob(projectPath, fileSystem);
|
|
64
110
|
const studioSceneId = normalizeString(studioJob && studioJob.scene && studioJob.scene.id);
|
|
65
111
|
const studioSceneSessionId = normalizeString(studioJob && studioJob.session && studioJob.session.scene_session_id);
|
|
@@ -2,6 +2,7 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { SteeringContract, normalizeToolName } = require('./steering-contract');
|
|
4
4
|
const { getSceStateStore } = require('../state/sce-state-store');
|
|
5
|
+
const { ProjectChannelContextStore } = require('./project-channel-context-store');
|
|
5
6
|
|
|
6
7
|
const SESSION_SCHEMA_VERSION = '1.0';
|
|
7
8
|
const SESSION_DIR = path.join('.sce', 'sessions');
|
|
@@ -43,6 +44,46 @@ function nextSnapshotId(session) {
|
|
|
43
44
|
return `snap-${snapshots.length + 1}`;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
function normalizeString(value) {
|
|
48
|
+
if (typeof value !== 'string') {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
return value.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function mapSessionStatusToChannelRunState(status) {
|
|
55
|
+
const normalized = normalizeString(status).toLowerCase();
|
|
56
|
+
if (!normalized) {
|
|
57
|
+
return 'idle';
|
|
58
|
+
}
|
|
59
|
+
if (normalized === 'active' || normalized === 'running') {
|
|
60
|
+
return 'running';
|
|
61
|
+
}
|
|
62
|
+
if (normalized === 'paused' || normalized === 'blocked') {
|
|
63
|
+
return 'blocked';
|
|
64
|
+
}
|
|
65
|
+
if (normalized === 'completed') {
|
|
66
|
+
return 'completed';
|
|
67
|
+
}
|
|
68
|
+
if (normalized === 'failed' || normalized === 'timeout' || normalized === 'rolled_back') {
|
|
69
|
+
return 'failed';
|
|
70
|
+
}
|
|
71
|
+
return 'idle';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toSessionFileRelativePath(sessionId) {
|
|
75
|
+
return path.join(SESSION_DIR, `${sessionId}.json`).replace(/\\/g, '/');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sessionIdFromRelativePath(sessionPath) {
|
|
79
|
+
const normalized = normalizeString(sessionPath);
|
|
80
|
+
if (!normalized) {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
const parsed = path.posix.parse(normalized.replace(/\\/g, '/'));
|
|
84
|
+
return parsed.ext === '.json' ? parsed.name : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
46
87
|
class SessionStore {
|
|
47
88
|
constructor(workspaceRoot, steeringContract = null, options = {}) {
|
|
48
89
|
this._workspaceRoot = workspaceRoot;
|
|
@@ -57,6 +98,8 @@ class SessionStore {
|
|
|
57
98
|
env: this._env,
|
|
58
99
|
sqliteModule: options.sqliteModule
|
|
59
100
|
});
|
|
101
|
+
this._projectChannelContextStore = options.projectChannelContextStore
|
|
102
|
+
|| new ProjectChannelContextStore(workspaceRoot, this._fileSystem);
|
|
60
103
|
this._preferSqliteSceneReads = options.preferSqliteSceneReads !== undefined
|
|
61
104
|
? options.preferSqliteSceneReads === true
|
|
62
105
|
: true;
|
|
@@ -197,6 +240,149 @@ class SessionStore {
|
|
|
197
240
|
return records;
|
|
198
241
|
}
|
|
199
242
|
|
|
243
|
+
async bindSessionToProjectChannel(sessionRef = 'latest', options = {}) {
|
|
244
|
+
const projectId = normalizeString(options.projectId);
|
|
245
|
+
const channelId = normalizeString(options.channelId);
|
|
246
|
+
if (!projectId) {
|
|
247
|
+
throw new Error('projectId is required for bindSessionToProjectChannel');
|
|
248
|
+
}
|
|
249
|
+
if (!channelId) {
|
|
250
|
+
throw new Error('channelId is required for bindSessionToProjectChannel');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const { session } = await this._resolveSession(sessionRef);
|
|
254
|
+
const updatedAt = normalizeString(session.updated_at) || nowIso();
|
|
255
|
+
const patch = {
|
|
256
|
+
activeSessionPath: toSessionFileRelativePath(session.session_id),
|
|
257
|
+
activeScene: normalizeString(options.activeScene)
|
|
258
|
+
|| normalizeString(session && session.scene && session.scene.id),
|
|
259
|
+
activeSpecId: normalizeString(options.activeSpecId)
|
|
260
|
+
|| normalizeString(session && session.scene && session.scene.spec_id),
|
|
261
|
+
activeDoc: normalizeString(options.activeDoc),
|
|
262
|
+
selectedTreeNodeId: normalizeString(options.selectedTreeNodeId),
|
|
263
|
+
draftInput: options.draftInput !== undefined ? options.draftInput : undefined,
|
|
264
|
+
openedTabs: Array.isArray(options.openedTabs) ? options.openedTabs : undefined,
|
|
265
|
+
runState: normalizeString(options.runState) || mapSessionStatusToChannelRunState(session.status),
|
|
266
|
+
updatedAt
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const sanitizedPatch = {};
|
|
270
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
271
|
+
if (value !== undefined && value !== '') {
|
|
272
|
+
sanitizedPatch[key] = value;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let context = await this._projectChannelContextStore.updateChannel(
|
|
277
|
+
projectId,
|
|
278
|
+
channelId,
|
|
279
|
+
sanitizedPatch,
|
|
280
|
+
{
|
|
281
|
+
now: updatedAt,
|
|
282
|
+
syncCurrentContext: false
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const shouldFocus = options.focus !== false;
|
|
287
|
+
if (shouldFocus) {
|
|
288
|
+
context = await this._projectChannelContextStore.focusChannel(projectId, channelId, {
|
|
289
|
+
syncCurrentContext: false
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (options.syncCurrentContext === true) {
|
|
294
|
+
await this._projectChannelContextStore.syncCurrentContext(projectId, {
|
|
295
|
+
projectChannelContext: context
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
session,
|
|
301
|
+
context,
|
|
302
|
+
channel: context.channels[channelId]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getProjectChannelSession(projectId, channelId = null, options = {}) {
|
|
307
|
+
const normalizedProjectId = normalizeString(projectId);
|
|
308
|
+
const requestedChannelId = normalizeString(channelId) || null;
|
|
309
|
+
if (!normalizedProjectId) {
|
|
310
|
+
throw new Error('projectId is required for getProjectChannelSession');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const detection = await this._projectChannelContextStore.detectPersistedContext(normalizedProjectId);
|
|
314
|
+
if (!detection.available) {
|
|
315
|
+
return {
|
|
316
|
+
projectId: normalizedProjectId,
|
|
317
|
+
requestedChannelId,
|
|
318
|
+
resolvedChannelId: null,
|
|
319
|
+
contextAvailable: false,
|
|
320
|
+
storageMode: detection.storageMode,
|
|
321
|
+
context: null,
|
|
322
|
+
channel: null,
|
|
323
|
+
session: null
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const context = await this._projectChannelContextStore.load(normalizedProjectId, {
|
|
328
|
+
...options,
|
|
329
|
+
allowDefaultChannelSynthesis: false
|
|
330
|
+
});
|
|
331
|
+
const resolvedChannelId = requestedChannelId || normalizeString(context.focusedChannelId);
|
|
332
|
+
if (!resolvedChannelId || !context.channels[resolvedChannelId]) {
|
|
333
|
+
return {
|
|
334
|
+
projectId: normalizedProjectId,
|
|
335
|
+
requestedChannelId,
|
|
336
|
+
resolvedChannelId: null,
|
|
337
|
+
contextAvailable: true,
|
|
338
|
+
storageMode: detection.storageMode,
|
|
339
|
+
context,
|
|
340
|
+
channel: null,
|
|
341
|
+
session: null
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const channel = context.channels[resolvedChannelId];
|
|
346
|
+
const sessionId = sessionIdFromRelativePath(channel.activeSessionPath);
|
|
347
|
+
if (!sessionId) {
|
|
348
|
+
return {
|
|
349
|
+
projectId: normalizedProjectId,
|
|
350
|
+
requestedChannelId,
|
|
351
|
+
resolvedChannelId,
|
|
352
|
+
contextAvailable: true,
|
|
353
|
+
storageMode: detection.storageMode,
|
|
354
|
+
context,
|
|
355
|
+
channel,
|
|
356
|
+
session: null
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const session = await this.getSession(sessionId);
|
|
362
|
+
return {
|
|
363
|
+
projectId: normalizedProjectId,
|
|
364
|
+
requestedChannelId,
|
|
365
|
+
resolvedChannelId,
|
|
366
|
+
contextAvailable: true,
|
|
367
|
+
storageMode: detection.storageMode,
|
|
368
|
+
context,
|
|
369
|
+
channel,
|
|
370
|
+
session
|
|
371
|
+
};
|
|
372
|
+
} catch (_error) {
|
|
373
|
+
return {
|
|
374
|
+
projectId: normalizedProjectId,
|
|
375
|
+
requestedChannelId,
|
|
376
|
+
resolvedChannelId,
|
|
377
|
+
contextAvailable: true,
|
|
378
|
+
storageMode: detection.storageMode,
|
|
379
|
+
context,
|
|
380
|
+
channel,
|
|
381
|
+
session: null
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
200
386
|
async beginSceneSession(options = {}) {
|
|
201
387
|
const sceneId = `${options.sceneId || ''}`.trim();
|
|
202
388
|
if (!sceneId) {
|
|
@@ -24,6 +24,7 @@ const DEFAULT_MANIFEST = Object.freeze({
|
|
|
24
24
|
codex: '>=0.0.0',
|
|
25
25
|
claude: '>=0.0.0',
|
|
26
26
|
cursor: '>=0.0.0',
|
|
27
|
+
'sce-native': '*',
|
|
27
28
|
generic: '*',
|
|
28
29
|
},
|
|
29
30
|
session: {
|
|
@@ -58,6 +59,11 @@ const TOOL_RUNTIME_PROFILES = Object.freeze({
|
|
|
58
59
|
default_permission_args: [],
|
|
59
60
|
notes: 'Provide runtime permission args in the target agent adapter.',
|
|
60
61
|
},
|
|
62
|
+
'sce-native': {
|
|
63
|
+
recommended_command: 'sce native start',
|
|
64
|
+
default_permission_args: [],
|
|
65
|
+
notes: 'Native SCE semantic runtime with no embedded host dependency.',
|
|
66
|
+
},
|
|
61
67
|
});
|
|
62
68
|
|
|
63
69
|
function normalizeToolName(value) {
|
|
@@ -68,7 +74,7 @@ function normalizeToolName(value) {
|
|
|
68
74
|
if (raw === 'claude-code') {
|
|
69
75
|
return 'claude';
|
|
70
76
|
}
|
|
71
|
-
if (raw === 'codex' || raw === 'claude' || raw === 'cursor') {
|
|
77
|
+
if (raw === 'codex' || raw === 'claude' || raw === 'cursor' || raw === 'sce-native') {
|
|
72
78
|
return raw;
|
|
73
79
|
}
|
|
74
80
|
return 'generic';
|