@zq-silk/yui 0.2.0 → 0.4.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/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
package/dist/role/role.js
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { defaultRoleAgentConfig, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
3
|
+
import { validateRoleSessionSet } from "../executor/agentExecutor.js";
|
|
1
4
|
export function createRoleAgentBinding(agent, config) {
|
|
2
5
|
const agentId = requireSafeIdentity(agent.id, "Role Agent id");
|
|
3
6
|
const adapterId = requireSupportedAdapterId(agent.adapterId);
|
|
4
|
-
const
|
|
7
|
+
const defaults = defaultRoleAgentConfig(adapterId);
|
|
8
|
+
const effectiveConfig = config === undefined
|
|
9
|
+
? defaults
|
|
10
|
+
: config.permission === undefined
|
|
11
|
+
? { ...config, permission: defaults.permission }
|
|
12
|
+
: config;
|
|
5
13
|
if (effectiveConfig.adapterId !== adapterId) {
|
|
6
14
|
throw new Error(`Role Agent config adapter does not match Agent: ${agentId}.`);
|
|
7
15
|
}
|
|
8
16
|
return cloneBinding({ agentId, adapterId, config: effectiveConfig });
|
|
9
17
|
}
|
|
10
|
-
export function createRole(taskId, name, bindings, activeAgentId, workspace, now, profile = {}) {
|
|
11
|
-
const owner = createRoleOwner(name, bindings, activeAgentId, workspace, now, profile);
|
|
18
|
+
export function createRole(taskId, name, bindings, activeAgentId, workspace, now, profile = {}, defaultAccess = "write") {
|
|
19
|
+
const owner = createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess);
|
|
12
20
|
return validateTaskRole({
|
|
13
21
|
...owner,
|
|
14
22
|
taskId: requireSafeIdentity(taskId, "Task id"),
|
|
15
23
|
status: "idle"
|
|
16
24
|
});
|
|
17
25
|
}
|
|
18
|
-
export function createGlobalRole(name, bindings, activeAgentId, workspace, now, profile = {}) {
|
|
19
|
-
return createRoleOwner(name, bindings, activeAgentId, workspace, now, profile);
|
|
26
|
+
export function createGlobalRole(name, bindings, activeAgentId, workspace, now, profile = {}, defaultAccess = "write") {
|
|
27
|
+
return createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess);
|
|
20
28
|
}
|
|
21
29
|
export function copyGlobalRoleToTaskRole(globalRole, taskId, now, name = globalRole.name) {
|
|
22
30
|
validateGlobalRole(globalRole);
|
|
23
31
|
const timestamp = now.toISOString();
|
|
24
32
|
return validateTaskRole({
|
|
25
33
|
...cloneProfile(globalRole),
|
|
26
|
-
schemaVersion:
|
|
34
|
+
schemaVersion: 3,
|
|
35
|
+
launchRevision: 1,
|
|
36
|
+
defaultAccess: globalRole.defaultAccess,
|
|
27
37
|
taskId: requireSafeIdentity(taskId, "Task id"),
|
|
28
38
|
name: requireSafeIdentity(name, "Role name"),
|
|
29
39
|
activeAgentId: globalRole.activeAgentId,
|
|
@@ -40,22 +50,30 @@ export function activeRoleAgentBinding(role) {
|
|
|
40
50
|
}
|
|
41
51
|
export function updateGlobalRole(role, patch, now) {
|
|
42
52
|
validateGlobalRole(role);
|
|
53
|
+
const desiredBefore = desiredLaunchProjection(role);
|
|
43
54
|
const updated = {
|
|
44
55
|
...role,
|
|
45
56
|
...cloneRolePatch(patch),
|
|
46
57
|
updatedAt: now.toISOString()
|
|
47
58
|
};
|
|
48
59
|
clearProfileFields(updated, patch);
|
|
60
|
+
updated.launchRevision = isDeepStrictEqual(desiredBefore, desiredLaunchProjection(updated))
|
|
61
|
+
? role.launchRevision
|
|
62
|
+
: role.launchRevision + 1;
|
|
49
63
|
return validateGlobalRole(updated);
|
|
50
64
|
}
|
|
51
65
|
export function updateRole(role, patch, now) {
|
|
52
66
|
validateTaskRole(role);
|
|
67
|
+
const desiredBefore = desiredLaunchProjection(role);
|
|
53
68
|
const updated = {
|
|
54
69
|
...role,
|
|
55
70
|
...cloneRolePatch(patch),
|
|
56
71
|
updatedAt: now.toISOString()
|
|
57
72
|
};
|
|
58
73
|
clearProfileFields(updated, patch);
|
|
74
|
+
updated.launchRevision = isDeepStrictEqual(desiredBefore, desiredLaunchProjection(updated))
|
|
75
|
+
? role.launchRevision
|
|
76
|
+
: role.launchRevision + 1;
|
|
59
77
|
return validateTaskRole(updated);
|
|
60
78
|
}
|
|
61
79
|
export function updateRoleStatus(role, status, now) {
|
|
@@ -66,31 +84,26 @@ export function updateRoleStatus(role, status, now) {
|
|
|
66
84
|
}
|
|
67
85
|
export function switchActiveRoleAgent(role, sessions, targetAgentId, runtime, now) {
|
|
68
86
|
validateRoleOwner(role);
|
|
69
|
-
if (runtime.activeRun) {
|
|
70
|
-
throw new Error("Cannot switch Role Agent while an active AgentRun exists.");
|
|
71
|
-
}
|
|
72
|
-
if (runtime.nativeProcessRunning) {
|
|
73
|
-
throw new Error("Cannot switch Role Agent while the native Agent process is running.");
|
|
74
|
-
}
|
|
75
87
|
const normalizedTarget = requireSafeIdentity(targetAgentId, "Target Agent id");
|
|
76
88
|
if (!Object.hasOwn(role.agentBindings, normalizedTarget)) {
|
|
77
89
|
throw new Error(`Role Agent is not bound: ${normalizedTarget}.`);
|
|
78
90
|
}
|
|
79
91
|
assertSessionOwnerMatchesRole(role, sessions);
|
|
80
|
-
if (sessions.activeAgentId !== role.activeAgentId) {
|
|
81
|
-
throw new Error(`Role session active Agent does not match Role: ${role.name}.`);
|
|
82
|
-
}
|
|
83
92
|
const fromAgentId = role.activeAgentId;
|
|
84
93
|
const mode = sessions.sessions[normalizedTarget] === undefined ? "new" : "resume";
|
|
85
94
|
const timestamp = now.toISOString();
|
|
86
95
|
const updatedRole = "taskId" in role
|
|
87
96
|
? updateRole(role, { activeAgentId: normalizedTarget }, now)
|
|
88
97
|
: updateGlobalRole(role, { activeAgentId: normalizedTarget }, now);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
// A running process keeps its immutable effective identity. The desired
|
|
99
|
+
// switch becomes visible only when the next launch is planned.
|
|
100
|
+
const updatedSessions = runtime.activeRun || runtime.nativeProcessRunning
|
|
101
|
+
? sessions
|
|
102
|
+
: {
|
|
103
|
+
...sessions,
|
|
104
|
+
activeAgentId: normalizedTarget,
|
|
105
|
+
updatedAt: timestamp
|
|
106
|
+
};
|
|
94
107
|
return {
|
|
95
108
|
role: updatedRole,
|
|
96
109
|
sessions: updatedSessions,
|
|
@@ -101,6 +114,62 @@ export function switchActiveRoleAgent(role, sessions, targetAgentId, runtime, no
|
|
|
101
114
|
}
|
|
102
115
|
};
|
|
103
116
|
}
|
|
117
|
+
export function unbindRoleAgent(role, sessions, agentId, now) {
|
|
118
|
+
validateRoleOwner(role);
|
|
119
|
+
const normalizedAgentId = requireSafeIdentity(agentId, "Role Agent id");
|
|
120
|
+
if (normalizedAgentId === role.activeAgentId) {
|
|
121
|
+
throw new Error(`Cannot unbind active Role Agent: ${normalizedAgentId}.`);
|
|
122
|
+
}
|
|
123
|
+
if (!Object.hasOwn(role.agentBindings, normalizedAgentId)) {
|
|
124
|
+
throw new Error(`Role Agent is not bound: ${normalizedAgentId}.`);
|
|
125
|
+
}
|
|
126
|
+
let updatedSessions = sessions;
|
|
127
|
+
if (sessions !== null) {
|
|
128
|
+
validateRoleSessionSet(sessions);
|
|
129
|
+
assertSessionOwnerMatchesRole(role, sessions);
|
|
130
|
+
const taskSessions = sessions.owner.scope === "task"
|
|
131
|
+
? sessions
|
|
132
|
+
: null;
|
|
133
|
+
if (taskSessions?.inFlight?.agentId === normalizedAgentId) {
|
|
134
|
+
throw new Error(`Cannot unbind Role Agent with an active Run: ${normalizedAgentId}.`);
|
|
135
|
+
}
|
|
136
|
+
const targetSession = sessions.sessions[normalizedAgentId];
|
|
137
|
+
if (targetSession !== undefined && targetSession.status !== "stopped") {
|
|
138
|
+
throw new Error(`Cannot unbind Role Agent while its native session is ${targetSession.status}: `
|
|
139
|
+
+ `${normalizedAgentId}.`);
|
|
140
|
+
}
|
|
141
|
+
const globalSessions = sessions.owner.scope === "global"
|
|
142
|
+
? sessions
|
|
143
|
+
: null;
|
|
144
|
+
const targetHistory = globalSessions === null
|
|
145
|
+
? []
|
|
146
|
+
: Object.entries(globalSessions.history ?? {}).filter(([, session]) => (session.agentId === normalizedAgentId));
|
|
147
|
+
const targetTaskHistory = (taskSessions?.history ?? []).filter((session) => (session.agentId === normalizedAgentId));
|
|
148
|
+
if (targetSession !== undefined || targetHistory.length > 0 || targetTaskHistory.length > 0) {
|
|
149
|
+
const remainingSessions = { ...sessions.sessions };
|
|
150
|
+
delete remainingSessions[normalizedAgentId];
|
|
151
|
+
const remainingHistory = globalSessions === null
|
|
152
|
+
? undefined
|
|
153
|
+
: Object.fromEntries(Object.entries(globalSessions.history ?? {}).filter(([, session]) => (session.agentId !== normalizedAgentId)));
|
|
154
|
+
const remainingTaskHistory = taskSessions === null
|
|
155
|
+
? undefined
|
|
156
|
+
: (taskSessions.history ?? []).filter((session) => (session.agentId !== normalizedAgentId));
|
|
157
|
+
updatedSessions = validateRoleSessionSet({
|
|
158
|
+
...sessions,
|
|
159
|
+
sessions: remainingSessions,
|
|
160
|
+
...(remainingHistory === undefined ? {} : { history: remainingHistory }),
|
|
161
|
+
...(remainingTaskHistory === undefined ? {} : { history: remainingTaskHistory }),
|
|
162
|
+
updatedAt: now.toISOString()
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const remainingBindings = { ...role.agentBindings };
|
|
167
|
+
delete remainingBindings[normalizedAgentId];
|
|
168
|
+
const updatedRole = "taskId" in role
|
|
169
|
+
? updateRole(role, { agentBindings: remainingBindings }, now)
|
|
170
|
+
: updateGlobalRole(role, { agentBindings: remainingBindings }, now);
|
|
171
|
+
return { role: updatedRole, sessions: updatedSessions };
|
|
172
|
+
}
|
|
104
173
|
export function validateGlobalRole(role) {
|
|
105
174
|
return validateRoleOwner(role);
|
|
106
175
|
}
|
|
@@ -110,7 +179,7 @@ export function validateTaskRole(role) {
|
|
|
110
179
|
throw new Error(`Role status is invalid: ${role.status}.`);
|
|
111
180
|
return validateRoleOwner(role);
|
|
112
181
|
}
|
|
113
|
-
function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile) {
|
|
182
|
+
function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess) {
|
|
114
183
|
const mappedBindings = {};
|
|
115
184
|
for (const sourceBinding of bindings) {
|
|
116
185
|
const binding = validateRoleAgentBinding(cloneBinding(sourceBinding));
|
|
@@ -122,7 +191,9 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile)
|
|
|
122
191
|
const timestamp = now.toISOString();
|
|
123
192
|
return validateGlobalRole({
|
|
124
193
|
...cloneProfile(profile),
|
|
125
|
-
schemaVersion:
|
|
194
|
+
schemaVersion: 3,
|
|
195
|
+
launchRevision: 1,
|
|
196
|
+
defaultAccess,
|
|
126
197
|
name: requireSafeIdentity(name, "Role name"),
|
|
127
198
|
activeAgentId: requireSafeIdentity(activeAgentId, "Role active Agent id"),
|
|
128
199
|
agentBindings: mappedBindings,
|
|
@@ -132,19 +203,34 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile)
|
|
|
132
203
|
});
|
|
133
204
|
}
|
|
134
205
|
function validateRoleOwner(role) {
|
|
135
|
-
if (role.schemaVersion !==
|
|
206
|
+
if (role.schemaVersion !== 3)
|
|
136
207
|
throw new Error("Role schema version is invalid.");
|
|
208
|
+
if (!Number.isSafeInteger(role.launchRevision) || role.launchRevision < 1) {
|
|
209
|
+
throw new Error("Role launch revision must be a positive integer.");
|
|
210
|
+
}
|
|
211
|
+
if (role.defaultAccess !== "read" && role.defaultAccess !== "write") {
|
|
212
|
+
throw new Error("Role default access is invalid.");
|
|
213
|
+
}
|
|
137
214
|
requireSafeIdentity(role.name, "Role name");
|
|
138
215
|
requireSafeIdentity(role.activeAgentId, "Role active Agent id");
|
|
139
216
|
requireText(role.workspace, "Role workspace");
|
|
140
217
|
const entries = Object.entries(role.agentBindings);
|
|
141
218
|
if (entries.length === 0)
|
|
142
219
|
throw new Error("Role requires at least one Agent binding.");
|
|
220
|
+
const operatorAdapters = new Map();
|
|
143
221
|
for (const [agentId, binding] of entries) {
|
|
144
222
|
validateRoleAgentBinding(binding);
|
|
145
223
|
if (agentId !== binding.agentId) {
|
|
146
224
|
throw new Error(`Role Agent binding identity is inconsistent: ${agentId}.`);
|
|
147
225
|
}
|
|
226
|
+
if (!("taskId" in role) && role.name === "operator") {
|
|
227
|
+
const existingAgentId = operatorAdapters.get(binding.adapterId);
|
|
228
|
+
if (existingAgentId !== undefined) {
|
|
229
|
+
throw new Error("Operator supports one Agent per adapter; "
|
|
230
|
+
+ `${binding.adapterId} is bound to both ${existingAgentId} and ${agentId}.`);
|
|
231
|
+
}
|
|
232
|
+
operatorAdapters.set(binding.adapterId, agentId);
|
|
233
|
+
}
|
|
148
234
|
}
|
|
149
235
|
if (!Object.hasOwn(role.agentBindings, role.activeAgentId)) {
|
|
150
236
|
throw new Error(`Role active Agent is not bound: ${role.activeAgentId}.`);
|
|
@@ -160,6 +246,10 @@ function validateRoleAgentBinding(binding) {
|
|
|
160
246
|
if (binding.config.adapterId !== adapterId) {
|
|
161
247
|
throw new Error(`Role Agent binding adapter is inconsistent: ${agentId}.`);
|
|
162
248
|
}
|
|
249
|
+
if (binding.config.permission === undefined) {
|
|
250
|
+
throw new Error(`Role Agent binding requires an explicit permission strategy: ${agentId}.`);
|
|
251
|
+
}
|
|
252
|
+
resolveAgentAdapter(adapterId).canonicalizeConfig(binding.config);
|
|
163
253
|
return binding;
|
|
164
254
|
}
|
|
165
255
|
function assertSessionOwnerMatchesRole(role, sessions) {
|
|
@@ -178,10 +268,21 @@ function cloneRolePatch(patch) {
|
|
|
178
268
|
? {}
|
|
179
269
|
: { activeAgentId: requireSafeIdentity(patch.activeAgentId, "Role active Agent id") }),
|
|
180
270
|
...(patch.workspace === undefined ? {} : { workspace: requireText(patch.workspace, "Role workspace") }),
|
|
271
|
+
...(patch.defaultAccess === undefined ? {} : { defaultAccess: patch.defaultAccess }),
|
|
181
272
|
...(patch.agentBindings === undefined ? {} : { agentBindings: cloneBindings(patch.agentBindings) }),
|
|
182
273
|
...cloneProfile(patch)
|
|
183
274
|
};
|
|
184
275
|
}
|
|
276
|
+
function desiredLaunchProjection(role) {
|
|
277
|
+
return {
|
|
278
|
+
name: role.name,
|
|
279
|
+
activeAgentId: role.activeAgentId,
|
|
280
|
+
agentBindings: role.agentBindings,
|
|
281
|
+
workspace: role.workspace,
|
|
282
|
+
defaultAccess: role.defaultAccess,
|
|
283
|
+
...cloneProfile(role)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
185
286
|
function cloneBindings(bindings) {
|
|
186
287
|
return Object.fromEntries(Object.entries(bindings).map(([agentId, binding]) => [agentId, cloneBinding(binding)]));
|
|
187
288
|
}
|
package/dist/run/agentRun.js
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
2
|
+
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
|
+
import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
|
|
4
|
+
export function createAgentRun(id, taskId, roleName, mode, input, now, context) {
|
|
2
5
|
if (mode !== "new" && mode !== "resume") {
|
|
3
6
|
throw new Error(`Agent run dispatch mode is invalid: ${mode}.`);
|
|
4
7
|
}
|
|
5
8
|
const timestamp = now.toISOString();
|
|
6
9
|
return {
|
|
7
|
-
schemaVersion:
|
|
10
|
+
schemaVersion: 6,
|
|
8
11
|
id: requireSafeIdentity(id, "Agent run id"),
|
|
9
12
|
taskId: requireSafeIdentity(taskId, "Task id"),
|
|
10
13
|
roleName: requireSafeIdentity(roleName, "Role name"),
|
|
11
14
|
mode,
|
|
12
15
|
input: requireText(input, "Agent run input"),
|
|
16
|
+
purpose: context.purpose ?? "execution",
|
|
13
17
|
...(context.workItemId === undefined
|
|
14
18
|
? {}
|
|
15
19
|
: { workItemId: requireSafeIdentity(context.workItemId, "Work item id") }),
|
|
20
|
+
...(context.reviewRoundId === undefined
|
|
21
|
+
? {}
|
|
22
|
+
: { reviewRoundId: requireSafeIdentity(context.reviewRoundId, "ReviewRound id") }),
|
|
23
|
+
...(context.executionGroupId === undefined
|
|
24
|
+
? {}
|
|
25
|
+
: { executionGroupId: requireSafeIdentity(context.executionGroupId, "ExecutionGroup id") }),
|
|
26
|
+
...(context.executionLaneId === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { executionLaneId: requireSafeIdentity(context.executionLaneId, "ExecutionLane id") }),
|
|
29
|
+
...(context.workspace === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: { workspace: validateManagedWorkspace(context.workspace) }),
|
|
32
|
+
effective: validateEffectiveLaunchSnapshot(context.effective),
|
|
16
33
|
status: "active",
|
|
17
34
|
createdAt: timestamp,
|
|
18
35
|
updatedAt: timestamp
|
|
@@ -21,6 +38,15 @@ export function createAgentRun(id, taskId, roleName, mode, input, now, context =
|
|
|
21
38
|
export function isActiveAgentRun(run) {
|
|
22
39
|
return run.status === "active";
|
|
23
40
|
}
|
|
41
|
+
export function markAgentRunPushed(run, now) {
|
|
42
|
+
if (run.status !== "active") {
|
|
43
|
+
throw new Error(`Cannot push a terminal Agent run: ${run.id}.`);
|
|
44
|
+
}
|
|
45
|
+
if (run.pushedAt !== undefined)
|
|
46
|
+
return run;
|
|
47
|
+
const timestamp = now.toISOString();
|
|
48
|
+
return { ...run, pushedAt: timestamp, updatedAt: timestamp };
|
|
49
|
+
}
|
|
24
50
|
export function markAgentRunDelivered(run, now) {
|
|
25
51
|
if (run.status !== "active") {
|
|
26
52
|
throw new Error(`Cannot deliver a terminal Agent run: ${run.id}.`);
|
|
@@ -28,27 +54,136 @@ export function markAgentRunDelivered(run, now) {
|
|
|
28
54
|
if (run.deliveredAt !== undefined)
|
|
29
55
|
return run;
|
|
30
56
|
const timestamp = now.toISOString();
|
|
31
|
-
|
|
57
|
+
// Acceptance implies the prompt was pushed first; record both so a consumer
|
|
58
|
+
// never sees delivered-without-pushed.
|
|
59
|
+
return {
|
|
60
|
+
...run,
|
|
61
|
+
...(run.pushedAt === undefined ? { pushedAt: timestamp } : {}),
|
|
62
|
+
deliveredAt: timestamp,
|
|
63
|
+
updatedAt: timestamp
|
|
64
|
+
};
|
|
32
65
|
}
|
|
33
66
|
export function validateAgentRun(run) {
|
|
34
|
-
if (run.schemaVersion !==
|
|
35
|
-
throw new Error("Agent run must use schemaVersion
|
|
36
|
-
|
|
37
|
-
requireSafeIdentity(run.taskId, "Task id");
|
|
67
|
+
if (run.schemaVersion !== 6)
|
|
68
|
+
throw new Error("Agent run must use schemaVersion 6.");
|
|
69
|
+
validateTaskRecordReference({ taskId: run.taskId, localId: run.id }, "agentRun");
|
|
38
70
|
requireSafeIdentity(run.roleName, "Role name");
|
|
39
71
|
if (run.mode !== "new" && run.mode !== "resume") {
|
|
40
72
|
throw new Error(`Agent run dispatch mode is invalid: ${String(run.mode)}.`);
|
|
41
73
|
}
|
|
42
74
|
requireText(run.input, "Agent run input");
|
|
43
|
-
if (run.
|
|
44
|
-
|
|
75
|
+
if (!["execution", "review"].includes(run.purpose)) {
|
|
76
|
+
throw new Error(`Agent run purpose is invalid: ${String(run.purpose)}.`);
|
|
77
|
+
}
|
|
78
|
+
if (run.workItemId !== undefined) {
|
|
79
|
+
validateTaskRecordReference({ taskId: run.taskId, localId: run.workItemId }, "workItem");
|
|
80
|
+
}
|
|
81
|
+
if (run.reviewRoundId !== undefined) {
|
|
82
|
+
validateTaskRecordReference({ taskId: run.taskId, localId: run.reviewRoundId }, "reviewRound");
|
|
83
|
+
}
|
|
84
|
+
if ((run.executionGroupId === undefined) !== (run.executionLaneId === undefined)) {
|
|
85
|
+
throw new Error("Agent run execution lineage is incomplete.");
|
|
86
|
+
}
|
|
87
|
+
if (run.executionGroupId !== undefined) {
|
|
88
|
+
requireSafeIdentity(run.executionGroupId, "ExecutionGroup id");
|
|
89
|
+
requireSafeIdentity(run.executionLaneId, "ExecutionLane id");
|
|
90
|
+
}
|
|
91
|
+
if (run.workspace !== undefined) {
|
|
92
|
+
validateManagedWorkspace(run.workspace);
|
|
93
|
+
if (run.workspace.owner.taskId !== run.taskId) {
|
|
94
|
+
throw new Error("Agent run workspace belongs to another Task.");
|
|
95
|
+
}
|
|
96
|
+
if (run.workspace.owner.type === "work-item"
|
|
97
|
+
&& run.workspace.owner.workItemId !== run.workItemId) {
|
|
98
|
+
throw new Error("Agent run workspace belongs to another Work Item.");
|
|
99
|
+
}
|
|
100
|
+
if (run.workspace.owner.type === "work-item" && run.workItemId === undefined) {
|
|
101
|
+
throw new Error("A WorkItem workspace requires a WorkItem Agent run reference.");
|
|
102
|
+
}
|
|
103
|
+
if (run.workspace.owner.type === "review-round" && run.purpose !== "review") {
|
|
104
|
+
throw new Error("A ReviewRound workspace requires a review Agent run.");
|
|
105
|
+
}
|
|
106
|
+
if (run.workspace.owner.type === "integration-attempt") {
|
|
107
|
+
throw new Error("An IntegrationAttempt workspace cannot be used by an Agent run.");
|
|
108
|
+
}
|
|
109
|
+
if (run.workspace.owner.type === "execution-lane") {
|
|
110
|
+
if (run.workspace.owner.executionGroupId !== run.executionGroupId
|
|
111
|
+
|| run.workspace.owner.executionLaneId !== run.executionLaneId) {
|
|
112
|
+
throw new Error("Agent run Execution Lane workspace lineage does not match the Run.");
|
|
113
|
+
}
|
|
114
|
+
if (run.workspace.owner.purpose === "execution"
|
|
115
|
+
&& run.workspace.owner.workItemId !== run.workItemId) {
|
|
116
|
+
throw new Error("Agent run Execution Lane workspace WorkItem does not match the Run.");
|
|
117
|
+
}
|
|
118
|
+
if (run.workspace.owner.purpose === "review"
|
|
119
|
+
&& run.workspace.owner.reviewRoundId !== run.reviewRoundId) {
|
|
120
|
+
throw new Error("Agent run review Lane workspace ReviewRound does not match the Run.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (run.workspace.owner.type === "review-round"
|
|
124
|
+
&& run.workspace.owner.reviewRoundId !== run.reviewRoundId) {
|
|
125
|
+
throw new Error(`Agent run ReviewRound workspace owner does not match ${run.reviewRoundId ?? "none"}.`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (run.purpose === "review") {
|
|
129
|
+
if (run.workItemId === undefined || run.reviewRoundId === undefined) {
|
|
130
|
+
throw new Error("A review Agent run requires WorkItem and ReviewRound references.");
|
|
131
|
+
}
|
|
132
|
+
if (run.workspace === undefined
|
|
133
|
+
|| !((run.workspace.owner.type === "review-round"
|
|
134
|
+
&& run.workspace.owner.reviewRoundId === run.reviewRoundId)
|
|
135
|
+
|| (run.workspace.owner.type === "execution-lane"
|
|
136
|
+
&& run.workspace.owner.purpose === "review"
|
|
137
|
+
&& run.workspace.owner.reviewRoundId === run.reviewRoundId))) {
|
|
138
|
+
throw new Error(`A review Agent run requires its exact ReviewRound workspace owner: ${run.reviewRoundId}.`);
|
|
139
|
+
}
|
|
140
|
+
if (run.workspace.entries.length === 0
|
|
141
|
+
|| run.workspace.entries.some(({ access }) => access !== "write")) {
|
|
142
|
+
throw new Error("A review Agent run requires only isolated writable workspace entries.");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (run.reviewRoundId !== undefined) {
|
|
147
|
+
throw new Error("An execution Agent run cannot reference a ReviewRound.");
|
|
148
|
+
}
|
|
149
|
+
if (run.workspace?.owner.type === "review-round") {
|
|
150
|
+
throw new Error("An execution Agent run cannot use a ReviewRound-owned workspace.");
|
|
151
|
+
}
|
|
152
|
+
if (run.workspace?.owner.type === "execution-lane" && run.workspace.owner.purpose !== "execution") {
|
|
153
|
+
throw new Error("An execution Agent run cannot use a review Lane workspace.");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
validateEffectiveLaunchSnapshot(run.effective);
|
|
157
|
+
if (run.workspace !== undefined
|
|
158
|
+
&& (run.effective.workspace.root !== run.workspace.root
|
|
159
|
+
|| JSON.stringify(run.effective.workspace.entries) !== JSON.stringify(run.workspace.entries))) {
|
|
160
|
+
throw new Error("Agent run effective workspace does not match its managed workspace.");
|
|
161
|
+
}
|
|
162
|
+
if (run.purpose === "review") {
|
|
163
|
+
if (run.effective.reviewRoundId !== run.reviewRoundId) {
|
|
164
|
+
throw new Error("Review Agent run effective provenance does not match its ReviewRound.");
|
|
165
|
+
}
|
|
166
|
+
if (!run.workspace.entries.some(({ baseCommit }) => baseCommit === run.effective.reviewBaseCommit)) {
|
|
167
|
+
throw new Error("Review Agent run effective base does not match its workspace.");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (run.effective.reviewRoundId !== undefined) {
|
|
171
|
+
throw new Error("Execution Agent run cannot carry Review effective provenance.");
|
|
172
|
+
}
|
|
45
173
|
if (!["active", "yielded", "failed"].includes(run.status)) {
|
|
46
174
|
throw new Error(`Agent run status is invalid: ${String(run.status)}.`);
|
|
47
175
|
}
|
|
48
176
|
requireTimestamp(run.createdAt, "Agent run createdAt");
|
|
49
177
|
requireTimestamp(run.updatedAt, "Agent run updatedAt");
|
|
50
|
-
if (run.
|
|
178
|
+
if (run.pushedAt !== undefined)
|
|
179
|
+
requireTimestamp(run.pushedAt, "Agent run pushedAt");
|
|
180
|
+
if (run.deliveredAt !== undefined) {
|
|
51
181
|
requireTimestamp(run.deliveredAt, "Agent run deliveredAt");
|
|
182
|
+
// Acceptance can never precede its transport push.
|
|
183
|
+
if (run.pushedAt === undefined) {
|
|
184
|
+
throw new Error("Agent run deliveredAt requires a prior pushedAt.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
52
187
|
if (run.status === "active") {
|
|
53
188
|
if (run.summary !== undefined || run.endedAt !== undefined) {
|
|
54
189
|
throw new Error("An active Agent run cannot have terminal metadata.");
|
|
@@ -61,10 +196,10 @@ export function validateAgentRun(run) {
|
|
|
61
196
|
return run;
|
|
62
197
|
}
|
|
63
198
|
export function yieldAgentRun(run, summary, now) {
|
|
64
|
-
return finishAgentRun(run, "yielded",
|
|
199
|
+
return finishAgentRun(run, "yielded", requireResultText(summary, "Agent run summary"), now);
|
|
65
200
|
}
|
|
66
201
|
export function failAgentRun(run, summary, now) {
|
|
67
|
-
return finishAgentRun(run, "failed",
|
|
202
|
+
return finishAgentRun(run, "failed", requireResultText(summary, "Agent run summary"), now);
|
|
68
203
|
}
|
|
69
204
|
function finishAgentRun(run, status, summary, now) {
|
|
70
205
|
if (run.status !== "active") {
|
|
@@ -95,6 +230,14 @@ function requireText(value, label) {
|
|
|
95
230
|
throw new Error(`${label} is required.`);
|
|
96
231
|
return normalized;
|
|
97
232
|
}
|
|
233
|
+
/** Result transport preserves the provider's complete text, including outer whitespace. */
|
|
234
|
+
function requireResultText(value, label) {
|
|
235
|
+
if (typeof value !== "string" || value.includes("\0"))
|
|
236
|
+
throw new Error(`${label} is invalid.`);
|
|
237
|
+
if (value.trim().length === 0)
|
|
238
|
+
throw new Error(`${label} is required.`);
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
98
241
|
function requireTimestamp(value, label) {
|
|
99
242
|
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
100
243
|
throw new Error(`${label} must be an ISO timestamp.`);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { MAX_SESSION_TITLE_LENGTH } from "../runtime/sessionTitle.js";
|
|
2
|
+
const TITLED_RUN_SEPARATOR = " · Run ";
|
|
3
|
+
/** Adds the causal token consumed by the native Turn-complete Hook. */
|
|
4
|
+
export function markYuiRunInput(input, runId, title) {
|
|
5
|
+
const text = input.replace(/\r/g, "").trim();
|
|
6
|
+
return managedRunInput(text, runId, title);
|
|
7
|
+
}
|
|
8
|
+
/** Replaces the causal token on an input already owned by a persisted Yui Run. */
|
|
9
|
+
export function retagYuiRunInput(input, runId, title) {
|
|
10
|
+
const lines = input.replace(/\r/g, "").split("\n");
|
|
11
|
+
if (lines.length < 3
|
|
12
|
+
|| lines[1] !== ""
|
|
13
|
+
|| runIdFromHeader(lines[0]) === undefined) {
|
|
14
|
+
throw new Error("Managed Run input header is required.");
|
|
15
|
+
}
|
|
16
|
+
return managedRunInput(lines.slice(2).join("\n").trim(), runId, title);
|
|
17
|
+
}
|
|
18
|
+
function managedRunInput(text, runId, title) {
|
|
19
|
+
const id = requireRunId(runId);
|
|
20
|
+
if (text.length === 0)
|
|
21
|
+
throw new Error("Run input is required.");
|
|
22
|
+
return `${requireRunTitle(title)}${TITLED_RUN_SEPARATOR}${id}\n\n${text}`;
|
|
23
|
+
}
|
|
24
|
+
/** Reads the last managed Yui header from Codex's structured input-messages. */
|
|
25
|
+
export function yuiRunIdFromInputMessages(value) {
|
|
26
|
+
if (!Array.isArray(value))
|
|
27
|
+
return undefined;
|
|
28
|
+
let found;
|
|
29
|
+
for (const entry of value) {
|
|
30
|
+
if (typeof entry !== "string")
|
|
31
|
+
continue;
|
|
32
|
+
const lines = entry.replace(/\r/g, "").split("\n");
|
|
33
|
+
if (lines.length < 3 || lines[1] !== "")
|
|
34
|
+
continue;
|
|
35
|
+
const runId = runIdFromHeader(lines[0]);
|
|
36
|
+
if (runId !== undefined)
|
|
37
|
+
found = runId;
|
|
38
|
+
}
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
/** Removes only a valid managed Run header, preserving user-authored lookalikes. */
|
|
42
|
+
export function yuiRunBodyFromInputMessage(value) {
|
|
43
|
+
const lines = value.replace(/\r/g, "").trim().split("\n");
|
|
44
|
+
return lines.length >= 3
|
|
45
|
+
&& lines[1] === ""
|
|
46
|
+
&& runIdFromHeader(lines[0]) !== undefined
|
|
47
|
+
? lines.slice(2).join("\n")
|
|
48
|
+
: lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
function runIdFromHeader(line) {
|
|
51
|
+
const separator = line.lastIndexOf(TITLED_RUN_SEPARATOR);
|
|
52
|
+
if (separator > 0) {
|
|
53
|
+
try {
|
|
54
|
+
requireRunTitle(line.slice(0, separator));
|
|
55
|
+
return requireRunId(line.slice(separator + TITLED_RUN_SEPARATOR.length));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function requireRunTitle(value) {
|
|
64
|
+
const title = value.trim();
|
|
65
|
+
if (title.length === 0
|
|
66
|
+
|| title.length > MAX_SESSION_TITLE_LENGTH
|
|
67
|
+
|| !title.startsWith("Yui · ")
|
|
68
|
+
|| /[\r\n\0]/u.test(title)) {
|
|
69
|
+
throw new Error("Run title is invalid.");
|
|
70
|
+
}
|
|
71
|
+
return title;
|
|
72
|
+
}
|
|
73
|
+
function requireRunId(value) {
|
|
74
|
+
const id = value.trim();
|
|
75
|
+
if (id.length === 0
|
|
76
|
+
|| id.length > 1_024
|
|
77
|
+
|| ["__proto__", "prototype", "constructor", ".", ".."].includes(id)
|
|
78
|
+
|| /[\/\\\0\s]/.test(id)) {
|
|
79
|
+
throw new Error("Run id is invalid.");
|
|
80
|
+
}
|
|
81
|
+
return id;
|
|
82
|
+
}
|