agl 40.6.0 → 41.0.1
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 +40 -0
- package/dist_serve/bundle.js +3514 -3357
- package/dist_serve/bundle.js.map +2 -2
- package/dist_serve/bundle.js.third-party-notices.json +58 -32
- package/dist_serve/bundle.js.third-party-notices.txt +108 -33
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/aglapi/aglapisurface.document.js +64 -37
- package/dist_ts/classes.controller.d.ts +8 -0
- package/dist_ts/classes.controller.js +110 -22
- package/dist_ts/classes.controllereventdelivery.d.ts +72 -0
- package/dist_ts/classes.controllereventdelivery.js +233 -0
- package/dist_ts/classes.flexmodels.public.js +4 -1
- package/dist_ts/classes.flexprojectionreader.d.ts +6 -0
- package/dist_ts/classes.flexprojectionreader.js +29 -5
- package/dist_ts/classes.flexservice.d.ts +2 -1
- package/dist_ts/classes.flexservice.js +34 -6
- package/dist_ts/classes.flexstore.d.ts +41 -4
- package/dist_ts/classes.flexstore.js +266 -203
- package/dist_ts/classes.mcpreexec.js +4 -4
- package/dist_ts/classes.ticketnotifier.js +4 -2
- package/dist_ts/classes.ticketstore.d.ts +50 -2
- package/dist_ts/classes.ticketstore.js +195 -78
- package/dist_ts/classes.workercallers.d.ts +2 -0
- package/dist_ts/classes.workercallers.js +11 -8
- package/dist_ts/driver/classes.drivereventrelay.d.ts +13 -1
- package/dist_ts/driver/classes.drivereventrelay.js +99 -6
- package/dist_ts/driver/functions.driverinstructions.d.ts +1 -1
- package/dist_ts/driver/functions.driverinstructions.js +1 -1
- package/dist_ts/driver/functions.drivertools.d.ts +11 -2
- package/dist_ts/driver/functions.drivertools.js +19 -9
- package/dist_ts/fleet/classes.boardpump.d.ts +8 -2
- package/dist_ts/fleet/classes.boardpump.js +38 -20
- package/dist_ts/fleet/classes.ticketeffects.d.ts +17 -2
- package/dist_ts/fleet/classes.ticketeffects.js +16 -10
- package/dist_ts/fleet/classes.workers.d.ts +4 -0
- package/dist_ts/fleet/classes.workers.js +37 -17
- package/dist_ts/fleet/functions.workervalidation.js +2 -2
- package/dist_ts/functions.flexprivatesnapshot.d.ts +102 -0
- package/dist_ts/functions.flexprivatesnapshot.js +461 -0
- package/dist_ts/functions.mcptickettools.d.ts +3 -0
- package/dist_ts/functions.mcptickettools.js +2 -2
- package/dist_ts/functions.ticketcli.d.ts +1 -1
- package/dist_ts/functions.ticketcli.js +8 -4
- package/dist_ts/functions.ticketvalidation.d.ts +3 -0
- package/dist_ts/functions.ticketvalidation.js +7 -4
- package/dist_ts/functions.workerprocessproof.d.ts +12 -3
- package/dist_ts/functions.workerprocessproof.js +17 -6
- package/dist_ts/interfaces.flexipc.d.ts +21 -2
- package/dist_ts/interfaces.flexipc.js +33 -16
- package/dist_ts_interfaces/fleetworkers.d.ts +6 -0
- package/dist_ts_interfaces/interfaces.d.ts +1 -1
- package/dist_ts_interfaces/interfaces.js +1 -1
- package/dist_ts_interfaces/requests.tickets.d.ts +8 -12
- package/dist_ts_interfaces/tickets.d.ts +37 -9
- package/dist_ts_interfaces/tickets.js +7 -1
- package/package.json +7 -7
- package/readme.md +62 -15
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/aglapi/aglapisurface.document.ts +63 -36
- package/ts/classes.controller.ts +132 -32
- package/ts/classes.controllereventdelivery.ts +289 -0
- package/ts/classes.flexmodels.public.ts +10 -0
- package/ts/classes.flexprojectionreader.ts +30 -3
- package/ts/classes.flexservice.ts +33 -5
- package/ts/classes.flexstore.ts +381 -246
- package/ts/classes.mcpreexec.ts +3 -3
- package/ts/classes.ticketnotifier.ts +3 -1
- package/ts/classes.ticketstore.ts +201 -75
- package/ts/classes.workercallers.ts +12 -7
- package/ts/driver/classes.drivereventrelay.ts +89 -4
- package/ts/driver/functions.driverinstructions.ts +1 -1
- package/ts/driver/functions.drivertools.ts +22 -8
- package/ts/fleet/classes.boardpump.ts +51 -19
- package/ts/fleet/classes.ticketeffects.ts +25 -11
- package/ts/fleet/classes.workers.ts +28 -16
- package/ts/fleet/functions.workervalidation.ts +1 -1
- package/ts/functions.flexprivatesnapshot.ts +601 -0
- package/ts/functions.mcptickettools.ts +1 -1
- package/ts/functions.ticketcli.ts +7 -3
- package/ts/functions.ticketvalidation.ts +7 -3
- package/ts/functions.workerprocessproof.ts +17 -3
- package/ts/interfaces.flexipc.ts +50 -21
- package/ts_interfaces/fleetworkers.ts +6 -0
- package/ts_interfaces/interfaces.ts +1 -1
- package/ts_interfaces/requests.tickets.ts +9 -6
- package/ts_interfaces/tickets.ts +32 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements.harnesscontrollerapp.ts +19 -8
- package/ts_web/elements.ticketboard.ts +16 -4
- package/ts_web/settings.general.ts +10 -4
|
@@ -142,15 +142,26 @@ const processOwnsLoopbackConnection = async (connectionArg: IWorkerCallerConnect
|
|
|
142
142
|
return matches.length === 1 && await processOwnsInode(pidArg, matches[0][9]);
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* The arguments after its `cli.js` entry that an upgrade re-executes an `agl mcp` bridge of the controller on
|
|
147
|
+
* `controllerPortArg` with (`AglMcpReexecCoordinator`): the port pins the next image to its handover record's.
|
|
148
|
+
* A bridge proof accepts exactly these besides the approved command's own.
|
|
149
|
+
*/
|
|
150
|
+
export const aglMcpReexecArguments = (controllerPortArg: number): string[] => ['mcp', '--port', String(controllerPortArg)];
|
|
151
|
+
|
|
145
152
|
/**
|
|
146
153
|
* The bridge process `bridgePidArg`, proved to hold the caller's end of the direct loopback connection and to run the
|
|
147
154
|
* approved command, whatever its parent. The command is the one recorded when the launch was prepared: an upgrade
|
|
148
|
-
* installs AGL under another path, and a command whose files are gone is refused as such.
|
|
155
|
+
* installs AGL under another path, and a command whose files are gone is refused as such. Its arguments are the
|
|
156
|
+
* approved ones or, once an upgrade re-executed the bridge in place, `aglMcpReexecArguments` for `controllerPortArg`,
|
|
157
|
+
* the port this controller is configured with, never one the bridge names: the connection proof already shows that
|
|
158
|
+
* the bridge talks to this controller.
|
|
149
159
|
*/
|
|
150
160
|
export const readWorkerBridge = async (
|
|
151
161
|
connectionArg: IWorkerCallerConnection | undefined,
|
|
152
162
|
bridgePidArg: number,
|
|
153
163
|
commandArg: IWorkerBridgeCommand,
|
|
164
|
+
controllerPortArg: number,
|
|
154
165
|
): Promise<IWorkerProcessSnapshot> => {
|
|
155
166
|
for (const file of [commandArg.executable, commandArg.entry]) {
|
|
156
167
|
let resolved: string;
|
|
@@ -165,8 +176,10 @@ export const readWorkerBridge = async (
|
|
|
165
176
|
}
|
|
166
177
|
const bridge = await readWorkerProcessSnapshot(bridgePidArg);
|
|
167
178
|
const entry = await plugins.fs.promises.realpath(bridge.arguments[1] ?? '');
|
|
179
|
+
const argumentsTail = bridge.arguments.slice(2);
|
|
168
180
|
if (bridge.executable !== commandArg.executable || entry !== commandArg.entry
|
|
169
|
-
|| !plugins.util.isDeepStrictEqual(
|
|
181
|
+
|| (!plugins.util.isDeepStrictEqual(argumentsTail, commandArg.arguments)
|
|
182
|
+
&& !plugins.util.isDeepStrictEqual(argumentsTail, aglMcpReexecArguments(controllerPortArg)))) {
|
|
170
183
|
throw new WorkerCallerProofError('Worker bridge does not match the approved launch.');
|
|
171
184
|
}
|
|
172
185
|
return bridge;
|
|
@@ -177,8 +190,9 @@ export const verifyWorkerBridge = async (
|
|
|
177
190
|
bridgePidArg: number,
|
|
178
191
|
commandArg: IWorkerBridgeCommand,
|
|
179
192
|
parentArg: IWorkerProcessSnapshot,
|
|
193
|
+
controllerPortArg: number,
|
|
180
194
|
): Promise<IWorkerProcessSnapshot> => {
|
|
181
|
-
const bridge = await readWorkerBridge(connectionArg, bridgePidArg, commandArg);
|
|
195
|
+
const bridge = await readWorkerBridge(connectionArg, bridgePidArg, commandArg, controllerPortArg);
|
|
182
196
|
if (bridge.parentPid !== parentArg.pid) throw new WorkerCallerProofError('Worker bridge does not match the approved launch.');
|
|
183
197
|
if (!workerSnapshotsEqual(parentArg, await readWorkerProcessSnapshot(parentArg.pid))
|
|
184
198
|
|| !workerSnapshotsEqual(bridge, await readWorkerProcessSnapshot(bridge.pid))) {
|
package/ts/interfaces.flexipc.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type TFlexCreateSessionOptions = plugins.flexharness.IFlexCreateSessionOptions;
|
|
24
24
|
type TFlexMessage = plugins.flexharness.IFlexMessage;
|
|
25
25
|
type TFlexMessagePage = plugins.flexharness.IFlexMessagePage;
|
|
26
|
+
type TFlexMessageMetadataPage = plugins.flexharness.IFlexMessageMetadataPage;
|
|
26
27
|
type TFlexPermissionRequest = plugins.flexharness.IFlexPermissionRequest;
|
|
27
28
|
export type TFlexPromptQueueEntry = plugins.flexharness.IFlexPromptQueueEntry;
|
|
28
29
|
type TFlexPromptOptions = plugins.flexharness.IFlexPromptOptions;
|
|
@@ -67,7 +68,7 @@ export type TFlexTurnReversionCaptureInspectionV2 =
|
|
|
67
68
|
export type TFlexTurnReversionApplyInspection =
|
|
68
69
|
plugins.flexharness.IFlexTurnReversionApplyInspection;
|
|
69
70
|
|
|
70
|
-
export const flexIpcProtocolVersion =
|
|
71
|
+
export const flexIpcProtocolVersion = 39 as const;
|
|
71
72
|
/**
|
|
72
73
|
* The installation's own Flex runs, which belong to no project: the configuration projection's adapting run, and
|
|
73
74
|
* Session Intelligence over a session outside every project.
|
|
@@ -307,6 +308,8 @@ export interface IFlexDriverRequest {
|
|
|
307
308
|
export interface IFlexDriverResolution {
|
|
308
309
|
instructions: string;
|
|
309
310
|
notes: string;
|
|
311
|
+
/** The revision of the session scratchpad that holds the notes; names them as the driver's pinned context. */
|
|
312
|
+
notesRevision: number;
|
|
310
313
|
}
|
|
311
314
|
export const flexDriverToolNamePattern = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
312
315
|
export const flexDriverToolInputMaxBytes = 2 * 1024 * 1024;
|
|
@@ -363,7 +366,7 @@ export interface IFlexHostRequestMap {
|
|
|
363
366
|
request: IFlexTurnReversionReleaseRequest;
|
|
364
367
|
response: { released: true };
|
|
365
368
|
};
|
|
366
|
-
/** The controller-owned driver session's instructions and notes; null for every other session. */
|
|
369
|
+
/** The controller-owned driver session's instructions and notes with their revision; null for every other session. */
|
|
367
370
|
'driver.resolve': {
|
|
368
371
|
request: IFlexDriverRequest;
|
|
369
372
|
response: { driver: IFlexDriverResolution | null };
|
|
@@ -836,6 +839,14 @@ export interface IFlexRequestMap {
|
|
|
836
839
|
request: { scopeId: string; sessionId: string; limit?: number; before?: string; includeUndone?: boolean };
|
|
837
840
|
response: TFlexMessagePage;
|
|
838
841
|
};
|
|
842
|
+
/**
|
|
843
|
+
* The same pages with each message's identity and state only (no parts, model, usage or error):
|
|
844
|
+
* a whole transcript is scanned without reading its text or tool output.
|
|
845
|
+
*/
|
|
846
|
+
'message.metadata.page': {
|
|
847
|
+
request: { scopeId: string; sessionId: string; limit?: number; before?: string; includeUndone?: boolean };
|
|
848
|
+
response: TFlexMessageMetadataPage;
|
|
849
|
+
};
|
|
839
850
|
'message.get': {
|
|
840
851
|
request: { scopeId: string; sessionId: string; messageId: string; includeUndone?: boolean };
|
|
841
852
|
response: TFlexMessage;
|
|
@@ -1358,6 +1369,7 @@ export const readOnlyFlexRequestMethods: ReadonlySet<TFlexRequestMethod> = new S
|
|
|
1358
1369
|
'session.reversion.info',
|
|
1359
1370
|
'slash.list',
|
|
1360
1371
|
'message.page',
|
|
1372
|
+
'message.metadata.page',
|
|
1361
1373
|
'message.get',
|
|
1362
1374
|
'prompt.get',
|
|
1363
1375
|
'prompt.list',
|
|
@@ -1387,6 +1399,7 @@ const requestMethods: ReadonlySet<string> = new Set<TFlexRequestMethod>([
|
|
|
1387
1399
|
'slash.list',
|
|
1388
1400
|
'slash.execute',
|
|
1389
1401
|
'message.page',
|
|
1402
|
+
'message.metadata.page',
|
|
1390
1403
|
'message.get',
|
|
1391
1404
|
'prompt.start',
|
|
1392
1405
|
'prompt.continue',
|
|
@@ -1919,28 +1932,22 @@ const isFlexModelLimit = (valueArg: unknown): valueArg is TFlexModelLimit => (
|
|
|
1919
1932
|
isFlexPlainObject(valueArg) && plugins.flexModels.isModelLimitInfo(valueArg)
|
|
1920
1933
|
);
|
|
1921
1934
|
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1935
|
+
const flexMessageIdentityKeys = ['messageId', 'sessionId', 'runId', 'role', 'status', 'createdAt'] as const;
|
|
1936
|
+
const flexMessageOptionalIdentityKeys = ['completedAt', 'steerId', 'promptId', 'keptInContext', 'undone', 'cacheWarm'] as const;
|
|
1937
|
+
const flexMessageKeys = [...flexMessageIdentityKeys, 'parts'] as const;
|
|
1938
|
+
const flexMessageOptionalKeys = [
|
|
1939
|
+
...flexMessageOptionalIdentityKeys, 'model', 'usage', 'error', 'limit', 'rejectedPermissionId',
|
|
1940
|
+
] as const;
|
|
1941
|
+
|
|
1942
|
+
/** The identity and state a transcript message and its metadata share (`TFlexMessageMetadata`). */
|
|
1943
|
+
const hasFlexMessageIdentity = (valueArg: Record<string, unknown>): boolean =>
|
|
1944
|
+
isIdentifier(valueArg.messageId)
|
|
1930
1945
|
&& isIdentifier(valueArg.sessionId)
|
|
1931
1946
|
&& isIdentifier(valueArg.runId)
|
|
1932
1947
|
&& ['user', 'assistant'].includes(String(valueArg.role))
|
|
1933
1948
|
&& ['streaming', 'completed', 'failed', 'cancelled'].includes(String(valueArg.status))
|
|
1934
1949
|
&& isIsoDate(valueArg.createdAt)
|
|
1935
1950
|
&& (valueArg.completedAt === undefined || isIsoDate(valueArg.completedAt))
|
|
1936
|
-
&& Array.isArray(valueArg.parts)
|
|
1937
|
-
&& valueArg.parts.length <= 2000
|
|
1938
|
-
&& valueArg.parts.every((partArg) => isMessagePart(partArg))
|
|
1939
|
-
&& (valueArg.model === undefined || isModelIdentity(valueArg.model))
|
|
1940
|
-
&& (valueArg.usage === undefined || isUsage(valueArg.usage))
|
|
1941
|
-
&& (valueArg.error === undefined || isBoundedString(valueArg.error, 2048))
|
|
1942
|
-
&& (valueArg.limit === undefined || isFlexModelLimit(valueArg.limit))
|
|
1943
|
-
&& (valueArg.rejectedPermissionId === undefined || isIdentifier(valueArg.rejectedPermissionId))
|
|
1944
1951
|
&& (valueArg.steerId === undefined || (valueArg.role === 'user' && isIdentifier(valueArg.steerId)))
|
|
1945
1952
|
&& (valueArg.promptId === undefined || (valueArg.role === 'user' && isBoundedString(valueArg.promptId, 512)))
|
|
1946
1953
|
&& !(valueArg.promptId !== undefined && valueArg.steerId !== undefined)
|
|
@@ -1949,6 +1956,25 @@ export const isFlexPublicMessage = (valueArg: unknown): valueArg is TFlexMessage
|
|
|
1949
1956
|
&& (valueArg.undone === undefined || valueArg.undone === true)
|
|
1950
1957
|
&& (valueArg.cacheWarm === undefined || valueArg.cacheWarm === true);
|
|
1951
1958
|
|
|
1959
|
+
export const isFlexPublicMessage = (valueArg: unknown): valueArg is TFlexMessage =>
|
|
1960
|
+
isFlexPlainObject(valueArg)
|
|
1961
|
+
&& hasFlexExactKeys(valueArg, flexMessageKeys, flexMessageOptionalKeys)
|
|
1962
|
+
&& hasFlexMessageIdentity(valueArg)
|
|
1963
|
+
&& Array.isArray(valueArg.parts)
|
|
1964
|
+
&& valueArg.parts.length <= 2000
|
|
1965
|
+
&& valueArg.parts.every((partArg) => isMessagePart(partArg))
|
|
1966
|
+
&& (valueArg.model === undefined || isModelIdentity(valueArg.model))
|
|
1967
|
+
&& (valueArg.usage === undefined || isUsage(valueArg.usage))
|
|
1968
|
+
&& (valueArg.error === undefined || isBoundedString(valueArg.error, 2048))
|
|
1969
|
+
&& (valueArg.limit === undefined || isFlexModelLimit(valueArg.limit))
|
|
1970
|
+
&& (valueArg.rejectedPermissionId === undefined || isIdentifier(valueArg.rejectedPermissionId));
|
|
1971
|
+
|
|
1972
|
+
/** A transcript message's identity and state as `message.metadata.page` carries it: no content. */
|
|
1973
|
+
export const isFlexMessageMetadata = (valueArg: unknown): valueArg is plugins.flexharness.TFlexMessageMetadata =>
|
|
1974
|
+
isFlexPlainObject(valueArg)
|
|
1975
|
+
&& hasFlexExactKeys(valueArg, flexMessageIdentityKeys, flexMessageOptionalIdentityKeys)
|
|
1976
|
+
&& hasFlexMessageIdentity(valueArg);
|
|
1977
|
+
|
|
1952
1978
|
export const isFlexPublicSession = (valueArg: unknown): valueArg is TFlexSession => {
|
|
1953
1979
|
if (
|
|
1954
1980
|
!isFlexPlainObject(valueArg)
|
|
@@ -2573,6 +2599,7 @@ export const isFlexRequestPayload = <TMethod extends TFlexRequestMethod>(
|
|
|
2573
2599
|
&& isScopeAndSession(valueArg)
|
|
2574
2600
|
&& isUpdateSessionOptions(valueArg.options);
|
|
2575
2601
|
case 'message.page':
|
|
2602
|
+
case 'message.metadata.page':
|
|
2576
2603
|
return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId'], ['limit', 'before', 'includeUndone'])
|
|
2577
2604
|
&& isScopeAndSession(valueArg)
|
|
2578
2605
|
&& (valueArg.limit === undefined
|
|
@@ -3118,10 +3145,11 @@ export const isFlexHostResponseResult = <TMethod extends TFlexHostRequestMethod>
|
|
|
3118
3145
|
if (!hasFlexExactKeys(valueArg, ['driver'])) return false;
|
|
3119
3146
|
const driver = valueArg.driver;
|
|
3120
3147
|
return driver === null || (isFlexPlainObject(driver)
|
|
3121
|
-
&& hasFlexExactKeys(driver, ['instructions', 'notes'])
|
|
3148
|
+
&& hasFlexExactKeys(driver, ['instructions', 'notes', 'notesRevision'])
|
|
3122
3149
|
&& isBoundedString(driver.instructions, 64 * 1024)
|
|
3123
3150
|
&& isBoundedString(driver.notes, 128 * 1024, true)
|
|
3124
|
-
&& driver.notes.length <= 32_768
|
|
3151
|
+
&& driver.notes.length <= 32_768
|
|
3152
|
+
&& isNonNegativeInteger(driver.notesRevision));
|
|
3125
3153
|
}
|
|
3126
3154
|
if (methodArg === 'driver.tool') {
|
|
3127
3155
|
if (Object.hasOwn(valueArg, 'error')) {
|
|
@@ -3441,11 +3469,12 @@ export const isFlexResponseResult = <TMethod extends TFlexRequestMethod>(
|
|
|
3441
3469
|
case 'slash.execute':
|
|
3442
3470
|
return isSlashExecutionResult(valueArg);
|
|
3443
3471
|
case 'message.page':
|
|
3472
|
+
case 'message.metadata.page':
|
|
3444
3473
|
return isFlexPlainObject(valueArg)
|
|
3445
3474
|
&& hasFlexExactKeys(valueArg, ['messages', 'startIndex'], ['nextCursor'])
|
|
3446
3475
|
&& Array.isArray(valueArg.messages) && valueArg.messages.length <= 50
|
|
3447
3476
|
&& Number.isSafeInteger(valueArg.startIndex) && (valueArg.startIndex as number) >= 0
|
|
3448
|
-
&& valueArg.messages.every(isFlexPublicMessage)
|
|
3477
|
+
&& valueArg.messages.every(methodArg === 'message.page' ? isFlexPublicMessage : isFlexMessageMetadata)
|
|
3449
3478
|
&& (valueArg.nextCursor === undefined || isBoundedString(valueArg.nextCursor, 4096));
|
|
3450
3479
|
case 'message.get':
|
|
3451
3480
|
return isFlexPublicMessage(valueArg);
|
|
@@ -228,6 +228,12 @@ export interface IFleetEvent {
|
|
|
228
228
|
/** The native session (`harness:nativeId`) a ticket notice addressed. */
|
|
229
229
|
sessionId?: string;
|
|
230
230
|
reportId?: string;
|
|
231
|
+
/**
|
|
232
|
+
* The comment on `ticketKey` that holds this event's whole text, by the id the ticket board returned: a worker's report
|
|
233
|
+
* the fleet commented (`worker.report.mid_turn`, `worker.turn.ended`), the board pump's stall notice, a commented
|
|
234
|
+
* `ticket.changed`. Read it with `ticket_get` and `commentId`. Absent when no comment holds the text.
|
|
235
|
+
*/
|
|
236
|
+
commentId?: string;
|
|
231
237
|
turnId?: string;
|
|
232
238
|
status?: string;
|
|
233
239
|
text?: string;
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
TControllerBrowserPaintAvailability,
|
|
6
6
|
TControllerBrowserPaintTermination,
|
|
7
7
|
} from './browserpaint.js';
|
|
8
|
-
export const controllerProtocolVersion =
|
|
8
|
+
export const controllerProtocolVersion = 46 as const;
|
|
9
9
|
export const controllerUpgradeManagementVersion = 2 as const;
|
|
10
10
|
export const controllerPackageName = 'agl' as const;
|
|
11
11
|
/** The systemd user unit `agl service install` writes; a controller it runs reports it in its status. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as plugins from './plugins.js';
|
|
2
2
|
import type {
|
|
3
|
-
ITicket,
|
|
4
|
-
ITicketFilters, ITicketMove, ITicketQuestion, TTicketLink, TTicketOperation, TTicketReferenceContent, TTicketRequest,
|
|
3
|
+
ITicket, ITicketAnswer, ITicketAsk, ITicketBoard, ITicketChanges, ITicketComment, ITicketCreate,
|
|
4
|
+
ITicketDetail, ITicketFilters, ITicketGet, ITicketMove, ITicketQuestion, TTicketLink, TTicketOperation, TTicketReferenceContent, TTicketRequest,
|
|
5
5
|
TTicketResponse,
|
|
6
6
|
} from './tickets.js';
|
|
7
7
|
|
|
@@ -61,15 +61,18 @@ export interface IReq_ControllerMcpTicketSearch extends plugins.typedrequestInte
|
|
|
61
61
|
response: { tickets: ITicket[] };
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Reads a ticket by its `AGL-<n>` key
|
|
65
|
-
*
|
|
64
|
+
* Reads a ticket by its `AGL-<n>` key with its source, links, all its questions and their answers,
|
|
65
|
+
* and the newest page of its history (up to `limit` entries, default 50, at most 500, within 256 KiB), oldest first: the
|
|
66
|
+
* activity entries and the Markdown comments they name, a `commented` entry naming its comment by
|
|
67
|
+
* id. `historyBefore` is present while older history exists; pass it as `before` for the next page.
|
|
68
|
+
* `commentId` reads that one comment instead (`comments: [it]`, `activity: []`), without `before` or `limit`.
|
|
66
69
|
*/
|
|
67
70
|
export interface IReq_ControllerMcpTicketGet extends plugins.typedrequestInterfaces.implementsTR<
|
|
68
71
|
TControllerMcpTicketRequest<'get'>, IReq_ControllerMcpTicketGet
|
|
69
72
|
> {
|
|
70
73
|
method: 'agl.mcp.ticket.get';
|
|
71
|
-
request:
|
|
72
|
-
response:
|
|
74
|
+
request: ITicketGet & { ownerToken?: string };
|
|
75
|
+
response: ITicketDetail;
|
|
73
76
|
}
|
|
74
77
|
/** Reads a ticket's comments. */
|
|
75
78
|
export interface IReq_ControllerMcpTicketComments extends plugins.typedrequestInterfaces.implementsTR<
|
package/ts_interfaces/tickets.ts
CHANGED
|
@@ -200,6 +200,36 @@ export interface ITicketCreate {
|
|
|
200
200
|
attachments?: ITicketAttachment[];
|
|
201
201
|
}
|
|
202
202
|
export interface ITicketMove { ticketId: string; columnId: TTicketColumn; beforeTicketId?: string; }
|
|
203
|
+
/**
|
|
204
|
+
* How many history entries one `get` returns unless it names `limit`, the most it may name, and the bytes a page holds
|
|
205
|
+
* at most (its entries plus the comments they name, serialized; a page holds at least one entry). The budget wins over `limit`.
|
|
206
|
+
*/
|
|
207
|
+
export const ticketHistoryPage = { default: 50, maximum: 500, bytes: 256 * 1024 } as const;
|
|
208
|
+
/** A ticket and one page of its history: the newest entries, or those older than the entry `before` names, within `ticketHistoryPage`. */
|
|
209
|
+
export interface ITicketGet {
|
|
210
|
+
ticketId: string;
|
|
211
|
+
/** An activity id, as `historyBefore` returned it: the page holds only older entries. */
|
|
212
|
+
before?: string;
|
|
213
|
+
/** Entries on the page, 1 to 500 (`ticketHistoryPage.maximum`); 50 (`ticketHistoryPage.default`) when absent. */
|
|
214
|
+
limit?: number;
|
|
215
|
+
/**
|
|
216
|
+
* One comment of this ticket, by the id an activity entry or a fleet event names: the response holds the ticket, its
|
|
217
|
+
* questions and that comment alone (`comments: [it]`, `activity: []`, no `historyBefore`). Excludes `before` and `limit`.
|
|
218
|
+
*/
|
|
219
|
+
commentId?: string;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* A ticket, all its questions and one page of its history, oldest first. `activity` is the page; `comments` are the
|
|
223
|
+
* comments its `commented` entries name, whose `changes.comment` holds only `{ after: { id } }` here (the stored
|
|
224
|
+
* entry keeps the whole comment). `historyBefore` is present while older entries exist: pass it as `before`.
|
|
225
|
+
*/
|
|
226
|
+
export interface ITicketDetail {
|
|
227
|
+
ticket: ITicket;
|
|
228
|
+
comments: ITicketComment[];
|
|
229
|
+
activity: ITicketActivity[];
|
|
230
|
+
questions: ITicketQuestion[];
|
|
231
|
+
historyBefore?: string;
|
|
232
|
+
}
|
|
203
233
|
export interface ITicketBoard {
|
|
204
234
|
revision: number;
|
|
205
235
|
columns: { id: TTicketColumn; title: string; done?: boolean }[];
|
|
@@ -211,7 +241,7 @@ export interface ITicketOperations {
|
|
|
211
241
|
list: { request: ITicketFilters; response: { tickets: ITicket[] } };
|
|
212
242
|
board: { request: ITicketFilters; response: ITicketBoard };
|
|
213
243
|
search: { request: { query: string; label?: string }; response: { tickets: ITicket[] } };
|
|
214
|
-
get: { request:
|
|
244
|
+
get: { request: ITicketGet; response: ITicketDetail };
|
|
215
245
|
comments: { request: { ticketId: string }; response: { comments: ITicketComment[] } };
|
|
216
246
|
create: { request: ITicketCreate; response: { ticket: ITicket } };
|
|
217
247
|
update: { request: { ticketId: string; expectedRevision: number; changes: ITicketChanges }; response: { ticket: ITicket } };
|
|
@@ -259,6 +289,7 @@ export const ticketErrorMessages = {
|
|
|
259
289
|
ticket_ask_forbidden: 'Workers may ask questions only on tickets assigned to their verified session.',
|
|
260
290
|
ticket_answer_forbidden: 'Only the owner or the driver may answer ticket questions.',
|
|
261
291
|
ticket_question_not_found: 'The requested question does not exist on this ticket.',
|
|
292
|
+
ticket_comment_not_found: 'The requested comment does not exist on this ticket.',
|
|
262
293
|
ticket_question_answered: 'The question already has a different answer; a second answer is refused. Ask a new question instead.',
|
|
263
294
|
ticket_answer_invalid: 'An answer chooses existing option ids (one unless multiSelect) and/or gives free text where the question allows it.',
|
|
264
295
|
} as const;
|
|
@@ -941,10 +941,13 @@ const modelHarnessId = (choiceArg: interfaces.TControllerModelChoice): TBrowserS
|
|
|
941
941
|
/** How long after asking for a restart this page expects its connection to drop; still connected, AGL did not restart. */
|
|
942
942
|
const restartCheckMs = 20_000;
|
|
943
943
|
|
|
944
|
+
/** A model as a harness's own row names it, without the harness: "openai/gpt-5.6-sol". */
|
|
945
|
+
const modelChoiceName = (choiceArg: interfaces.TControllerModelChoice): string => `${choiceArg.providerID}/${choiceArg.modelID}`;
|
|
946
|
+
|
|
944
947
|
const modelOptionLabel = (choiceArg: interfaces.TControllerModelChoice): string => {
|
|
945
948
|
return [
|
|
946
949
|
sessionHarnessLabel(modelHarnessId(choiceArg)),
|
|
947
|
-
|
|
950
|
+
modelChoiceName(choiceArg),
|
|
948
951
|
].join(' · ');
|
|
949
952
|
};
|
|
950
953
|
|
|
@@ -18816,16 +18819,23 @@ ${entry.detail.stack}`}</pre>`}
|
|
|
18816
18819
|
|
|
18817
18820
|
/** General as the controller's confirmed settings and this browser's own choices have it. */
|
|
18818
18821
|
private generalSettingsView(): IGeneralSettingsView {
|
|
18822
|
+
// Each row belongs to one harness, so it names a model without the harness.
|
|
18823
|
+
const modelName = (modelStringArg: string): string => {
|
|
18824
|
+
const choice = this.modelChoicesByString.get(modelStringArg);
|
|
18825
|
+
return choice ? modelChoiceName(choice) : modelStringArg;
|
|
18826
|
+
};
|
|
18819
18827
|
return {
|
|
18820
18828
|
defaults: (['opencode', 'flex', 'codex', 'claude'] as const).map((harnessIdArg) => {
|
|
18821
18829
|
const name = sessionHarnessLabel(harnessIdArg);
|
|
18822
18830
|
const model = this.defaultModelStrings[harnessIdArg];
|
|
18823
18831
|
const unavailable = this.unavailableDefaultModels[harnessIdArg];
|
|
18824
18832
|
// Flex has no default of its own: without one AGL can name, its composer asks for a model.
|
|
18825
|
-
const
|
|
18826
|
-
|
|
18833
|
+
const composerDefault = this.composerDefaultModelStrings[harnessIdArg];
|
|
18834
|
+
const runningDefault = composerDefault
|
|
18835
|
+
? modelName(composerDefault)
|
|
18836
|
+
: harnessIdArg === 'flex' ? '' : `${name}'s own default`;
|
|
18827
18837
|
const note = unavailable
|
|
18828
|
-
? `The saved default ${
|
|
18838
|
+
? `The saved default ${modelChoiceName(unavailable)} is no longer offered. ${runningDefault
|
|
18829
18839
|
? `New conversations run on ${runningDefault}.`
|
|
18830
18840
|
: 'New Flex conversations need an account and model chosen in the composer.'}`
|
|
18831
18841
|
: harnessIdArg === 'flex' && !model ? 'Without a default, Flex\'s composer asks for an account and a model.' : '';
|
|
@@ -18834,7 +18844,7 @@ ${entry.detail.stack}`}</pre>`}
|
|
|
18834
18844
|
name,
|
|
18835
18845
|
models: [
|
|
18836
18846
|
{ key: '', label: harnessIdArg === 'flex' ? 'Ask in the composer' : `${name}'s own default` },
|
|
18837
|
-
...this.modelOptionsForHarness(harnessIdArg).map((optionArg) => ({ key: optionArg, label: optionArg })),
|
|
18847
|
+
...this.modelOptionsForHarness(harnessIdArg).map((optionArg) => ({ key: optionArg, label: modelName(optionArg) })),
|
|
18838
18848
|
],
|
|
18839
18849
|
model,
|
|
18840
18850
|
efforts: this.settingsEffortDropdownOptions(model)
|
|
@@ -18854,9 +18864,10 @@ ${entry.detail.stack}`}</pre>`}
|
|
|
18854
18864
|
gpu: this.browserGpu,
|
|
18855
18865
|
activeGpu: this.activeBrowserGpu,
|
|
18856
18866
|
presentation: this.browserPresentation,
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
18867
|
+
// Said only while Paint cannot be used: an available Paint needs no sentence.
|
|
18868
|
+
paintState: !this.activeBrowserPaint
|
|
18869
|
+
? 'No browser runtime is running.'
|
|
18870
|
+
: this.activeBrowserPaint.state === 'available' ? '' : describeBrowserPaintAvailability(this.activeBrowserPaint),
|
|
18860
18871
|
paintRearm: this.activeBrowserPaint?.state === 'unavailable'
|
|
18861
18872
|
&& this.activeBrowserPaint.reason !== 'descriptor-missing'
|
|
18862
18873
|
&& this.activeBrowserPaint.reason !== 'unsupported-platform'
|
|
@@ -250,6 +250,20 @@ export class AglTicketBoard extends plugins.deesElement.DeesElement {
|
|
|
250
250
|
?? { id: personArg.id, name: personArg.name };
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/** A ticket's whole history: `get` returns it a page at a time, newest first, so this reads back to the first entry. */
|
|
254
|
+
private async history(ticketIdArg: string, signalArg?: AbortSignal): Promise<Omit<interfaces.ITicketDetail, 'historyBefore'>> {
|
|
255
|
+
const pages: interfaces.ITicketDetail[] = [];
|
|
256
|
+
let before: string | undefined;
|
|
257
|
+
do {
|
|
258
|
+
const page = await this.fire('get', { ticketId: ticketIdArg, limit: interfaces.ticketHistoryPage.maximum,
|
|
259
|
+
...(before === undefined ? {} : { before }) }, signalArg);
|
|
260
|
+
pages.unshift(page);
|
|
261
|
+
before = page.historyBefore;
|
|
262
|
+
} while (before !== undefined);
|
|
263
|
+
const { ticket, questions } = pages.at(-1)!;
|
|
264
|
+
return { ticket, questions, comments: pages.flatMap((pageArg) => pageArg.comments), activity: pages.flatMap((pageArg) => pageArg.activity) };
|
|
265
|
+
}
|
|
266
|
+
|
|
253
267
|
/** Tickets as the kanban shows them, remembering whom they name. */
|
|
254
268
|
private shown(ticketsArg: interfaces.ITicket[]): IKanbanTicket[] {
|
|
255
269
|
for (const ticket of ticketsArg) {
|
|
@@ -274,12 +288,10 @@ export class AglTicketBoard extends plugins.deesElement.DeesElement {
|
|
|
274
288
|
searchTickets: async (queryArg, signalArg) => this.shown((await this.fire('search', { query: queryArg }, signalArg)).tickets),
|
|
275
289
|
// The discussion is the comments and the questions; the kanban orders them by when they were written.
|
|
276
290
|
loadComments: async (ticketIdArg, signalArg) => {
|
|
277
|
-
const { comments, questions } = await this.
|
|
291
|
+
const { comments, questions } = await this.history(ticketIdArg, signalArg);
|
|
278
292
|
return [...comments.map(kanbanComment), ...questions.map(kanbanQuestion)];
|
|
279
293
|
},
|
|
280
|
-
loadActivity: async (ticketIdArg, signalArg) => (
|
|
281
|
-
(await this.fire('get', { ticketId: ticketIdArg }, signalArg)).activity.map(kanbanActivity)
|
|
282
|
-
),
|
|
294
|
+
loadActivity: async (ticketIdArg, signalArg) => (await this.history(ticketIdArg, signalArg)).activity.map(kanbanActivity),
|
|
283
295
|
addComment: async (inputArg) => this.change(async () => (
|
|
284
296
|
kanbanComment((await this.fire('comment', { ticketId: inputArg.ticketId, body: inputArg.body })).comment)
|
|
285
297
|
)),
|
|
@@ -36,7 +36,7 @@ export interface IGeneralSettingsView {
|
|
|
36
36
|
gpu: 'enabled' | 'disabled';
|
|
37
37
|
activeGpu: 'enabled' | 'disabled';
|
|
38
38
|
presentation: interfaces.TControllerBrowserPresentation;
|
|
39
|
-
/**
|
|
39
|
+
/** Why Paint cannot be used now, or that no browser runtime runs; empty while Paint is available. */
|
|
40
40
|
paintState: string;
|
|
41
41
|
/** Paint's integrity retries stopped and can be started again. */
|
|
42
42
|
paintRearm: boolean;
|
|
@@ -90,10 +90,15 @@ export const renderGeneralSettings = (
|
|
|
90
90
|
leading: html`<mp-harness-mark .harness=${rowArg.harnessId}></mp-harness-mark>`,
|
|
91
91
|
label: rowArg.name,
|
|
92
92
|
...(rowArg.note ? { description: rowArg.note } : {}),
|
|
93
|
-
|
|
93
|
+
// One folded button per row (model · effort), as wide as it says and at most a share of a narrow screen;
|
|
94
|
+
// it opens the model and the effort in a speech bubble. A saved default no longer offered gives it the
|
|
95
|
+
// warning tone.
|
|
96
|
+
control: html`<mp-model-picker class="settingsDefault" data-harness=${rowArg.harnessId} fold="always"
|
|
97
|
+
style="max-width: min(360px, 45vw);" .label=${`Default model for ${rowArg.name}`}
|
|
98
|
+
.warning=${rowArg.noteIsWarning ? rowArg.note : ''}
|
|
94
99
|
.models=${rowArg.models} .model=${rowArg.model} .efforts=${rowArg.efforts} .effort=${rowArg.effort}
|
|
95
100
|
.effortDisabledReason=${rowArg.effortDisabledReason} .modelPlaceholder=${'Choose a model'}
|
|
96
|
-
.modelLabel=${
|
|
101
|
+
.modelLabel=${'Model'} .effortLabel=${'Reasoning effort'}
|
|
97
102
|
@mp-model-change=${(eventArg: CustomEvent<TMpModelChoice>) => actionsArg.setDefault(rowArg.harnessId, eventArg.detail)}
|
|
98
103
|
></mp-model-picker>`,
|
|
99
104
|
})),
|
|
@@ -161,7 +166,8 @@ export const renderGeneralSettings = (
|
|
|
161
166
|
{
|
|
162
167
|
key: 'presentation',
|
|
163
168
|
label: 'New browser views show',
|
|
164
|
-
description: `Paint draws the page as vector scenes in this tab; Video streams it. Each view can switch on its own
|
|
169
|
+
description: `Paint draws the page as vector scenes in this tab; Video streams it. Each view can switch on its own.${
|
|
170
|
+
viewArg.paintState ? ` ${viewArg.paintState}` : ''}`,
|
|
165
171
|
control: html`
|
|
166
172
|
${viewArg.paintRearm
|
|
167
173
|
? html`<dees-button class="settingsBrowserPaintRearm" size="sm" text="Retry Paint" @clicked=${() => actionsArg.rearmPaint()}></dees-button>`
|