gestalt-mobile 0.17.0 → 0.17.2
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 +4 -1
- package/dist/client/assets/index-DgD-0I3M.js +25 -0
- package/dist/client/index.html +1 -1
- package/dist/server/server/composition.js +6 -0
- package/dist/server/server/features/plans/get-plan/endpoint.js +2 -2
- package/dist/server/server/features/skills/model/skill-profile.js +33 -2
- package/dist/server/server/platform/git/git-summary-cache.js +15 -9
- package/dist/server/server/platform/plans/filesystem-plan-status-source.js +19 -12
- package/package.json +1 -1
- package/dist/client/assets/index-Cfo4jnPb.js +0 -25
package/dist/client/index.html
CHANGED
|
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
16
16
|
<link rel="icon" href="/icons/gestalt-mobile-192.png" />
|
|
17
17
|
<link rel="apple-touch-icon" href="/icons/gestalt-mobile-180.png" />
|
|
18
18
|
<title>Gestalt Mobile</title>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DgD-0I3M.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-e2lJ5XqL.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
@@ -346,6 +346,12 @@ export async function composeRelayApp(options) {
|
|
|
346
346
|
planRoutes: {
|
|
347
347
|
exists: (id) => sessions.find(id) !== null,
|
|
348
348
|
find: (id) => supervisedPlans.find(id),
|
|
349
|
+
refresh: async (id) => {
|
|
350
|
+
const refreshed = await planStatusSource.refresh(id);
|
|
351
|
+
if (refreshed?.kind === 'updated')
|
|
352
|
+
return refreshed.plan;
|
|
353
|
+
return refreshed ? null : supervisedPlans.find(id);
|
|
354
|
+
},
|
|
349
355
|
removeStatus: (id) => planStatusSource.remove(id, supervisedPlans.identity(id) ?? undefined),
|
|
350
356
|
clear: (id) => supervisedPlans.clear(id),
|
|
351
357
|
closed: (id) => {
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
export function registerGetPlan(app, deps) {
|
|
7
|
-
app.get('/api/sessions/:id/plan', (request, reply) => {
|
|
7
|
+
app.get('/api/sessions/:id/plan', async (request, reply) => {
|
|
8
8
|
const id = request.params.id;
|
|
9
9
|
if (!deps.exists(id))
|
|
10
10
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
11
|
-
const plan = deps.
|
|
11
|
+
const plan = await deps.refresh(id);
|
|
12
12
|
return plan ? reply.send(plan) : reply.code(204).send();
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -81,6 +81,36 @@ function canonicalSkillPath(value) {
|
|
|
81
81
|
}
|
|
82
82
|
return value;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Return the durable identity of a skill inside Codex's versioned plugin cache.
|
|
86
|
+
* The concrete version directory is installation state, while marketplace,
|
|
87
|
+
* plugin, and skill-relative path remain stable across plugin upgrades.
|
|
88
|
+
*/
|
|
89
|
+
function versionNeutralPluginSkillPath(value) {
|
|
90
|
+
const match = value.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+)[\\/][^\\/]+([\\/]skills[\\/].+[\\/]SKILL\.md)$/);
|
|
91
|
+
return match ? `${match[1]}${match[2]}` : undefined;
|
|
92
|
+
}
|
|
93
|
+
function rebindVersionedPluginSkills(discovered, selection) {
|
|
94
|
+
const discoveredPaths = new Set(discovered.map((skill) => canonicalSkillPath(skill.path)));
|
|
95
|
+
const discoveredByDurablePath = new Map();
|
|
96
|
+
for (const skill of discovered) {
|
|
97
|
+
const durablePath = versionNeutralPluginSkillPath(canonicalSkillPath(skill.path));
|
|
98
|
+
if (durablePath === undefined)
|
|
99
|
+
continue;
|
|
100
|
+
const matches = discoveredByDurablePath.get(durablePath) ?? [];
|
|
101
|
+
matches.push(skill);
|
|
102
|
+
discoveredByDurablePath.set(durablePath, matches);
|
|
103
|
+
}
|
|
104
|
+
return createSkillSelection(selection.map((entry) => {
|
|
105
|
+
if (discoveredPaths.has(entry.path))
|
|
106
|
+
return entry;
|
|
107
|
+
const durablePath = versionNeutralPluginSkillPath(entry.path);
|
|
108
|
+
if (durablePath === undefined)
|
|
109
|
+
return entry;
|
|
110
|
+
const matches = discoveredByDurablePath.get(durablePath) ?? [];
|
|
111
|
+
return matches.length === 1 ? { ...entry, path: matches[0].path } : entry;
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
84
114
|
/**
|
|
85
115
|
* Validate a complete selection and return its canonical deterministic order.
|
|
86
116
|
* This is lexical only: resolving symlinks is I/O and belongs to a platform
|
|
@@ -137,11 +167,12 @@ export function compileSkillOverride(input) {
|
|
|
137
167
|
const effective = selectEffectiveSkillSelection(input);
|
|
138
168
|
if (effective.selection === undefined)
|
|
139
169
|
return { source: 'native', skillsConfig: undefined, warnings: [] };
|
|
170
|
+
const reboundSelection = rebindVersionedPluginSkills(input.discovered, effective.selection);
|
|
140
171
|
const discoveredPaths = new Set(input.discovered.map((skill) => canonicalSkillPath(skill.path)));
|
|
141
|
-
const warnings =
|
|
172
|
+
const warnings = reboundSelection
|
|
142
173
|
.filter((entry) => !discoveredPaths.has(entry.path))
|
|
143
174
|
.map((entry) => `Saved skill path is no longer discovered: ${entry.path}`);
|
|
144
|
-
const configured = applySkillSelectionSnapshot(input.discovered,
|
|
175
|
+
const configured = applySkillSelectionSnapshot(input.discovered, reboundSelection);
|
|
145
176
|
return {
|
|
146
177
|
source: effective.source,
|
|
147
178
|
skillsConfig: configured
|
|
@@ -4,23 +4,29 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
|
-
/**
|
|
7
|
+
/** Coalesces concurrent inspections without retaining results across refreshes. */
|
|
8
8
|
export class GitSummaryCache {
|
|
9
9
|
inspectWorkspace;
|
|
10
|
-
|
|
10
|
+
inspections = new Map();
|
|
11
11
|
constructor(inspectWorkspace) {
|
|
12
12
|
this.inspectWorkspace = inspectWorkspace;
|
|
13
13
|
}
|
|
14
14
|
async inspect(workspacePath) {
|
|
15
15
|
const key = resolve(workspacePath);
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
const
|
|
20
|
-
this.
|
|
21
|
-
|
|
16
|
+
const pending = this.inspections.get(key);
|
|
17
|
+
if (pending)
|
|
18
|
+
return pending;
|
|
19
|
+
const inspection = this.inspectWorkspace(workspacePath);
|
|
20
|
+
this.inspections.set(key, inspection);
|
|
21
|
+
try {
|
|
22
|
+
return await inspection;
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
if (this.inspections.get(key) === inspection)
|
|
26
|
+
this.inspections.delete(key);
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
invalidate(workspacePath) {
|
|
24
|
-
this.
|
|
30
|
+
this.inspections.delete(resolve(workspacePath));
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -44,6 +44,9 @@ export class FilesystemPlanStatusSource {
|
|
|
44
44
|
lease.close();
|
|
45
45
|
this.leases.clear();
|
|
46
46
|
}
|
|
47
|
+
async refresh(sessionId) {
|
|
48
|
+
return (await this.leases.get(sessionId)?.refreshCurrent()) ?? null;
|
|
49
|
+
}
|
|
47
50
|
async remove(sessionId, identity) {
|
|
48
51
|
const statusPath = this.activeStatusPaths.get(sessionId);
|
|
49
52
|
const signal = statusPath ? await this.readStatusForRollback(statusPath) : undefined;
|
|
@@ -220,9 +223,12 @@ class ActiveLease {
|
|
|
220
223
|
this.debounce = undefined;
|
|
221
224
|
const pendingStatusPath = this.pendingStatusPath;
|
|
222
225
|
this.pendingStatusPath = undefined;
|
|
223
|
-
void (pendingStatusPath ? this.
|
|
226
|
+
void (pendingStatusPath ? this.refreshPath(pendingStatusPath) : this.refreshLatest());
|
|
224
227
|
}, 25);
|
|
225
228
|
}
|
|
229
|
+
async refreshCurrent() {
|
|
230
|
+
return this.activeStatusPath ? this.refreshPath(this.activeStatusPath) : this.refreshLatest();
|
|
231
|
+
}
|
|
226
232
|
async refreshLatest() {
|
|
227
233
|
try {
|
|
228
234
|
const candidates = (await readdir(this.statusDirectory))
|
|
@@ -239,15 +245,13 @@ class ActiveLease {
|
|
|
239
245
|
.filter((candidate) => candidate.signal !== null)
|
|
240
246
|
.sort((left, right) => right.signal.updatedAt.localeCompare(left.signal.updatedAt) ||
|
|
241
247
|
right.modifiedAt - left.modifiedAt)[0];
|
|
242
|
-
|
|
243
|
-
await this.refresh(latest.statusPath, latest.signal);
|
|
248
|
+
return latest ? this.refreshPath(latest.statusPath, latest.signal) : null;
|
|
244
249
|
}
|
|
245
250
|
catch {
|
|
246
|
-
|
|
247
|
-
this.emitUnavailable();
|
|
251
|
+
return !this.closed ? this.emitUnavailable() : null;
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
|
-
async
|
|
254
|
+
async refreshPath(statusPath, parsedSignal) {
|
|
251
255
|
try {
|
|
252
256
|
const signal = parsedSignal ?? parseSignal(await this.planReadFilesystem.readFile(statusPath, 'utf8'));
|
|
253
257
|
if (!signal)
|
|
@@ -260,7 +264,7 @@ class ActiveLease {
|
|
|
260
264
|
throw new Error('PATH_OUTSIDE_WORKSPACE');
|
|
261
265
|
const identity = createHash('sha256').update(planPath).digest('hex');
|
|
262
266
|
if (await this.isDismissed(identity))
|
|
263
|
-
return;
|
|
267
|
+
return null;
|
|
264
268
|
const result = parseSupervisedPlan({
|
|
265
269
|
source: await this.planReadFilesystem.readFile(planPath, 'utf8'),
|
|
266
270
|
planPath,
|
|
@@ -285,24 +289,27 @@ class ActiveLease {
|
|
|
285
289
|
}
|
|
286
290
|
if (previousStatusPath && previousStatusPath !== statusPath)
|
|
287
291
|
await rm(previousStatusPath, { force: true }).catch(() => { });
|
|
292
|
+
return update;
|
|
288
293
|
}
|
|
289
294
|
}
|
|
290
295
|
else if (!this.closed) {
|
|
291
|
-
this.emitUnavailable();
|
|
296
|
+
return this.emitUnavailable();
|
|
292
297
|
}
|
|
298
|
+
return null;
|
|
293
299
|
}
|
|
294
300
|
catch (error) {
|
|
295
301
|
if (error.code === 'ENOENT' &&
|
|
296
302
|
this.activeStatusPath !== undefined &&
|
|
297
303
|
statusPath !== this.activeStatusPath)
|
|
298
|
-
return;
|
|
299
|
-
|
|
300
|
-
this.emitUnavailable();
|
|
304
|
+
return null;
|
|
305
|
+
return !this.closed ? this.emitUnavailable() : null;
|
|
301
306
|
}
|
|
302
307
|
}
|
|
303
308
|
emitUnavailable() {
|
|
304
309
|
this.lastEmittedUpdate = undefined;
|
|
305
|
-
|
|
310
|
+
const update = { kind: 'unavailable', code: 'PLAN_STATUS_UNAVAILABLE' };
|
|
311
|
+
this.listener(update);
|
|
312
|
+
return update;
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
function parseSignal(source) {
|