chatroom-cli 1.132.1 → 1.133.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +643 -46
- package/dist/index.js.map +25 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20416,6 +20416,10 @@ var init_agent_role = __esm(() => {
|
|
|
20416
20416
|
reviewer: {
|
|
20417
20417
|
tags: ["ephemeral" /* Ephemeral */],
|
|
20418
20418
|
teams: [{ teamId: "duo", order: 35 }]
|
|
20419
|
+
},
|
|
20420
|
+
"game-asset-designer": {
|
|
20421
|
+
tags: ["ephemeral" /* Ephemeral */],
|
|
20422
|
+
teams: [{ teamId: "duo", order: 42 }]
|
|
20419
20423
|
}
|
|
20420
20424
|
};
|
|
20421
20425
|
AGENT_ROLE_DEFINITIONS = Object.fromEntries(Object.entries(roleMetadata).map(([role, metadata]) => [role, { role, ...metadata }]));
|
|
@@ -35274,6 +35278,25 @@ var init_assigned_task = __esm(() => {
|
|
|
35274
35278
|
ACTIVE_TASK_STATUSES = ["pending", "acknowledged", "in_progress"];
|
|
35275
35279
|
});
|
|
35276
35280
|
|
|
35281
|
+
// src/daemon/domain/usecase/compute-backoff-delay.ts
|
|
35282
|
+
function computeBackoffDelay(attempt, opts, random3 = Math.random) {
|
|
35283
|
+
const exp = Math.min(opts.maxMs, opts.baseMs * 2 ** (attempt - 1));
|
|
35284
|
+
const jitterRatio = opts.jitter ?? 0;
|
|
35285
|
+
if (jitterRatio <= 0)
|
|
35286
|
+
return Math.max(0, Math.round(exp));
|
|
35287
|
+
const jitter = exp * jitterRatio * (random3() * 2 - 1);
|
|
35288
|
+
return Math.max(0, Math.round(exp + jitter));
|
|
35289
|
+
}
|
|
35290
|
+
var REDELIVERY_BACKOFF_BASE_MS = 5000, REDELIVERY_BACKOFF_MAX_MS, REDELIVERY_BACKOFF_OPTIONS;
|
|
35291
|
+
var init_compute_backoff_delay = __esm(() => {
|
|
35292
|
+
REDELIVERY_BACKOFF_MAX_MS = 5 * 60000;
|
|
35293
|
+
REDELIVERY_BACKOFF_OPTIONS = {
|
|
35294
|
+
baseMs: REDELIVERY_BACKOFF_BASE_MS,
|
|
35295
|
+
maxMs: REDELIVERY_BACKOFF_MAX_MS,
|
|
35296
|
+
jitter: 0.2
|
|
35297
|
+
};
|
|
35298
|
+
});
|
|
35299
|
+
|
|
35277
35300
|
// src/daemon/infrastructure/local/harness/services/spawn-prompt.ts
|
|
35278
35301
|
function createSpawnPrompt(raw, opts) {
|
|
35279
35302
|
if (opts?.nativeBootstrap) {
|
|
@@ -36881,6 +36904,26 @@ var init_native_task_injector = __esm(() => {
|
|
|
36881
36904
|
};
|
|
36882
36905
|
});
|
|
36883
36906
|
|
|
36907
|
+
// src/daemon/services/agent-process-service/domain/usecase/is-cli-driven-harness.ts
|
|
36908
|
+
function isCliDrivenHarness(harness) {
|
|
36909
|
+
if (harness == null)
|
|
36910
|
+
return false;
|
|
36911
|
+
const capabilities = getHarnessCapabilities(harness);
|
|
36912
|
+
return capabilities?.runtimeKind === "cli";
|
|
36913
|
+
}
|
|
36914
|
+
var init_is_cli_driven_harness = __esm(() => {
|
|
36915
|
+
init_types();
|
|
36916
|
+
});
|
|
36917
|
+
|
|
36918
|
+
// src/daemon/services/agent-process-service/domain/usecase/restart-retry-interval.ts
|
|
36919
|
+
function restartRetryMaxIntervalMs(cliDriven) {
|
|
36920
|
+
return cliDriven ? CLI_RESTART_RETRY_MAX_INTERVAL_MS : AGENT_RESTART_RETRY_MAX_INTERVAL_MS;
|
|
36921
|
+
}
|
|
36922
|
+
var AGENT_RESTART_RETRY_MAX_INTERVAL_MS, CLI_RESTART_RETRY_MAX_INTERVAL_MS = 30000;
|
|
36923
|
+
var init_restart_retry_interval = __esm(() => {
|
|
36924
|
+
AGENT_RESTART_RETRY_MAX_INTERVAL_MS = 10 * 60000;
|
|
36925
|
+
});
|
|
36926
|
+
|
|
36884
36927
|
// src/daemon/services/agent-process-service/service/agent-work-manager.ts
|
|
36885
36928
|
class AgentWorkManager {
|
|
36886
36929
|
deps;
|
|
@@ -36895,6 +36938,8 @@ class AgentWorkManager {
|
|
|
36895
36938
|
recoveryGates = new Map;
|
|
36896
36939
|
respawnStartRetries = new Map;
|
|
36897
36940
|
respawnRestartSeq = 0;
|
|
36941
|
+
turnFailureAccountingFailures = new Map;
|
|
36942
|
+
turnFailureReleaseTimers = new Map;
|
|
36898
36943
|
disposed = false;
|
|
36899
36944
|
constructor(deps) {
|
|
36900
36945
|
this.deps = deps;
|
|
@@ -36927,11 +36972,14 @@ class AgentWorkManager {
|
|
|
36927
36972
|
recordDeliveryFailure: deps.taskService.recordDeliveryFailure,
|
|
36928
36973
|
clearDeliveryFailure: deps.taskService.clearDeliveryFailure,
|
|
36929
36974
|
recordTurnFailure: deps.taskService.recordTurnFailure,
|
|
36975
|
+
parkTurnFailure: deps.taskService.parkTurnFailure,
|
|
36930
36976
|
decideUncoveredTurnEnd: deps.taskService.decideUncoveredTurnEnd,
|
|
36931
36977
|
confirmNudgeSent: deps.taskService.confirmNudgeSent,
|
|
36932
36978
|
resetTurnTracking: deps.taskService.resetTurnTracking,
|
|
36933
36979
|
isRedeliveryExhausted: deps.taskService.isRedeliveryExhausted,
|
|
36934
36980
|
clearRedeliveryTracking: deps.taskService.clearRedeliveryTracking,
|
|
36981
|
+
markTurnFailureReleasePending: deps.taskService.markTurnFailureReleasePending,
|
|
36982
|
+
clearTurnFailureReleasePending: deps.taskService.clearTurnFailureReleasePending,
|
|
36935
36983
|
forgetStaleTask: deps.taskService.forgetStaleTask,
|
|
36936
36984
|
loadAssignedTaskForAction: deps.taskService.loadAssignedTaskForAction,
|
|
36937
36985
|
deliverNativeTask: (task, harnessSessionId, onTaskDelivered, isCurrent) => this.nativeTaskDeliveryQueue.enqueue({
|
|
@@ -36964,6 +37012,9 @@ class AgentWorkManager {
|
|
|
36964
37012
|
role: event.role
|
|
36965
37013
|
});
|
|
36966
37014
|
}
|
|
37015
|
+
if (event.reason === "user.start" || event.reason === "user.restart") {
|
|
37016
|
+
this.cancelTurnFailureReleasesForRole(event.chatroomId, event.role);
|
|
37017
|
+
}
|
|
36967
37018
|
await this.requestReconcile({
|
|
36968
37019
|
chatroomId: event.chatroomId,
|
|
36969
37020
|
role: event.role,
|
|
@@ -37021,14 +37072,29 @@ class AgentWorkManager {
|
|
|
37021
37072
|
}
|
|
37022
37073
|
async recoverStoppedRole(args2) {
|
|
37023
37074
|
const key = recoveryKey(args2.chatroomId, args2.role);
|
|
37024
|
-
|
|
37075
|
+
let gate = this.recoveryGates.get(key);
|
|
37025
37076
|
if (!gate)
|
|
37026
37077
|
throw new Error(`role recovery gate not prepared for ${args2.role}@${args2.chatroomId}`);
|
|
37027
|
-
|
|
37028
|
-
|
|
37078
|
+
while (gate.recoveryPromise) {
|
|
37079
|
+
if (args2.mode !== "explicit")
|
|
37080
|
+
return gate.recoveryPromise;
|
|
37081
|
+
await gate.recoveryPromise.catch(() => {
|
|
37082
|
+
return;
|
|
37083
|
+
});
|
|
37084
|
+
if (!this.recoveryGates.has(key)) {
|
|
37085
|
+
await this.prepareRoleRecovery({ chatroomId: args2.chatroomId, role: args2.role });
|
|
37086
|
+
}
|
|
37087
|
+
const nextGate = this.recoveryGates.get(key);
|
|
37088
|
+
if (!nextGate)
|
|
37089
|
+
throw new Error(`role recovery gate not prepared for ${args2.role}@${args2.chatroomId}`);
|
|
37090
|
+
gate = nextGate;
|
|
37091
|
+
}
|
|
37029
37092
|
gate.recoveryPromise = (async () => {
|
|
37030
37093
|
await gate.drainPromise;
|
|
37031
37094
|
const result3 = await this.deps.taskService.recoverInFlightTasks(args2);
|
|
37095
|
+
if (args2.mode === "explicit") {
|
|
37096
|
+
this.cancelTurnFailureReleasesForRole(args2.chatroomId, args2.role);
|
|
37097
|
+
}
|
|
37032
37098
|
if (this.recoveryGates.get(key) === gate) {
|
|
37033
37099
|
if (gate.retryTimer)
|
|
37034
37100
|
clearTimeout(gate.retryTimer);
|
|
@@ -37064,7 +37130,7 @@ class AgentWorkManager {
|
|
|
37064
37130
|
const gate = this.recoveryGates.get(recoveryKey(chatroomId, role));
|
|
37065
37131
|
if (!gate || gate.retryTimer || this.disposed)
|
|
37066
37132
|
return;
|
|
37067
|
-
const delay3 =
|
|
37133
|
+
const delay3 = computeBackoffDelay(gate.retryAttempts++ + 1, RECOVERY_RETRY_BACKOFF);
|
|
37068
37134
|
gate.retryTimer = setTimeout(() => {
|
|
37069
37135
|
gate.retryTimer = undefined;
|
|
37070
37136
|
this.recoverStoppedRole({ chatroomId, role, mode: "automatic" }).catch((error51) => {
|
|
@@ -37073,26 +37139,115 @@ class AgentWorkManager {
|
|
|
37073
37139
|
});
|
|
37074
37140
|
}, delay3);
|
|
37075
37141
|
}
|
|
37076
|
-
async recoverTaskAfterTurnFailure(event, activeTask
|
|
37142
|
+
async recoverTaskAfterTurnFailure(event, activeTask) {
|
|
37077
37143
|
const { chatroomId, role } = event;
|
|
37078
|
-
const { exceeded } = await this.deps.taskService.recordTurnFailure({
|
|
37144
|
+
const { attempts, exceeded } = await this.deps.taskService.recordTurnFailure({
|
|
37079
37145
|
chatroomId,
|
|
37080
37146
|
role,
|
|
37081
37147
|
taskId: activeTask.taskId
|
|
37082
37148
|
});
|
|
37083
|
-
if (exceeded)
|
|
37084
|
-
|
|
37085
|
-
try {
|
|
37086
|
-
await this.deps.taskService.releaseTaskAfterTurnFailure({
|
|
37149
|
+
if (exceeded) {
|
|
37150
|
+
this.cancelTurnFailureRelease({
|
|
37087
37151
|
chatroomId,
|
|
37088
37152
|
role,
|
|
37089
37153
|
taskId: activeTask.taskId
|
|
37090
37154
|
});
|
|
37155
|
+
return "exceeded";
|
|
37156
|
+
}
|
|
37157
|
+
this.scheduleTurnFailureRelease({ chatroomId, role, taskId: activeTask.taskId }, attempts, event.completion.turnId, "turn-failure");
|
|
37158
|
+
return "scheduled";
|
|
37159
|
+
}
|
|
37160
|
+
scheduleTurnFailureRelease(key, attempt, turnId, source) {
|
|
37161
|
+
if (this.disposed)
|
|
37162
|
+
return;
|
|
37163
|
+
const keyStr = turnFailureReleaseKey(key);
|
|
37164
|
+
if (source === "turn-failure")
|
|
37165
|
+
this.turnFailureAccountingFailures.delete(keyStr);
|
|
37166
|
+
this.cancelTurnFailureRelease(key);
|
|
37167
|
+
const delay3 = computeBackoffDelay(attempt, REDELIVERY_BACKOFF_OPTIONS);
|
|
37168
|
+
this.deps.taskService.markTurnFailureReleasePending(key);
|
|
37169
|
+
const timer = setTimeout(() => {
|
|
37170
|
+
this.turnFailureReleaseTimers.delete(keyStr);
|
|
37171
|
+
this.deps.taskService.clearTurnFailureReleasePending(key);
|
|
37172
|
+
if (this.disposed)
|
|
37173
|
+
return;
|
|
37174
|
+
this.releaseAfterTurnFailureBackoff(key, attempt);
|
|
37175
|
+
}, delay3);
|
|
37176
|
+
this.turnFailureReleaseTimers.set(keyStr, { key, timer });
|
|
37177
|
+
console.log(`[NativeDelivery:turn-failure-backoff] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} turn=${turnId} attempt=${attempt} delayMs=${delay3}`);
|
|
37178
|
+
}
|
|
37179
|
+
async releaseAfterTurnFailureBackoff(key, attempt) {
|
|
37180
|
+
const current = this.deps.taskInboxState.getForRole(key.chatroomId, key.role, key.taskId);
|
|
37181
|
+
if (!current || current.status !== "in_progress") {
|
|
37182
|
+
console.log(`[NativeDelivery:turn-failure-release-skipped] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} reason=task_not_current status=${current?.status ?? "missing"}`);
|
|
37183
|
+
return;
|
|
37184
|
+
}
|
|
37185
|
+
const activeTask = this.deps.agentTaskState.get({
|
|
37186
|
+
chatroomId: key.chatroomId,
|
|
37187
|
+
role: key.role
|
|
37188
|
+
});
|
|
37189
|
+
if (activeTask?.taskId === key.taskId) {
|
|
37190
|
+
console.log(`[NativeDelivery:turn-failure-release-skipped] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} reason=task_active`);
|
|
37191
|
+
return;
|
|
37192
|
+
}
|
|
37193
|
+
try {
|
|
37194
|
+
await this.deps.taskService.releaseTaskAfterTurnFailure({
|
|
37195
|
+
chatroomId: key.chatroomId,
|
|
37196
|
+
role: key.role,
|
|
37197
|
+
taskId: key.taskId
|
|
37198
|
+
});
|
|
37199
|
+
this.turnFailureAccountingFailures.delete(turnFailureReleaseKey(key));
|
|
37091
37200
|
} catch (error51) {
|
|
37092
|
-
console.error(`[NativeDelivery
|
|
37093
|
-
|
|
37201
|
+
console.error(`[NativeDelivery:turn-failed-release-error] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
|
|
37202
|
+
await this.retryReleaseAfterFailure(key, attempt);
|
|
37203
|
+
}
|
|
37204
|
+
}
|
|
37205
|
+
async retryReleaseAfterFailure(key, previousAttempt) {
|
|
37206
|
+
const keyStr = turnFailureReleaseKey(key);
|
|
37207
|
+
try {
|
|
37208
|
+
const { attempts, exceeded } = await this.deps.taskService.recordTurnFailure(key);
|
|
37209
|
+
this.turnFailureAccountingFailures.delete(keyStr);
|
|
37210
|
+
if (exceeded)
|
|
37211
|
+
return;
|
|
37212
|
+
this.scheduleTurnFailureRelease(key, attempts, "release-retry", "release-retry");
|
|
37213
|
+
} catch (error51) {
|
|
37214
|
+
console.error(`[NativeDelivery:turn-failed-release-retry-error] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} error=${error51 instanceof Error ? error51.stack ?? error51.message : String(error51)}`);
|
|
37215
|
+
const failures = (this.turnFailureAccountingFailures.get(keyStr) ?? 0) + 1;
|
|
37216
|
+
this.turnFailureAccountingFailures.set(keyStr, failures);
|
|
37217
|
+
if (failures >= MAX_TURN_FAILURE_ACCOUNTING_FAILURES) {
|
|
37218
|
+
this.turnFailureAccountingFailures.delete(keyStr);
|
|
37219
|
+
this.cancelTurnFailureRelease(key);
|
|
37220
|
+
console.error(`[NativeDelivery:turn-failure-accounting-stalled] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} failures=${failures}`);
|
|
37221
|
+
try {
|
|
37222
|
+
await this.deps.taskService.parkTurnFailure(key);
|
|
37223
|
+
} catch (parkError) {
|
|
37224
|
+
console.error(`[NativeDelivery:turn-failure-park-error] chatroom=${key.chatroomId} role=${key.role} task=${key.taskId} error=${parkError instanceof Error ? parkError.stack ?? parkError.message : String(parkError)}`);
|
|
37225
|
+
}
|
|
37226
|
+
return;
|
|
37227
|
+
}
|
|
37228
|
+
this.scheduleTurnFailureRelease(key, previousAttempt, "release-retry", "release-retry");
|
|
37229
|
+
}
|
|
37230
|
+
}
|
|
37231
|
+
cancelTurnFailureRelease(key) {
|
|
37232
|
+
const keyStr = turnFailureReleaseKey(key);
|
|
37233
|
+
const entry = this.turnFailureReleaseTimers.get(keyStr);
|
|
37234
|
+
if (entry === undefined)
|
|
37235
|
+
return;
|
|
37236
|
+
clearTimeout(entry.timer);
|
|
37237
|
+
this.turnFailureReleaseTimers.delete(keyStr);
|
|
37238
|
+
this.turnFailureAccountingFailures.delete(keyStr);
|
|
37239
|
+
this.deps.taskService.clearTurnFailureReleasePending(key);
|
|
37240
|
+
}
|
|
37241
|
+
cancelTurnFailureReleasesForRole(chatroomId, role) {
|
|
37242
|
+
const prefix = `${chatroomId}:${role.toLowerCase()}:`;
|
|
37243
|
+
for (const [keyStr, entry] of [...this.turnFailureReleaseTimers]) {
|
|
37244
|
+
if (!keyStr.startsWith(prefix))
|
|
37245
|
+
continue;
|
|
37246
|
+
clearTimeout(entry.timer);
|
|
37247
|
+
this.turnFailureReleaseTimers.delete(keyStr);
|
|
37248
|
+
this.turnFailureAccountingFailures.delete(keyStr);
|
|
37249
|
+
this.deps.taskService.clearTurnFailureReleasePending(entry.key);
|
|
37094
37250
|
}
|
|
37095
|
-
return "released";
|
|
37096
37251
|
}
|
|
37097
37252
|
async handleAgentTurnEnded(event) {
|
|
37098
37253
|
const completion = event.completion;
|
|
@@ -37104,14 +37259,11 @@ class AgentWorkManager {
|
|
|
37104
37259
|
const errorDetail = completion.error;
|
|
37105
37260
|
console.error(`[NativeDelivery:turn-failed] chatroom=${event.chatroomId} role=${event.role} task=${activeTask2?.taskId ?? "none"} turn=${completion.turnId} status=${completion.status} source=${completion.source} error=${errorDetail ?? "none"}`);
|
|
37106
37261
|
if (activeTask2) {
|
|
37107
|
-
const recovery = await this.recoverTaskAfterTurnFailure(event, activeTask2
|
|
37262
|
+
const recovery = await this.recoverTaskAfterTurnFailure(event, activeTask2);
|
|
37108
37263
|
if (recovery === "exceeded") {
|
|
37109
37264
|
this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
|
|
37110
37265
|
return { kind: "release-slot" };
|
|
37111
37266
|
}
|
|
37112
|
-
if (recovery === "recovery-failed") {
|
|
37113
|
-
return { kind: "hold-slot", reason: "task-recovery-failed" };
|
|
37114
|
-
}
|
|
37115
37267
|
this.deps.agentTaskState.clear({ chatroomId: event.chatroomId, role: event.role });
|
|
37116
37268
|
}
|
|
37117
37269
|
const fact = {
|
|
@@ -37300,7 +37452,9 @@ class AgentWorkManager {
|
|
|
37300
37452
|
return;
|
|
37301
37453
|
}
|
|
37302
37454
|
if (outcome.kind === "start-failed") {
|
|
37303
|
-
|
|
37455
|
+
const cliDriven = isCliDrivenHarness(startInput.agentHarness);
|
|
37456
|
+
const attemptLabel = cliDriven ? `1 of ${MAX_RESPAWN_START_ATTEMPTS}` : "1";
|
|
37457
|
+
console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} task=${task.taskId} attempt=${attemptLabel} error=${outcome.error}`);
|
|
37304
37458
|
this.beginRespawnStartRetries({ chatroomId, role, workingDir, startInput });
|
|
37305
37459
|
}
|
|
37306
37460
|
}
|
|
@@ -37315,9 +37469,13 @@ class AgentWorkManager {
|
|
|
37315
37469
|
};
|
|
37316
37470
|
}
|
|
37317
37471
|
}
|
|
37318
|
-
beginRespawnStartRetries(
|
|
37472
|
+
beginRespawnStartRetries(input) {
|
|
37319
37473
|
if (this.disposed)
|
|
37320
37474
|
return;
|
|
37475
|
+
const request3 = {
|
|
37476
|
+
...input,
|
|
37477
|
+
cliDriven: isCliDrivenHarness(input.startInput.agentHarness)
|
|
37478
|
+
};
|
|
37321
37479
|
const key = recoveryKey(request3.chatroomId, request3.role);
|
|
37322
37480
|
this.cancelRespawnStart(key, "superseded_by_new_restart");
|
|
37323
37481
|
this.respawnRestartSeq += 1;
|
|
@@ -37332,11 +37490,27 @@ class AgentWorkManager {
|
|
|
37332
37490
|
armRespawnStartRetry(key, pending) {
|
|
37333
37491
|
if (this.disposed)
|
|
37334
37492
|
return;
|
|
37335
|
-
const delay3 =
|
|
37493
|
+
const delay3 = computeBackoffDelay(pending.attempts, {
|
|
37494
|
+
baseMs: RESPAWN_RETRY_BASE_MS,
|
|
37495
|
+
maxMs: restartRetryMaxIntervalMs(pending.request.cliDriven),
|
|
37496
|
+
jitter: 0
|
|
37497
|
+
});
|
|
37336
37498
|
pending.timer = setTimeout(() => {
|
|
37337
37499
|
pending.timer = undefined;
|
|
37338
37500
|
this.retryRespawnStart(key, pending.restartId).catch((error51) => {
|
|
37339
|
-
|
|
37501
|
+
const current = this.respawnStartRetries.get(key);
|
|
37502
|
+
const message = error51 instanceof Error ? error51.stack ?? error51.message : String(error51);
|
|
37503
|
+
console.error(`[NativeDelivery:uncovered-restart-error] chatroom=${pending.request.chatroomId} role=${pending.request.role} error=${message}`);
|
|
37504
|
+
if (!current || current.restartId !== pending.restartId || this.disposed)
|
|
37505
|
+
return;
|
|
37506
|
+
current.attempts += 1;
|
|
37507
|
+
if (current.request.cliDriven && current.attempts >= MAX_RESPAWN_START_ATTEMPTS) {
|
|
37508
|
+
this.respawnStartRetries.delete(key);
|
|
37509
|
+
console.error(`[NativeDelivery:uncovered-restart-failed] attempts exhausted chatroom=${current.request.chatroomId} role=${current.request.role} attempts=${current.attempts} error=${message}`);
|
|
37510
|
+
return;
|
|
37511
|
+
}
|
|
37512
|
+
console.error(`[NativeDelivery:uncovered-restart-error-retry] chatroom=${current.request.chatroomId} role=${current.request.role} attempt=${current.attempts} error=${message}`);
|
|
37513
|
+
this.armRespawnStartRetry(key, current);
|
|
37340
37514
|
});
|
|
37341
37515
|
}, delay3);
|
|
37342
37516
|
}
|
|
@@ -37344,7 +37518,7 @@ class AgentWorkManager {
|
|
|
37344
37518
|
const pending = this.respawnStartRetries.get(key);
|
|
37345
37519
|
if (!pending || pending.restartId !== restartId || this.disposed)
|
|
37346
37520
|
return;
|
|
37347
|
-
const { chatroomId, role
|
|
37521
|
+
const { chatroomId, role } = pending.request;
|
|
37348
37522
|
if (this.deps.agentMgr.isStopRequested(chatroomId, role)) {
|
|
37349
37523
|
this.cancelRespawnStart(key, "stop_requested");
|
|
37350
37524
|
return;
|
|
@@ -37354,6 +37528,33 @@ class AgentWorkManager {
|
|
|
37354
37528
|
this.cancelRespawnStart(key, "agent_already_running");
|
|
37355
37529
|
return;
|
|
37356
37530
|
}
|
|
37531
|
+
const config3 = this.deps.configurationService.get(chatroomId, role);
|
|
37532
|
+
if (!config3) {
|
|
37533
|
+
if (this.deps.configurationService.state?.(chatroomId, role) === "syncing") {
|
|
37534
|
+
console.log(`[NativeDelivery:uncovered-restart-config-syncing] chatroom=${chatroomId} role=${role} attempts=${pending.attempts}`);
|
|
37535
|
+
this.armRespawnStartRetry(key, pending);
|
|
37536
|
+
return;
|
|
37537
|
+
}
|
|
37538
|
+
this.cancelRespawnStart(key, "config_removed");
|
|
37539
|
+
return;
|
|
37540
|
+
}
|
|
37541
|
+
const previous = pending.request;
|
|
37542
|
+
if (config3.agentHarness !== previous.startInput.agentHarness || config3.model !== previous.startInput.model || config3.workingDir !== previous.workingDir) {
|
|
37543
|
+
const startInput2 = {
|
|
37544
|
+
...previous.startInput,
|
|
37545
|
+
agentHarness: config3.agentHarness,
|
|
37546
|
+
model: config3.model,
|
|
37547
|
+
workingDir: config3.workingDir
|
|
37548
|
+
};
|
|
37549
|
+
pending.request = {
|
|
37550
|
+
...previous,
|
|
37551
|
+
workingDir: config3.workingDir,
|
|
37552
|
+
startInput: startInput2,
|
|
37553
|
+
cliDriven: isCliDrivenHarness(config3.agentHarness)
|
|
37554
|
+
};
|
|
37555
|
+
console.log(`[NativeDelivery:uncovered-restart-config-changed] chatroom=${chatroomId} role=${role} harness=${config3.agentHarness} model=${config3.model} workingDir=${config3.workingDir}`);
|
|
37556
|
+
}
|
|
37557
|
+
const { workingDir, startInput } = pending.request;
|
|
37357
37558
|
const outcome = await this.deps.runSerializedForAgent({ chatroomId, role, workingDir }, { timeoutMs: AGENT_LIFECYCLE_OPERATION_TIMEOUT_MS }, async (ops, context3) => {
|
|
37358
37559
|
if (this.respawnStartRetries.get(key)?.restartId !== restartId) {
|
|
37359
37560
|
return { kind: "skipped", reason: "superseded" };
|
|
@@ -37379,12 +37580,13 @@ class AgentWorkManager {
|
|
|
37379
37580
|
return;
|
|
37380
37581
|
}
|
|
37381
37582
|
current.attempts += 1;
|
|
37382
|
-
if (current.attempts >= MAX_RESPAWN_START_ATTEMPTS) {
|
|
37583
|
+
if (current.request.cliDriven && current.attempts >= MAX_RESPAWN_START_ATTEMPTS) {
|
|
37383
37584
|
this.respawnStartRetries.delete(key);
|
|
37384
37585
|
console.error(`[NativeDelivery:uncovered-restart-failed] attempts exhausted chatroom=${chatroomId} role=${role} attempts=${current.attempts} error=${outcome.error}`);
|
|
37385
37586
|
return;
|
|
37386
37587
|
}
|
|
37387
|
-
|
|
37588
|
+
const attemptLabel = current.request.cliDriven ? `${current.attempts} of ${MAX_RESPAWN_START_ATTEMPTS}` : `${current.attempts}`;
|
|
37589
|
+
console.error(`[NativeDelivery:uncovered-restart-failed] chatroom=${chatroomId} role=${role} attempt=${attemptLabel} error=${outcome.error}`);
|
|
37388
37590
|
this.armRespawnStartRetry(key, current);
|
|
37389
37591
|
}
|
|
37390
37592
|
cancelRespawnStart(key, reason) {
|
|
@@ -37436,6 +37638,12 @@ class AgentWorkManager {
|
|
|
37436
37638
|
}
|
|
37437
37639
|
dispose() {
|
|
37438
37640
|
this.disposed = true;
|
|
37641
|
+
for (const entry of this.turnFailureReleaseTimers.values()) {
|
|
37642
|
+
clearTimeout(entry.timer);
|
|
37643
|
+
this.deps.taskService.clearTurnFailureReleasePending(entry.key);
|
|
37644
|
+
}
|
|
37645
|
+
this.turnFailureReleaseTimers.clear();
|
|
37646
|
+
this.turnFailureAccountingFailures.clear();
|
|
37439
37647
|
for (const pending of this.respawnStartRetries.values()) {
|
|
37440
37648
|
if (pending.timer)
|
|
37441
37649
|
clearTimeout(pending.timer);
|
|
@@ -37500,6 +37708,20 @@ class AgentWorkManager {
|
|
|
37500
37708
|
if (notification.event.eventType === "task_deleted" /* TaskDeleted */) {
|
|
37501
37709
|
await this.cancelTaskWork(notification.event.chatroomId, notification.event.role, notification.event.taskId);
|
|
37502
37710
|
}
|
|
37711
|
+
if (notification.event.eventType === "task_assigned" /* TaskAssigned */) {
|
|
37712
|
+
this.cancelTurnFailureRelease({
|
|
37713
|
+
chatroomId: notification.event.chatroomId,
|
|
37714
|
+
role: notification.event.role,
|
|
37715
|
+
taskId: notification.event.taskId
|
|
37716
|
+
});
|
|
37717
|
+
}
|
|
37718
|
+
if (notification.event.task?.status === "completed") {
|
|
37719
|
+
this.cancelTurnFailureRelease({
|
|
37720
|
+
chatroomId: notification.event.chatroomId,
|
|
37721
|
+
role: notification.event.role,
|
|
37722
|
+
taskId: notification.event.taskId
|
|
37723
|
+
});
|
|
37724
|
+
}
|
|
37503
37725
|
if (notification.event.eventType === "task_deleted" /* TaskDeleted */) {
|
|
37504
37726
|
return { handledEventIds: [notification.event.eventId] };
|
|
37505
37727
|
}
|
|
@@ -37545,6 +37767,7 @@ class AgentWorkManager {
|
|
|
37545
37767
|
}
|
|
37546
37768
|
}
|
|
37547
37769
|
async cancelTaskWork(chatroomId, role, taskId) {
|
|
37770
|
+
this.cancelTurnFailureRelease({ chatroomId, role, taskId });
|
|
37548
37771
|
const activeTask = this.deps.agentTaskState.get({ chatroomId, role });
|
|
37549
37772
|
if (activeTask?.taskId !== taskId)
|
|
37550
37773
|
return;
|
|
@@ -37653,7 +37876,7 @@ class AgentWorkManager {
|
|
|
37653
37876
|
this.deps.agentTaskState.start(args2);
|
|
37654
37877
|
}
|
|
37655
37878
|
}
|
|
37656
|
-
var MAX_RESPAWN_START_ATTEMPTS = 3, MAX_UNCOVERED_NUDGE_SEND_ATTEMPTS = 3, UNCOVERED_NUDGE_RETRY_MS = 250, recoveryKey = (chatroomId, role) => `${chatroomId}:${role.toLowerCase()}`, sessionKeyFor = (pid, harnessSessionId) => `${pid}:${harnessSessionId ?? "none"}`;
|
|
37879
|
+
var MAX_RESPAWN_START_ATTEMPTS = 3, MAX_UNCOVERED_NUDGE_SEND_ATTEMPTS = 3, UNCOVERED_NUDGE_RETRY_MS = 250, MAX_TURN_FAILURE_ACCOUNTING_FAILURES = 5, RECOVERY_RETRY_BACKOFF, RESPAWN_RETRY_BASE_MS = 1000, recoveryKey = (chatroomId, role) => `${chatroomId}:${role.toLowerCase()}`, turnFailureReleaseKey = ({ chatroomId, role, taskId }) => `${chatroomId}:${role.toLowerCase()}:${taskId}`, sessionKeyFor = (pid, harnessSessionId) => `${pid}:${harnessSessionId ?? "none"}`;
|
|
37657
37880
|
var init_agent_work_manager = __esm(() => {
|
|
37658
37881
|
init_reliability();
|
|
37659
37882
|
init_agent();
|
|
@@ -37663,11 +37886,15 @@ var init_agent_work_manager = __esm(() => {
|
|
|
37663
37886
|
init_agent_slot();
|
|
37664
37887
|
init_assigned_task();
|
|
37665
37888
|
init_check_agent_slot();
|
|
37889
|
+
init_compute_backoff_delay();
|
|
37666
37890
|
init_native_task_injector_logic();
|
|
37667
37891
|
init_convex_native_task_delivery_gateway();
|
|
37668
37892
|
init_daemon_audit_port();
|
|
37669
37893
|
init_task_delivery_processor();
|
|
37670
37894
|
init_native_task_injector();
|
|
37895
|
+
init_is_cli_driven_harness();
|
|
37896
|
+
init_restart_retry_interval();
|
|
37897
|
+
RECOVERY_RETRY_BACKOFF = { baseMs: 1000, maxMs: 30000, jitter: 0 };
|
|
37671
37898
|
});
|
|
37672
37899
|
|
|
37673
37900
|
// src/daemon/services/agent-process-service/service/index.ts
|
|
@@ -39636,11 +39863,6 @@ class AgentProcessManager {
|
|
|
39636
39863
|
}
|
|
39637
39864
|
};
|
|
39638
39865
|
const dispositions = await Promise.all([...this.agentTurnEndedHandlers].map((handler) => handler(event)));
|
|
39639
|
-
const held = dispositions.find((disposition) => disposition?.kind === "hold-slot");
|
|
39640
|
-
if (held?.kind === "hold-slot") {
|
|
39641
|
-
console.warn(`[AgentProcessManager] Native turn slot held after ${completionStatus}: role=${opts.role} pid=${opts.pid} reason=${held.reason}`);
|
|
39642
|
-
return;
|
|
39643
|
-
}
|
|
39644
39866
|
if (completionStatus !== "completed" && !dispositions.some((disposition) => disposition?.kind === "release-slot")) {
|
|
39645
39867
|
console.warn(`[AgentProcessManager] Native turn slot held: no task-service disposition returned role=${opts.role} pid=${opts.pid} status=${completionStatus}`);
|
|
39646
39868
|
return;
|
|
@@ -40799,14 +41021,80 @@ function createTaskService(deps) {
|
|
|
40799
41021
|
let bootstrapRecoveryAttempts = 0;
|
|
40800
41022
|
let bootstrapRecoveryTimer;
|
|
40801
41023
|
let bootstrapRecoveryPromise;
|
|
40802
|
-
const TURN_FAILURE_RETRY_CAP =
|
|
41024
|
+
const TURN_FAILURE_RETRY_CAP = 8;
|
|
40803
41025
|
const turnFailureCounts = new Map;
|
|
40804
41026
|
const uncoveredLadder = new Map;
|
|
40805
41027
|
const redeliveryExhaustedKeys = new Set;
|
|
41028
|
+
const rearmCycleCounts = new Map;
|
|
41029
|
+
const rearmTimers = new Map;
|
|
41030
|
+
const pendingTurnFailureReleases = new Set;
|
|
40806
41031
|
const redeliveryKey = (chatroomId, role, taskId) => `${chatroomId}:${role.toLowerCase()}:${taskId}`;
|
|
40807
|
-
const
|
|
40808
|
-
const
|
|
40809
|
-
|
|
41032
|
+
const clearRearmTimer = (key) => {
|
|
41033
|
+
const timer = rearmTimers.get(key);
|
|
41034
|
+
if (timer === undefined)
|
|
41035
|
+
return;
|
|
41036
|
+
clearTimeout(timer);
|
|
41037
|
+
rearmTimers.delete(key);
|
|
41038
|
+
};
|
|
41039
|
+
const resetRearmBudget = (key) => {
|
|
41040
|
+
clearRearmTimer(key);
|
|
41041
|
+
rearmCycleCounts.delete(key);
|
|
41042
|
+
};
|
|
41043
|
+
const scheduleRedeliveryRearm = (args2) => {
|
|
41044
|
+
if (inboxStopped)
|
|
41045
|
+
return;
|
|
41046
|
+
const { key } = args2;
|
|
41047
|
+
const cycles = rearmCycleCounts.get(key) ?? 0;
|
|
41048
|
+
if (cycles >= MAX_REARM_CYCLES) {
|
|
41049
|
+
console.log(`[NativeDelivery:redelivery-rearm-exhausted] chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId} cycles=${MAX_REARM_CYCLES}`);
|
|
41050
|
+
return;
|
|
41051
|
+
}
|
|
41052
|
+
clearRearmTimer(key);
|
|
41053
|
+
const delay3 = REARM_DELAYS_MS[cycles];
|
|
41054
|
+
const timer = setTimeout(() => {
|
|
41055
|
+
rearmTimers.delete(key);
|
|
41056
|
+
runRedeliveryRearm({ ...args2, delay: delay3, cycle: cycles + 1 });
|
|
41057
|
+
}, delay3);
|
|
41058
|
+
rearmTimers.set(key, timer);
|
|
41059
|
+
};
|
|
41060
|
+
const runRedeliveryRearm = async (args2) => {
|
|
41061
|
+
const { chatroomId, role, taskId, key } = args2;
|
|
41062
|
+
const current = taskInboxState.getForRole(chatroomId, role, taskId);
|
|
41063
|
+
if (!current || current.status !== "in_progress" || !redeliveryExhaustedKeys.has(key)) {
|
|
41064
|
+
console.log(`[NativeDelivery:redelivery-rearm-skipped] reason=task_not_current chatroom=${chatroomId} role=${role} task=${taskId} status=${current?.status ?? "missing"}`);
|
|
41065
|
+
return;
|
|
41066
|
+
}
|
|
41067
|
+
rearmCycleCounts.set(key, args2.cycle);
|
|
41068
|
+
redeliveryExhaustedKeys.delete(key);
|
|
41069
|
+
turnFailureCounts.delete(key);
|
|
41070
|
+
try {
|
|
41071
|
+
await gateway.clearDeliveryFailure({
|
|
41072
|
+
sessionId: deps.sessionId,
|
|
41073
|
+
machineId: deps.machineId,
|
|
41074
|
+
taskId
|
|
41075
|
+
});
|
|
41076
|
+
} catch (error51) {
|
|
41077
|
+
console.warn(`[TaskService] redelivery-rearm clear failure failed chatroom=${chatroomId} role=${role} task=${taskId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
41078
|
+
}
|
|
41079
|
+
let released = false;
|
|
41080
|
+
try {
|
|
41081
|
+
const result3 = await releaseTask({ chatroomId, role, taskId });
|
|
41082
|
+
released = result3.released;
|
|
41083
|
+
if (!released) {
|
|
41084
|
+
console.warn(`[TaskService] redelivery-rearm release skipped chatroom=${chatroomId} role=${role} task=${taskId} cycle=${args2.cycle}`);
|
|
41085
|
+
}
|
|
41086
|
+
} catch (error51) {
|
|
41087
|
+
console.error(`[TaskService] redelivery-rearm release failed chatroom=${chatroomId} role=${role} task=${taskId} cycle=${args2.cycle}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
41088
|
+
}
|
|
41089
|
+
if (!released) {
|
|
41090
|
+
redeliveryExhaustedKeys.add(key);
|
|
41091
|
+
await recordExhaustedOnBackend({ chatroomId, role, taskId });
|
|
41092
|
+
scheduleRedeliveryRearm(args2);
|
|
41093
|
+
return;
|
|
41094
|
+
}
|
|
41095
|
+
console.log(`[NativeDelivery:redelivery-rearm] cycle=${args2.cycle}/${MAX_REARM_CYCLES} chatroom=${chatroomId} role=${role} task=${taskId} delayMs=${args2.delay}`);
|
|
41096
|
+
};
|
|
41097
|
+
const recordExhaustedOnBackend = async (args2) => {
|
|
40810
41098
|
try {
|
|
40811
41099
|
await gateway.recordDeliveryFailure({
|
|
40812
41100
|
sessionId: deps.sessionId,
|
|
@@ -40817,19 +41105,31 @@ function createTaskService(deps) {
|
|
|
40817
41105
|
} catch (error51) {
|
|
40818
41106
|
console.warn(`[TaskService] redelivery-exhausted failure record failed chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
40819
41107
|
}
|
|
41108
|
+
};
|
|
41109
|
+
const parkTask = async (args2, reason, attempts) => {
|
|
41110
|
+
const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
|
|
41111
|
+
redeliveryExhaustedKeys.add(key);
|
|
41112
|
+
await recordExhaustedOnBackend(args2);
|
|
40820
41113
|
const attemptsSuffix = attempts === undefined ? "" : ` attempts=${attempts}`;
|
|
40821
|
-
|
|
41114
|
+
const rearmCycles = rearmCycleCounts.get(key) ?? 0;
|
|
41115
|
+
const rearmDelay = reason === "provider_failure" && rearmCycles < MAX_REARM_CYCLES ? REARM_DELAYS_MS[rearmCycles] : undefined;
|
|
41116
|
+
console.log(`[NativeDelivery:redelivery-exhausted] chatroom=${args2.chatroomId} role=${args2.role} task=${args2.taskId}${attemptsSuffix} reason=${reason} rearm=${rearmDelay ?? "none"} — release/redelivery stopped; user.start or explicit recovery resets`);
|
|
41117
|
+
if (reason === "provider_failure") {
|
|
41118
|
+
scheduleRedeliveryRearm({ ...args2, key });
|
|
41119
|
+
}
|
|
40822
41120
|
};
|
|
40823
41121
|
const recordTurnFailure = async (args2) => {
|
|
40824
41122
|
const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
|
|
40825
|
-
|
|
40826
|
-
|
|
40827
|
-
|
|
41123
|
+
const recordedAttempts = turnFailureCounts.get(key) ?? 0;
|
|
41124
|
+
if (redeliveryExhaustedKeys.has(key)) {
|
|
41125
|
+
return { attempts: recordedAttempts, exceeded: true };
|
|
41126
|
+
}
|
|
41127
|
+
const attempts = recordedAttempts + 1;
|
|
40828
41128
|
turnFailureCounts.set(key, attempts);
|
|
40829
41129
|
if (attempts < TURN_FAILURE_RETRY_CAP)
|
|
40830
|
-
return { exceeded: false };
|
|
40831
|
-
await parkTask(args2, attempts);
|
|
40832
|
-
return { exceeded: true };
|
|
41130
|
+
return { attempts, exceeded: false };
|
|
41131
|
+
await parkTask(args2, "provider_failure", attempts);
|
|
41132
|
+
return { attempts, exceeded: true };
|
|
40833
41133
|
};
|
|
40834
41134
|
const decideUncoveredTurnEnd2 = async (args2) => {
|
|
40835
41135
|
const key = redeliveryKey(args2.chatroomId, args2.role, args2.taskId);
|
|
@@ -40843,7 +41143,7 @@ function createTaskService(deps) {
|
|
|
40843
41143
|
if (decision.action === "restart-session") {
|
|
40844
41144
|
uncoveredLadder.set(key, withSessionRestart(entry));
|
|
40845
41145
|
} else if (decision.action === "park") {
|
|
40846
|
-
await parkTask(args2);
|
|
41146
|
+
await parkTask(args2, "uncovered_turn");
|
|
40847
41147
|
}
|
|
40848
41148
|
return decision;
|
|
40849
41149
|
};
|
|
@@ -40862,6 +41162,7 @@ function createTaskService(deps) {
|
|
|
40862
41162
|
turnFailureCounts.delete(key);
|
|
40863
41163
|
uncoveredLadder.delete(key);
|
|
40864
41164
|
redeliveryExhaustedKeys.delete(key);
|
|
41165
|
+
resetRearmBudget(key);
|
|
40865
41166
|
};
|
|
40866
41167
|
const forgetStaleTask = ({
|
|
40867
41168
|
chatroomId,
|
|
@@ -40902,11 +41203,19 @@ function createTaskService(deps) {
|
|
|
40902
41203
|
if (key.startsWith(prefix))
|
|
40903
41204
|
redeliveryExhaustedKeys.delete(key);
|
|
40904
41205
|
}
|
|
41206
|
+
for (const key of [...rearmTimers.keys()]) {
|
|
41207
|
+
if (key.startsWith(prefix))
|
|
41208
|
+
resetRearmBudget(key);
|
|
41209
|
+
}
|
|
41210
|
+
for (const key of [...rearmCycleCounts.keys()]) {
|
|
41211
|
+
if (key.startsWith(prefix))
|
|
41212
|
+
rearmCycleCounts.delete(key);
|
|
41213
|
+
}
|
|
40905
41214
|
};
|
|
40906
41215
|
const scheduleBootstrapRecovery = () => {
|
|
40907
41216
|
if (inboxStopped || bootstrapRecoveryTimer || !bootstrapRecoveryPending)
|
|
40908
41217
|
return;
|
|
40909
|
-
const delay3 =
|
|
41218
|
+
const delay3 = computeBackoffDelay(++bootstrapRecoveryAttempts, BOOTSTRAP_RECOVERY_BACKOFF);
|
|
40910
41219
|
bootstrapRecoveryTimer = setTimeout(() => {
|
|
40911
41220
|
bootstrapRecoveryTimer = undefined;
|
|
40912
41221
|
if (!inboxStopped)
|
|
@@ -41123,6 +41432,11 @@ function createTaskService(deps) {
|
|
|
41123
41432
|
},
|
|
41124
41433
|
stopTaskInbox: () => {
|
|
41125
41434
|
inboxStopped = true;
|
|
41435
|
+
for (const timer of rearmTimers.values())
|
|
41436
|
+
clearTimeout(timer);
|
|
41437
|
+
rearmTimers.clear();
|
|
41438
|
+
rearmCycleCounts.clear();
|
|
41439
|
+
pendingTurnFailureReleases.clear();
|
|
41126
41440
|
if (bootstrapRecoveryTimer)
|
|
41127
41441
|
clearTimeout(bootstrapRecoveryTimer);
|
|
41128
41442
|
bootstrapRecoveryTimer = undefined;
|
|
@@ -41213,7 +41527,8 @@ function createTaskService(deps) {
|
|
|
41213
41527
|
}
|
|
41214
41528
|
if (mode === "automatic") {
|
|
41215
41529
|
const tracking = { chatroomId: task.chatroomId, role: taskRole, taskId: task.taskId };
|
|
41216
|
-
|
|
41530
|
+
const key = redeliveryKey(task.chatroomId, taskRole, task.taskId);
|
|
41531
|
+
if (isRedeliveryExhausted(tracking) || rearmTimers.has(key) || pendingTurnFailureReleases.has(key)) {
|
|
41217
41532
|
skipped += 1;
|
|
41218
41533
|
continue;
|
|
41219
41534
|
}
|
|
@@ -41299,6 +41614,7 @@ function createTaskService(deps) {
|
|
|
41299
41614
|
...expectedReason ? { expectedReason } : {}
|
|
41300
41615
|
}),
|
|
41301
41616
|
recordTurnFailure,
|
|
41617
|
+
parkTurnFailure: (args2) => parkTask(args2, "provider_failure"),
|
|
41302
41618
|
decideUncoveredTurnEnd: decideUncoveredTurnEnd2,
|
|
41303
41619
|
confirmNudgeSent,
|
|
41304
41620
|
resetTurnTracking,
|
|
@@ -41306,18 +41622,29 @@ function createTaskService(deps) {
|
|
|
41306
41622
|
clearRedeliveryTracking: ({ chatroomId, role }) => {
|
|
41307
41623
|
clearRoleRedeliveryTracking(chatroomId, role);
|
|
41308
41624
|
},
|
|
41625
|
+
markTurnFailureReleasePending: ({ chatroomId, role, taskId }) => {
|
|
41626
|
+
pendingTurnFailureReleases.add(redeliveryKey(chatroomId, role, taskId));
|
|
41627
|
+
},
|
|
41628
|
+
clearTurnFailureReleasePending: ({ chatroomId, role, taskId }) => {
|
|
41629
|
+
pendingTurnFailureReleases.delete(redeliveryKey(chatroomId, role, taskId));
|
|
41630
|
+
},
|
|
41309
41631
|
forgetStaleTask
|
|
41310
41632
|
};
|
|
41311
41633
|
return service4;
|
|
41312
41634
|
}
|
|
41635
|
+
var BOOTSTRAP_RECOVERY_BACKOFF, REARM_DELAYS_MS, MAX_REARM_CYCLES;
|
|
41313
41636
|
var init_task_service = __esm(() => {
|
|
41314
41637
|
init_chatroom_workspace_task_inbox();
|
|
41315
41638
|
init_pending_task_reconciliation_watcher();
|
|
41316
41639
|
init_task_inbox_acknowledgement_retry_watcher();
|
|
41317
41640
|
init_task_inbox_delivery_retry_watcher();
|
|
41318
41641
|
init_api3();
|
|
41642
|
+
init_compute_backoff_delay();
|
|
41319
41643
|
init_uncovered_turn_ladder();
|
|
41320
41644
|
init_convex_native_task_delivery_gateway();
|
|
41645
|
+
BOOTSTRAP_RECOVERY_BACKOFF = { baseMs: 1000, maxMs: 30000, jitter: 0 };
|
|
41646
|
+
REARM_DELAYS_MS = [15 * 60000, 30 * 60000, 60 * 60000];
|
|
41647
|
+
MAX_REARM_CYCLES = REARM_DELAYS_MS.length;
|
|
41321
41648
|
});
|
|
41322
41649
|
|
|
41323
41650
|
// src/daemon/services/task-service/infrastructure/repository/task-handoff-repository.ts
|
|
@@ -100031,7 +100358,7 @@ var init_team_presets = __esm(() => {
|
|
|
100031
100358
|
duo: {
|
|
100032
100359
|
structureId: TEAM_STRUCTURE_IDS.duo,
|
|
100033
100360
|
name: "Duo",
|
|
100034
|
-
description: "A planner and builder working as a pair, planner as coordinator, with optional ephemeral architect, researcher, triage, reviewer, uiux-engineer, and verifier roles",
|
|
100361
|
+
description: "A planner and builder working as a pair, planner as coordinator, with optional ephemeral architect, researcher, triage, reviewer, uiux-engineer, game-asset-designer, and verifier roles",
|
|
100035
100362
|
roles: getBuiltinRolesForTeam("duo"),
|
|
100036
100363
|
entryPoint: "planner"
|
|
100037
100364
|
},
|
|
@@ -100810,6 +101137,166 @@ var init_builder = __esm(() => {
|
|
|
100810
101137
|
};
|
|
100811
101138
|
});
|
|
100812
101139
|
|
|
101140
|
+
// ../../services/backend/prompts/teams/game-asset-designer-evidence.ts
|
|
101141
|
+
var GAME_ASSET_EVIDENCE_LABELS, GAME_ASSET_EVIDENCE_LABEL_LIST, GAME_ASSET_NEVER_CLAIM_RULE = "Never claim a polycount, texel density, loudness, color space, or visual-quality result without `measured` or `inspected` evidence. Numeric results (polycount, texel density, resolution, loudness, true peak, sample rate, bit depth) and color-space slot checks require `measured`; `inspected` covers only visual or audible judgments. A budget or target you write into the spec is a requirement and is labelled `specified`, not a result.", GAME_ASSET_RESULT_RULE = "A checklist item is `pass` or `fail` only with `measured` or `inspected` evidence. Otherwise record it as `not checked` and keep its label (`specified` or `unverified`).", GAME_ASSET_EVIDENCE_DEFINITIONS = `- \`measured\` — a tool or script you ran on the actual file produced the number or read the metadata field; cite the command and its output value (this includes header or JSON values read by command).
|
|
101142
|
+
- \`inspected\` — you opened the actual file or a render and made a qualitative observation (silhouette, seam, palette, style match); describe what you saw and give no numbers.
|
|
101143
|
+
- \`specified\` — a target or requirement from the brief or your own spec; no file was checked. A budget you write into a spec is \`specified\`.
|
|
101144
|
+
- \`unverified\` — a result that should have been checked but could not be (no file access, no tool, unreadable file), or a value someone else stated that you did not check; say why and name the source.`;
|
|
101145
|
+
var init_game_asset_designer_evidence = __esm(() => {
|
|
101146
|
+
GAME_ASSET_EVIDENCE_LABELS = ["measured", "inspected", "specified", "unverified"];
|
|
101147
|
+
GAME_ASSET_EVIDENCE_LABEL_LIST = GAME_ASSET_EVIDENCE_LABELS.map((label) => `\`${label}\``).join(", ");
|
|
101148
|
+
});
|
|
101149
|
+
|
|
101150
|
+
// ../../services/backend/prompts/teams/game-asset-designer-handoff-templates.ts
|
|
101151
|
+
function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
101152
|
+
const entryPoint = query.fromRole;
|
|
101153
|
+
return `Instructions (not part of the message): Pass the asset request as a brief the designer can act on without guessing. Provide every field below; write \`Not specified.\` for a field you cannot fill and the designer will ask or apply a stated default. Give each asset a stable ID (\`A01\`, \`A02\`…). Use \`spec only\` only when you want a specification with no files; the default is \`spec and generate\`.
|
|
101154
|
+
|
|
101155
|
+
## Asset request
|
|
101156
|
+
<one to three sentences: what the assets are for and the user-visible goal>
|
|
101157
|
+
|
|
101158
|
+
## Target
|
|
101159
|
+
<engine and version; genre and camera; platform tier (mobile, PC, console, VR); render pipeline if known>
|
|
101160
|
+
|
|
101161
|
+
## Assets requested
|
|
101162
|
+
<numbered list with stable IDs \`A01\`, \`A02\`…; each item: asset name, category (3D model, PBR textures, 2D sprite/UI, audio, other), purpose, quantity or variants>
|
|
101163
|
+
|
|
101164
|
+
## Budgets and conventions
|
|
101165
|
+
<triangle budget per LOD, texel density or pixel grid, texture resolution and atlas limits, loudness target, naming convention, folder layout; or \`Not specified.\`>
|
|
101166
|
+
|
|
101167
|
+
## Style reference
|
|
101168
|
+
<repository paths, URLs, or a written style sheet; or \`Not specified.\`>
|
|
101169
|
+
|
|
101170
|
+
## Spec consumer
|
|
101171
|
+
<who produces or integrates these assets from the spec: \`builder\`, \`uiux-engineer\`, an external artist, or \`user\`>
|
|
101172
|
+
|
|
101173
|
+
## Existing assets
|
|
101174
|
+
<repository paths the designer must reuse or must not overwrite; or \`Not specified.\`>
|
|
101175
|
+
|
|
101176
|
+
## Deliverable mode
|
|
101177
|
+
<\`spec and generate\` (default when you name an output location and allowed tools) or \`spec only\`>
|
|
101178
|
+
|
|
101179
|
+
## Allowed tools
|
|
101180
|
+
<the generators the designer may run, or \`Any tool available in the designer's shell.\`; say which>
|
|
101181
|
+
|
|
101182
|
+
## Allowed edit paths
|
|
101183
|
+
<repository-relative paths the designer may edit; \`None.\` for none>
|
|
101184
|
+
|
|
101185
|
+
## Commit
|
|
101186
|
+
<\`None.\` or the commit message and the paths to commit>
|
|
101187
|
+
|
|
101188
|
+
## Output location
|
|
101189
|
+
<the exact repository directory the designer may write to; required for \`spec and generate\`; \`None.\` for \`spec only\`>
|
|
101190
|
+
|
|
101191
|
+
## Acceptance criteria
|
|
101192
|
+
<what must be true for the planner to accept the delivery>
|
|
101193
|
+
|
|
101194
|
+
Instructions after the body (not part of the message): run the normal handoff command to \`game-asset-designer\` with the complete body above, then stop. Use this template only under the conditions below; the body above ends at \`## Acceptance criteria\`.
|
|
101195
|
+
|
|
101196
|
+
### When to use the game asset designer
|
|
101197
|
+
- the request creates or changes game art or audio assets (models, textures, sprites, UI art, sound effects);
|
|
101198
|
+
- assets must meet engine import conventions or budgets before a builder integrates them;
|
|
101199
|
+
- the user asks for an asset spec, style sheet, or asset audit.
|
|
101200
|
+
|
|
101201
|
+
Skip it for code-only work, for a trivial asset rename, and for non-game UI icons or illustrations (use \`uiux-engineer\`).
|
|
101202
|
+
|
|
101203
|
+
\`\`\`bash
|
|
101204
|
+
${handoffCommand({
|
|
101205
|
+
chatroomId: "<chatroom-id>",
|
|
101206
|
+
role: entryPoint,
|
|
101207
|
+
nextRole: "game-asset-designer",
|
|
101208
|
+
cliEnvPrefix: ""
|
|
101209
|
+
})}
|
|
101210
|
+
\`\`\``;
|
|
101211
|
+
}
|
|
101212
|
+
function getGameAssetDesignerToEntryPointHandoffTemplate(query) {
|
|
101213
|
+
const entryPoint = query.toRole;
|
|
101214
|
+
return `Instructions (not part of the message): Return a complete asset delivery report for the request the planner named. State only what you established. Label every checked property with one evidence label: ${GAME_ASSET_EVIDENCE_LABEL_LIST}. Definitions: ${GAME_ASSET_EVIDENCE_DEFINITIONS} ${GAME_ASSET_NEVER_CLAIM_RULE} ${GAME_ASSET_RESULT_RULE} Write only under the named output location and the named edit paths. In \`spec only\` you write no files. Use the exact sections below, in this order. If you are blocked or need a clarification, send the same sections with Summary starting \`Blocked:\` and every section you could not reach written as \`Not started.\`
|
|
101215
|
+
|
|
101216
|
+
## Summary
|
|
101217
|
+
<one to three sentences: what was delivered, whether it is spec only or includes files, and the count of \`fail\` and \`not checked\` items>
|
|
101218
|
+
|
|
101219
|
+
## Target and assumptions
|
|
101220
|
+
<each assumption you made and why; each deviation from the planner's target or budgets; or \`None.\`>
|
|
101221
|
+
|
|
101222
|
+
## Asset manifest
|
|
101223
|
+
<one entry per asset, keyed by the planner's stable ID (\`A01\`…): name, category, file format, path (or \`Specified only.\`), dimensions/units/scale, pivot, naming, LOD list with triangle budgets, texture set with resolution, color space and channel packing, atlas/pixel grid/frame data, audio format and loudness target, import settings, dependencies and variants; for each budget, the acceptance check (exact command and expected value); for each generated file, the generator (tool + version + command or seed)>
|
|
101224
|
+
|
|
101225
|
+
## Quality checklist results
|
|
101226
|
+
<per asset: each applicable checklist item as \`pass\`, \`fail\`, \`n/a\`, or \`not checked\`, with its evidence label and, for \`measured\`, the command and value; write exactly \`None failed.\` after the list if no item failed>
|
|
101227
|
+
|
|
101228
|
+
## Acceptance criteria results
|
|
101229
|
+
<each planner acceptance criterion as \`met\`, \`not met\`, or \`not checked\`, with its evidence label>
|
|
101230
|
+
|
|
101231
|
+
## Style consistency
|
|
101232
|
+
<how the set matches the style reference; deviations; what was compared and how>
|
|
101233
|
+
|
|
101234
|
+
## Created and edited paths
|
|
101235
|
+
<repository-relative paths and a one-line description each; write exactly \`None.\` for spec only>
|
|
101236
|
+
|
|
101237
|
+
## Commits
|
|
101238
|
+
<write exactly \`None.\` if none; otherwise one line per commit: \`<sha> <message>\`>
|
|
101239
|
+
|
|
101240
|
+
## Sources
|
|
101241
|
+
<URLs for engine or platform rules relied on, with version or date checked>
|
|
101242
|
+
|
|
101243
|
+
## Open questions
|
|
101244
|
+
<each clarification needed from the planner with a recommended default; write exactly \`None.\` if none>
|
|
101245
|
+
|
|
101246
|
+
## Not verified
|
|
101247
|
+
<every property you could not measure or inspect; write exactly \`None.\` if none>
|
|
101248
|
+
|
|
101249
|
+
## Recommended next step
|
|
101250
|
+
<exactly one target: \`game-asset-designer\` (re-run with the listed asset IDs), \`builder\` (integrate, or apply the listed import settings), \`uiux-engineer\` (place the listed UI art in a screen design), or \`planner\` (decision needed on an open question); then the affected asset IDs>
|
|
101251
|
+
|
|
101252
|
+
Instructions after the body (not part of the message): run the normal handoff command to \`${entryPoint}\` with the complete body above, then stop.
|
|
101253
|
+
|
|
101254
|
+
\`\`\`bash
|
|
101255
|
+
${handoffCommand({
|
|
101256
|
+
chatroomId: "<chatroom-id>",
|
|
101257
|
+
role: "game-asset-designer",
|
|
101258
|
+
nextRole: entryPoint,
|
|
101259
|
+
cliEnvPrefix: ""
|
|
101260
|
+
})}
|
|
101261
|
+
\`\`\``;
|
|
101262
|
+
}
|
|
101263
|
+
var init_game_asset_designer_handoff_templates = __esm(() => {
|
|
101264
|
+
init_game_asset_designer_evidence();
|
|
101265
|
+
init_command2();
|
|
101266
|
+
});
|
|
101267
|
+
|
|
101268
|
+
// ../../services/backend/prompts/teams/duo/handoff-templates/game-asset-designer-planner-code.ts
|
|
101269
|
+
function getGameAssetDesignerPlannerCodeTemplate(query) {
|
|
101270
|
+
return getGameAssetDesignerToEntryPointHandoffTemplate(query);
|
|
101271
|
+
}
|
|
101272
|
+
var init_game_asset_designer_planner_code = __esm(() => {
|
|
101273
|
+
init_game_asset_designer_handoff_templates();
|
|
101274
|
+
});
|
|
101275
|
+
|
|
101276
|
+
// ../../services/backend/prompts/teams/duo/handoff-templates/game-asset-designer-planner-chat.ts
|
|
101277
|
+
var init_game_asset_designer_planner_chat = __esm(() => {
|
|
101278
|
+
init_game_asset_designer_planner_code();
|
|
101279
|
+
});
|
|
101280
|
+
|
|
101281
|
+
// ../../services/backend/prompts/teams/duo/handoff-templates/game-asset-designer.ts
|
|
101282
|
+
var duoGameAssetDesignerHandoffContract;
|
|
101283
|
+
var init_game_asset_designer = __esm(() => {
|
|
101284
|
+
init_game_asset_designer_planner_chat();
|
|
101285
|
+
init_game_asset_designer_planner_code();
|
|
101286
|
+
init_contracts();
|
|
101287
|
+
duoGameAssetDesignerHandoffContract = {
|
|
101288
|
+
role: "game-asset-designer",
|
|
101289
|
+
receivesFrom: ["planner"],
|
|
101290
|
+
returnsTo: ["planner"],
|
|
101291
|
+
outboundTemplates: {
|
|
101292
|
+
planner: handoffTemplateByMode({
|
|
101293
|
+
chat: getGameAssetDesignerPlannerCodeTemplate,
|
|
101294
|
+
code: getGameAssetDesignerPlannerCodeTemplate
|
|
101295
|
+
})
|
|
101296
|
+
}
|
|
101297
|
+
};
|
|
101298
|
+
});
|
|
101299
|
+
|
|
100813
101300
|
// ../../services/backend/prompts/teams/duo/handoff-templates/planner-architect-code.ts
|
|
100814
101301
|
function getPlannerArchitectCodeTemplate(query) {
|
|
100815
101302
|
return getEntryPointToArchitectHandoffTemplate(query);
|
|
@@ -100911,6 +101398,19 @@ var init_planner_builder_chat = __esm(() => {
|
|
|
100911
101398
|
init_planner_builder_code();
|
|
100912
101399
|
});
|
|
100913
101400
|
|
|
101401
|
+
// ../../services/backend/prompts/teams/duo/handoff-templates/planner-game-asset-designer-code.ts
|
|
101402
|
+
function getPlannerGameAssetDesignerCodeTemplate(query) {
|
|
101403
|
+
return getEntryPointToGameAssetDesignerHandoffTemplate(query);
|
|
101404
|
+
}
|
|
101405
|
+
var init_planner_game_asset_designer_code = __esm(() => {
|
|
101406
|
+
init_game_asset_designer_handoff_templates();
|
|
101407
|
+
});
|
|
101408
|
+
|
|
101409
|
+
// ../../services/backend/prompts/teams/duo/handoff-templates/planner-game-asset-designer-chat.ts
|
|
101410
|
+
var init_planner_game_asset_designer_chat = __esm(() => {
|
|
101411
|
+
init_planner_game_asset_designer_code();
|
|
101412
|
+
});
|
|
101413
|
+
|
|
100914
101414
|
// ../../services/backend/prompts/teams/researcher-handoff-templates.ts
|
|
100915
101415
|
function getEntryPointToResearcherHandoffTemplate(query) {
|
|
100916
101416
|
const entryPoint = query.fromRole;
|
|
@@ -101562,6 +102062,8 @@ var init_planner = __esm(() => {
|
|
|
101562
102062
|
init_planner_architect_code();
|
|
101563
102063
|
init_planner_builder_chat();
|
|
101564
102064
|
init_planner_builder_code();
|
|
102065
|
+
init_planner_game_asset_designer_chat();
|
|
102066
|
+
init_planner_game_asset_designer_code();
|
|
101565
102067
|
init_planner_researcher_chat();
|
|
101566
102068
|
init_planner_researcher_code();
|
|
101567
102069
|
init_planner_reviewer_chat();
|
|
@@ -101585,6 +102087,7 @@ var init_planner = __esm(() => {
|
|
|
101585
102087
|
"triage",
|
|
101586
102088
|
"researcher",
|
|
101587
102089
|
"reviewer",
|
|
102090
|
+
"game-asset-designer",
|
|
101588
102091
|
"verifier"
|
|
101589
102092
|
],
|
|
101590
102093
|
returnsTo: [
|
|
@@ -101595,6 +102098,7 @@ var init_planner = __esm(() => {
|
|
|
101595
102098
|
"triage",
|
|
101596
102099
|
"researcher",
|
|
101597
102100
|
"reviewer",
|
|
102101
|
+
"game-asset-designer",
|
|
101598
102102
|
"verifier"
|
|
101599
102103
|
],
|
|
101600
102104
|
outboundTemplates: {
|
|
@@ -101622,6 +102126,10 @@ var init_planner = __esm(() => {
|
|
|
101622
102126
|
chat: getPlannerReviewerCodeTemplate,
|
|
101623
102127
|
code: getPlannerReviewerCodeTemplate
|
|
101624
102128
|
}),
|
|
102129
|
+
"game-asset-designer": handoffTemplateByMode({
|
|
102130
|
+
chat: getPlannerGameAssetDesignerCodeTemplate,
|
|
102131
|
+
code: getPlannerGameAssetDesignerCodeTemplate
|
|
102132
|
+
}),
|
|
101625
102133
|
verifier: handoffTemplateByMode({
|
|
101626
102134
|
chat: getPlannerVerifierCodeTemplate,
|
|
101627
102135
|
code: getPlannerVerifierCodeTemplate
|
|
@@ -101802,6 +102310,7 @@ var DUO_ROLE_HANDOFF_CATALOG, DUO_ROLE_HANDOFF_CONTRACTS, validatedDuoRoleHandof
|
|
|
101802
102310
|
var init_handoff_templates = __esm(() => {
|
|
101803
102311
|
init_architect();
|
|
101804
102312
|
init_builder();
|
|
102313
|
+
init_game_asset_designer();
|
|
101805
102314
|
init_planner();
|
|
101806
102315
|
init_researcher();
|
|
101807
102316
|
init_reviewer();
|
|
@@ -101816,6 +102325,7 @@ var init_handoff_templates = __esm(() => {
|
|
|
101816
102325
|
researcher: duoResearcherHandoffContract,
|
|
101817
102326
|
reviewer: duoReviewerHandoffContract,
|
|
101818
102327
|
"uiux-engineer": duoUiuxEngineerHandoffContract,
|
|
102328
|
+
"game-asset-designer": duoGameAssetDesignerHandoffContract,
|
|
101819
102329
|
triage: duoTriageHandoffContract,
|
|
101820
102330
|
verifier: duoVerifierHandoffContract
|
|
101821
102331
|
};
|
|
@@ -102628,6 +103138,79 @@ var init_builder2 = __esm(() => {
|
|
|
102628
103138
|
init_sections();
|
|
102629
103139
|
});
|
|
102630
103140
|
|
|
103141
|
+
// ../../services/backend/prompts/cli/roles/game-asset-designer.ts
|
|
103142
|
+
var GAME_ASSET_DESIGNER_GUIDANCE;
|
|
103143
|
+
var init_game_asset_designer2 = __esm(() => {
|
|
103144
|
+
init_game_asset_designer_evidence();
|
|
103145
|
+
init_sections();
|
|
103146
|
+
GAME_ASSET_DESIGNER_GUIDANCE = `## Game Asset Designer Operating Model
|
|
103147
|
+
|
|
103148
|
+
You are the game asset designer responsible for specifying production-quality game assets, and for producing them only when the planner's request and your available tools allow it. You own the asset specification, generate the asset files yourself, and edit only the asset-integration paths the brief allows (engine import or config files and the code that references the asset files). You do not implement application logic outside those paths. In \`spec only\` mode you write no files. You do not act as the builder or planner.
|
|
103149
|
+
|
|
103150
|
+
${"{SESSION_CONTINUITY}"}
|
|
103151
|
+
|
|
103152
|
+
${getInterRoleHandoffRule()}
|
|
103153
|
+
|
|
103154
|
+
1. Recover the authoritative request from the injected task and the \`<handoff-templates>\` brief. Recover relevant chatroom history if the brief refers to earlier assets. Production quality is relative to a target, so confirm these inputs: target engine and version, genre and camera, platform tier (mobile, PC, console, VR), art style reference, asset categories, and budgets (triangles per LOD, texel density, texture resolution, atlas size, loudness). If an input is missing and the repository does not answer it, ask \`{ENTRY_POINT_ROLE}\` through the handoff with a clarification request. Do not guess a target engine or platform. For a non-blocking gap, apply the stated default and list it under assumptions. If you are blocked, send the same report to \`{ENTRY_POINT_ROLE}\` with Summary starting \`Blocked:\` and every section you could not reach written as \`Not started.\`
|
|
103155
|
+
2. Inspect the repository for existing assets, naming and folder conventions, engine and import configuration, and style references. Match what exists unless the planner says otherwise. Cite paths.
|
|
103156
|
+
3. Classify each asset as 3D model, PBR material or texture set, 2D sprite or UI art, audio, or other, and write one manifest entry per asset using the fields in the handoff template.
|
|
103157
|
+
4. Specify against the target engine's documented import conventions. Prefer official engine documentation, the Khronos glTF 2.0 specification, and platform audio guidance. Cite the source for any rule you rely on. Treat community figures (albedo ranges, texel density tiers, polycount tiers) as starting points that the request's declared budgets override, and say so.
|
|
103158
|
+
5. Apply the quality checklist below to every asset before handoff.
|
|
103159
|
+
6. Say exactly how each claim was established, using only these evidence labels (${GAME_ASSET_EVIDENCE_LABEL_LIST}):
|
|
103160
|
+
${GAME_ASSET_EVIDENCE_DEFINITIONS}
|
|
103161
|
+
${GAME_ASSET_NEVER_CLAIM_RULE} ${GAME_ASSET_RESULT_RULE}
|
|
103162
|
+
7. Generate the asset files yourself when the brief names an output location and allowed tools (default mode \`spec and generate\`). Use only the tools the brief allows, and use tools already available in your shell. Record the tool, its version, and the command or seed for every generated file in the manifest. Write only under the named output location. Edit files only at the paths named under \`Allowed edit paths\`. Never overwrite or delete an existing file unless the brief names it. Commit only when the brief names the paths and the commit message; otherwise leave changes uncommitted and say so. If \`Deliverable mode\` is \`spec only\`, write no files. If the output location is missing or ambiguous, deliver \`spec only\`, state that in Summary, and list the gap under Open questions. Describe generated output as production-ready only when every checklist item for it passes with \`measured\` or \`inspected\` evidence. Run each numeric check against the generated file itself, not against your own generator's settings.
|
|
103163
|
+
|
|
103164
|
+
In \`spec only\` the deliverable is a \`Specified only.\` manifest. Each entry has: a stable ID (\`A01\`, \`A02\`…); category; format; units, scale and pivot; name and path; budgets per property; import settings; dependencies and variants; and, for each budget, an acceptance check that gives the exact measuring command and expected value (for example \`ffmpeg -i <file> -af ebur128=peak=true -f null -\`, \`gltf-transform inspect <file>\`, \`ffprobe <file>\`).
|
|
103165
|
+
8. Follow the injected \`<handoff-templates>\` asset delivery report exactly, in its order.
|
|
103166
|
+
9. Hand the report to \`{ENTRY_POINT_ROLE}\` with the normal command, then stop:
|
|
103167
|
+
|
|
103168
|
+
\`\`\`bash
|
|
103169
|
+
chatroom handoff --chatroom-id="<chatroom-id>" --role="game-asset-designer" --next-role="{ENTRY_POINT_ROLE}"
|
|
103170
|
+
\`\`\`
|
|
103171
|
+
|
|
103172
|
+
### Quality checklist (apply to each asset; mark each item pass, fail, or not applicable, with its evidence label)
|
|
103173
|
+
|
|
103174
|
+
**All assets**
|
|
103175
|
+
- Name follows the project convention (type prefix, descriptive name, variant, LOD suffix); ASCII, no spaces; folder matches the asset category.
|
|
103176
|
+
- File format is the one the target engine imports (glTF 2.0/GLB, FBX, PNG, WAV/OGG as named in the request); no unused data (hidden meshes, unused materials, history).
|
|
103177
|
+
- Style matches the named style reference: silhouette, value range, palette, level of detail, and texel density or pixel grid.
|
|
103178
|
+
- Budgets in the request are met; any exceedance is called out with the measured value.
|
|
103179
|
+
|
|
103180
|
+
**3D models**
|
|
103181
|
+
- Units and axes match the target (glTF: meters, +Y up, front +Z); scale is applied (identity scale) and a real-world size is stated.
|
|
103182
|
+
- Pivot/origin follows the declared policy; LODs share the base pivot and alignment.
|
|
103183
|
+
- Clean topology: no n-gons or non-manifold edges unless declared, no flipped normals or inverted winding (glTF: counter-clockwise front faces), consistent normals and tangents (MikkTSpace).
|
|
103184
|
+
- Triangle count per LOD is within budget; LOD naming matches the engine (Unity \`_LOD0\`, \`_LOD1\`; Unreal LODs share the base pivot and alignment and are imported per the FBX import options; Godot generates LODs at import, so declare the Generate LODs setting instead of a suffix).
|
|
103185
|
+
- UVs: non-overlapping unique layout where textures are unique, island padding, consistent texel density, no stretched islands; a second UV set if the engine needs baked lighting.
|
|
103186
|
+
- Collision and sockets use the engine's naming (Unreal \`UBX_\`/\`UCP_\`/\`USP_\`/\`UCX_\` + mesh name and \`SOCKET_\` + mesh name; Godot \`-col\`/\`-convcolonly\` suffixes).
|
|
103187
|
+
- Skinned meshes: bone count within budget, influences per vertex within the declared budget (commonly 4, marked \`unverified\`; glTF allows more via \`JOINTS_1\`), no leftover IK helpers, a named rest pose.
|
|
103188
|
+
|
|
103189
|
+
**PBR materials and textures**
|
|
103190
|
+
- Metallic-roughness workflow with documented channel packing (glTF: roughness in G, metallic in B, occlusion in R).
|
|
103191
|
+
- Color space: base color and emissive sRGB; normal, roughness, metallic, occlusion, masks linear. State the normal map Y convention. In glTF, color space is set by the material slot that references the image, not by embedded PNG chunks. Verify the slot for each map: \`baseColorTexture\` and \`emissiveTexture\` are sRGB; \`metallicRoughnessTexture\`, \`occlusionTexture\` and \`normalTexture\` are linear.
|
|
103192
|
+
- Base color values stay in a physically plausible range (community starting range roughly 30–240 sRGB per channel for non-metals; the request's budget overrides it); metallic is near binary.
|
|
103193
|
+
- Resolution matches the texel-density budget and surface size; dimensions meet the engine/platform rule (power of two where the engine needs mipmaps or streaming, for example Unreal; confirm for the target engine version); compression format named.
|
|
103194
|
+
- Tiling textures wrap without a visible seam; no baked-in lighting or text in base color unless declared.
|
|
103195
|
+
- Maps in one set share texel density, value range, and naming (\`T_Name_BaseColor\`, \`_Normal\`, \`_ORM\`).
|
|
103196
|
+
|
|
103197
|
+
**2D sprites and UI art**
|
|
103198
|
+
- Fixed grid and pixels-per-unit declared; sprite sizes are multiples of the grid; no sub-pixel offsets.
|
|
103199
|
+
- Palette size and colors declared; transparent background with clean alpha edges.
|
|
103200
|
+
- Animation: uniform frame size per clip, frame count, frame rate, loop flag, and pivot per clip.
|
|
103201
|
+
- Atlas: padding of at least the engine default (Unity default 4 px), atlas size within the platform max, filter mode Point for pixel art and no mip maps.
|
|
103202
|
+
- Export PNG (lossless with alpha); name states size and frame count where relevant.
|
|
103203
|
+
|
|
103204
|
+
**Audio**
|
|
103205
|
+
- Loudness measured against the platform target stated in the request (for example console -24 LUFS, mobile -18 LUFS, true peak at or below -1 dBTP); no clipping. Measure with a tool: \`ffmpeg -i <file> -af ebur128=peak=true -f null -\` for integrated loudness and true peak; \`ffprobe <file>\` for sample rate, bit depth and channels; for glTF, \`gltf-transform inspect <file>\` (quote the command's actual output). Cite the platform source. If the request does not state the target, mark the loudness target \`unverified\` and name the source. Mobile targets vary (-18 vs -16 LUFS).
|
|
103206
|
+
- Sample rate, bit depth, channel count, loop points, and format stated; short noisy SFX use PCM or ADPCM, long audio uses compressed or streamed formats, per the engine's guidance.
|
|
103207
|
+
|
|
103208
|
+
### Scope boundary
|
|
103209
|
+
You may specify, name, organize, generate, validate, and integrate assets within the paths the brief allows. You must not claim fidelity you did not establish. If you cannot open or measure a file, write \`unverified\`. Do not present alternative art directions beyond what the request needs; recommend one.
|
|
103210
|
+
|
|
103211
|
+
The handoff must contain every required report section, the manifest with one entry per asset, checklist results with evidence labels, assumptions, created and edited paths (or \`None.\`), and open questions for \`{ENTRY_POINT_ROLE}\`.`;
|
|
103212
|
+
});
|
|
103213
|
+
|
|
102631
103214
|
// ../../services/backend/prompts/cli/roles/planner.ts
|
|
102632
103215
|
var init_planner2 = __esm(() => {
|
|
102633
103216
|
init_env();
|
|
@@ -102783,6 +103366,7 @@ var init_fromContext = __esm(() => {
|
|
|
102783
103366
|
var init_fromContext2 = __esm(() => {
|
|
102784
103367
|
init_architect3();
|
|
102785
103368
|
init_builder2();
|
|
103369
|
+
init_game_asset_designer2();
|
|
102786
103370
|
init_planner2();
|
|
102787
103371
|
init_researcher2();
|
|
102788
103372
|
init_reviewer2();
|
|
@@ -102892,6 +103476,19 @@ var init_templates = __esm(() => {
|
|
|
102892
103476
|
],
|
|
102893
103477
|
defaultHandoffTarget: "planner"
|
|
102894
103478
|
},
|
|
103479
|
+
"game-asset-designer": {
|
|
103480
|
+
role: "game-asset-designer",
|
|
103481
|
+
title: "Game Asset Designer",
|
|
103482
|
+
description: "You are the game asset designer responsible for specifying production-quality game assets (and producing them when the planner allows); you do not implement application code.",
|
|
103483
|
+
responsibilities: [
|
|
103484
|
+
"Recover the target, asset request, style reference, and budgets from the planner brief",
|
|
103485
|
+
"Inspect the repository for existing assets, naming and folder conventions, and engine import configuration",
|
|
103486
|
+
"Write one manifest entry per asset covering format, units, pivot, LODs, textures, atlases, and audio",
|
|
103487
|
+
"Self-check each asset against the quality checklist and label every claim with its evidence label",
|
|
103488
|
+
"Hand one asset delivery report to the planner, separating assumptions and unverified properties from established results"
|
|
103489
|
+
],
|
|
103490
|
+
defaultHandoffTarget: "planner"
|
|
103491
|
+
},
|
|
102895
103492
|
verifier: {
|
|
102896
103493
|
role: "verifier",
|
|
102897
103494
|
title: "Verifier",
|
|
@@ -139302,4 +139899,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
139302
139899
|
});
|
|
139303
139900
|
program2.parse();
|
|
139304
139901
|
|
|
139305
|
-
//# debugId=
|
|
139902
|
+
//# debugId=71E0747DD56ECDB664756E2164756E21
|