opencode-supertask 0.1.35 → 0.1.36

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/cli/index.js CHANGED
@@ -20955,6 +20955,7 @@ var init_management_lock = __esm({
20955
20955
  // src/daemon/pm2.ts
20956
20956
  var pm2_exports = {};
20957
20957
  __export(pm2_exports, {
20958
+ diagnoseOpenCodeRuntime: () => diagnoseOpenCodeRuntime,
20958
20959
  ensureGateway: () => ensureGateway,
20959
20960
  ensurePm2LogRotation: () => ensurePm2LogRotation,
20960
20961
  getGatewayDiagnostic: () => getGatewayDiagnostic,
@@ -21074,7 +21075,35 @@ function scopesMatch(left, right) {
21074
21075
  function currentScope() {
21075
21076
  return runtimeScope({ cwd: process.cwd(), env: process.env });
21076
21077
  }
21077
- function getGatewayDiagnostic() {
21078
+ function diagnoseOpenCodeRuntime(runtime = {
21079
+ cwd: process.cwd(),
21080
+ env: process.env
21081
+ }) {
21082
+ const executable = runtimeScope(runtime).opencodePath;
21083
+ const result = spawnSync2(executable, ["--version"], {
21084
+ cwd: runtime.cwd,
21085
+ env: runtime.env,
21086
+ encoding: "utf8",
21087
+ timeout: 1e4,
21088
+ killSignal: "SIGKILL"
21089
+ });
21090
+ const ok = result.status === 0 && result.error === void 0;
21091
+ return {
21092
+ ok,
21093
+ executable,
21094
+ version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
21095
+ error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
21096
+ };
21097
+ }
21098
+ function assertOpenCodeRuntime(runtime) {
21099
+ const diagnostic = diagnoseOpenCodeRuntime(runtime);
21100
+ if (!diagnostic.ok) {
21101
+ throw new Error(
21102
+ `[supertask] \u76EE\u6807 Gateway \u73AF\u5883\u65E0\u6CD5\u6267\u884C OpenCode (${diagnostic.executable}): ${diagnostic.error}`
21103
+ );
21104
+ }
21105
+ }
21106
+ function getGatewayDiagnostic(options = {}) {
21078
21107
  const producerScope = currentScope();
21079
21108
  if (!isPm2Installed()) {
21080
21109
  return {
@@ -21090,7 +21119,8 @@ function getGatewayDiagnostic() {
21090
21119
  startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
21091
21120
  currentScope: producerScope,
21092
21121
  gatewayScope: null,
21093
- scopeMatches: false
21122
+ scopeMatches: false,
21123
+ gatewayOpenCode: null
21094
21124
  };
21095
21125
  }
21096
21126
  const processes = pm2JsonList();
@@ -21118,7 +21148,8 @@ function getGatewayDiagnostic() {
21118
21148
  ) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
21119
21149
  currentScope: producerScope,
21120
21150
  gatewayScope: managedScope,
21121
- scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope)
21151
+ scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
21152
+ gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
21122
21153
  };
21123
21154
  }
21124
21155
  function writeRunningVersion(version2, env = process.env, cwd = process.cwd()) {
@@ -21921,6 +21952,7 @@ function installUnlocked() {
21921
21952
  assertRuntimeCanControlPm2(oldRuntime, existing);
21922
21953
  gatewayKillTimeoutMs(targetRuntime);
21923
21954
  gatewayTerminationCommandTimeoutMs(targetRuntime);
21955
+ assertOpenCodeRuntime(targetRuntime);
21924
21956
  if (existing) requirePm2Termination("delete", "pm2 delete existing Gateway", oldRuntime);
21925
21957
  const version2 = getPackageVersion();
21926
21958
  try {
@@ -22033,6 +22065,7 @@ function upgradeUnlocked(target) {
22033
22065
  assertRuntimeCanControlPm2(oldRuntime, existing);
22034
22066
  gatewayKillTimeoutMs(targetRuntime);
22035
22067
  gatewayTerminationCommandTimeoutMs(targetRuntime);
22068
+ assertOpenCodeRuntime(targetRuntime);
22036
22069
  if (existing) requirePm2Termination("delete", "pm2 delete old Gateway", oldRuntime);
22037
22070
  try {
22038
22071
  pm2StartGateway(targetRuntime);
@@ -22085,6 +22118,7 @@ function ensureGatewayUnlocked() {
22085
22118
  const before = getRunningVersion(oldRuntime?.env ?? process.env, oldRuntime?.cwd);
22086
22119
  gatewayKillTimeoutMs(targetRuntime);
22087
22120
  gatewayTerminationCommandTimeoutMs(targetRuntime);
22121
+ assertOpenCodeRuntime(targetRuntime);
22088
22122
  if (existing) requirePm2Termination("delete", "pm2 delete stale Gateway", oldRuntime);
22089
22123
  try {
22090
22124
  pm2StartGateway(targetRuntime);
@@ -22665,7 +22699,7 @@ import { spawn } from "child_process";
22665
22699
  import { fileURLToPath as fileURLToPath5 } from "url";
22666
22700
  import { existsSync as existsSync7 } from "fs";
22667
22701
  import { dirname as dirname8, join as join6 } from "path";
22668
- import { randomUUID as randomUUID2 } from "crypto";
22702
+ import { randomUUID as randomUUID3 } from "crypto";
22669
22703
  function runCommandContext(executable, args, cwd) {
22670
22704
  return JSON.stringify({
22671
22705
  type: "supertask_command",
@@ -22806,7 +22840,7 @@ var init_worker = __esm({
22806
22840
  }
22807
22841
  let runId = null;
22808
22842
  try {
22809
- const launchIdentity = `gateway-${process.pid}:launch:${randomUUID2()}`;
22843
+ const launchIdentity = `gateway-${process.pid}:launch:${randomUUID3()}`;
22810
22844
  const run = await TaskRunService.create({
22811
22845
  taskId: task.id,
22812
22846
  model: this.resolveModel(task.model),
@@ -22931,8 +22965,8 @@ var init_worker = __esm({
22931
22965
  }
22932
22966
  });
22933
22967
  let spawnError = null;
22934
- const spawned = new Promise((resolve5, reject) => {
22935
- child.once("spawn", resolve5);
22968
+ const spawned = new Promise((resolve6, reject) => {
22969
+ child.once("spawn", resolve6);
22936
22970
  child.once("error", (error) => {
22937
22971
  spawnError = error;
22938
22972
  reject(error);
@@ -22980,10 +23014,10 @@ var init_worker = __esm({
22980
23014
  return;
22981
23015
  }
22982
23016
  try {
22983
- await new Promise((resolve5, reject) => {
23017
+ await new Promise((resolve6, reject) => {
22984
23018
  child.stdin.end("START\n", (error) => {
22985
23019
  if (error) reject(error);
22986
- else resolve5();
23020
+ else resolve6();
22987
23021
  });
22988
23022
  });
22989
23023
  } catch (error) {
@@ -25997,7 +26031,7 @@ function cleanOutput(value) {
25997
26031
  return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
25998
26032
  }
25999
26033
  function runOpenCode(executable, args, cwd, timeoutMs) {
26000
- return new Promise((resolve5, reject) => {
26034
+ return new Promise((resolve6, reject) => {
26001
26035
  const child = spawn2(executable, args, {
26002
26036
  cwd,
26003
26037
  env: process.env,
@@ -26041,7 +26075,7 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26041
26075
  reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26042
26076
  return;
26043
26077
  }
26044
- resolve5(cleanOutput(stdout));
26078
+ resolve6(cleanOutput(stdout));
26045
26079
  });
26046
26080
  });
26047
26081
  }
@@ -26178,7 +26212,70 @@ function clientMessages(locale) {
26178
26212
  "action.refresh",
26179
26213
  "action.logs",
26180
26214
  "action.hideLogs",
26215
+ "details.title",
26216
+ "details.subtitle",
26217
+ "details.taskTitle",
26218
+ "details.runTitle",
26219
+ "details.templateTitle",
26220
+ "details.raw",
26181
26221
  "details.copySuccess",
26222
+ "details.id",
26223
+ "details.project",
26224
+ "details.prompt",
26225
+ "details.result",
26226
+ "details.category",
26227
+ "details.batch",
26228
+ "details.dependency",
26229
+ "details.importance",
26230
+ "details.urgency",
26231
+ "details.retryCount",
26232
+ "details.retryBackoff",
26233
+ "details.timeout",
26234
+ "details.createdAt",
26235
+ "details.updatedAt",
26236
+ "details.startedAt",
26237
+ "details.finishedAt",
26238
+ "details.scheduledAt",
26239
+ "details.enabled",
26240
+ "details.scheduleRule",
26241
+ "details.maxInstances",
26242
+ "details.maxRetries",
26243
+ "details.lastRun",
26244
+ "details.nextRun",
26245
+ "details.taskId",
26246
+ "details.session",
26247
+ "details.heartbeat",
26248
+ "details.process",
26249
+ "details.history",
26250
+ "details.noHistory",
26251
+ "details.none",
26252
+ "details.default",
26253
+ "details.enabledYes",
26254
+ "details.enabledNo",
26255
+ "table.name",
26256
+ "table.agent",
26257
+ "table.model",
26258
+ "table.status",
26259
+ "table.duration",
26260
+ "template.scheduleType",
26261
+ "status.pending",
26262
+ "status.running",
26263
+ "status.done",
26264
+ "status.failed",
26265
+ "status.dead_letter",
26266
+ "status.cancelled",
26267
+ "status.unknown",
26268
+ "runStatus.running",
26269
+ "runStatus.done",
26270
+ "runStatus.failed",
26271
+ "schedule.cron",
26272
+ "schedule.recurring",
26273
+ "schedule.delayed",
26274
+ "schedule.unknown",
26275
+ "duration.seconds",
26276
+ "duration.minutes",
26277
+ "duration.hours",
26278
+ "duration.days",
26182
26279
  "feedback.copyFailed",
26183
26280
  "dialog.cancelTask",
26184
26281
  "dialog.cancelTaskBody",
@@ -26307,8 +26404,8 @@ function renderLayout(options) {
26307
26404
  <div class="dialog-actions"><button type="button" class="btn" onclick="document.getElementById('directory-dialog').close()">${t(locale, "action.cancel")}</button><button id="directory-choose" type="button" class="btn btn-primary">${icon("folder")}${t(locale, "action.chooseThisFolder")}</button></div>
26308
26405
  </dialog>
26309
26406
  <dialog id="detail-dialog">
26310
- <div class="dialog-head"><div><h2>${t(locale, "details.title")}</h2><p>${t(locale, "details.subtitle")}</p></div><button class="icon-button" onclick="document.getElementById('detail-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
26311
- <div class="dialog-body"><pre id="detail-content" class="json-view"></pre></div>
26407
+ <div class="dialog-head"><div><h2 id="detail-title">${t(locale, "details.title")}</h2><p>${t(locale, "details.subtitle")}</p></div><button class="icon-button" onclick="document.getElementById('detail-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
26408
+ <div class="dialog-body"><div id="detail-content" class="detail-view"></div><details class="detail-raw"><summary>${t(locale, "details.raw")}</summary><pre id="detail-raw" class="json-view"></pre></details></div>
26312
26409
  <div class="dialog-actions"><button class="btn" onclick="copyDetails()">${icon("copy")}${t(locale, "action.copy")}</button><button class="btn btn-primary" onclick="document.getElementById('detail-dialog').close()">${t(locale, "action.close")}</button></div>
26313
26410
  </dialog>
26314
26411
  <dialog id="confirm-dialog">
@@ -26340,9 +26437,39 @@ function renderLayout(options) {
26340
26437
  async function retryTask(id){if(!await ask(text('dialog.retryTask',{id}),text('dialog.retryTaskBody')))return;try{await readJson(await fetch('/api/tasks/'+id+'/retry',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.retryFailed')+': '+error.message,'error')}}
26341
26438
  async function cancelTask(id){if(!await ask(text('dialog.cancelTask',{id}),text('dialog.cancelTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id+'/cancel',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.cancelFailed')+': '+error.message,'error')}}
26342
26439
  async function deleteTask(id){if(!await ask(text('dialog.deleteTask',{id}),text('dialog.deleteTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(text('feedback.deleteFailed')+': '+error.message,'error')}}
26343
- async function showRecord(url){try{const data=await readJson(await fetch(url));document.getElementById('detail-content').textContent=JSON.stringify(data,null,2);document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
26344
- const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
26345
- async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26440
+ function detailDate(value){if(value===null||value===undefined||value==='')return text('details.none');const epoch=typeof value==='number'&&value<100000000000?value*1000:value;const date=new Date(epoch);return Number.isNaN(date.getTime())?String(value):date.toLocaleString(document.documentElement.lang)}
26441
+ function detailDuration(value){if(value===null||value===undefined)return text('details.default');const milliseconds=Number(value);if(!Number.isFinite(milliseconds))return String(value);if(milliseconds===0)return '0 ms';const units=[[86400000,text('duration.days')],[3600000,text('duration.hours')],[60000,text('duration.minutes')],[1000,text('duration.seconds')]];for(const [size,label] of units){if(milliseconds>=size&&milliseconds%size===0)return String(milliseconds/size)+' '+label}return String(milliseconds)+' ms'}
26442
+ function detailStatus(type,value){const prefix=type==='run'?'runStatus.':'status.';const key=prefix+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('status.unknown')}
26443
+ function detailScheduleType(value){const key='schedule.'+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('schedule.unknown')}
26444
+ function detailModel(value){return !value||value==='default'?text('details.default'):String(value)}
26445
+ function detailSession(value){if(!value)return text('details.none');const session=String(value);return session.length<=10?session:session.slice(0,6)+'***'+session.slice(-4)}
26446
+ function detailTaskResult(data){const presentation=data._resultPresentation;if(!presentation)return text('details.none');const parts=[];if(Array.isArray(presentation.errors)&&presentation.errors.length)parts.push(presentation.errors.join('\\n'));if(presentation.text)parts.push(presentation.text);return parts.join('\\n\\n')||text('details.none')}
26447
+ function detailField(label,value,options={}){const item=document.createElement('div');item.className='detail-item'+(options.wide?' wide':'');const name=document.createElement('div');name.className='detail-label';name.textContent=label;const content=options.long?document.createElement('pre'):document.createElement('div');content.className='detail-value'+(options.mono?' mono':'')+(options.long?' long':'');content.textContent=value===null||value===undefined||value===''?text('details.none'):String(value);item.append(name,content);return item}
26448
+ function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
26449
+ function detailFields(type,data){if(type==='task')return [
26450
+ [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('table.status'),detailStatus('task',data.status)],[text('details.project'),data.cwd,{wide:true,mono:true}],
26451
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26452
+ [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
26453
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
26454
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
26455
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26456
+ [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
26457
+ ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
26458
+ [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
26459
+ [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26460
+ [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
26461
+ [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
26462
+ ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
26463
+ [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('details.enabled'),data.enabled?text('details.enabledYes'):text('details.enabledNo')],[text('details.project'),data.cwd,{wide:true,mono:true}],
26464
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26465
+ [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
26466
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
26467
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
26468
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.updatedAt'),detailDate(data.updatedAt)]
26469
+ ]}
26470
+ async function showRecord(url,type){try{const data=await readJson(await fetch(url));const content=document.getElementById('detail-content');content.replaceChildren();const grid=document.createElement('div');grid.className='detail-grid';for(const [label,value,options] of detailFields(type,data))grid.appendChild(detailField(label,value,options));content.appendChild(grid);if(type==='task')content.appendChild(renderDetailHistory(data._runs));document.getElementById('detail-title').textContent=text(type==='task'?'details.taskTitle':type==='run'?'details.runTitle':'details.templateTitle');document.getElementById('detail-raw').textContent=JSON.stringify(data,null,2);document.querySelector('#detail-dialog .detail-raw').open=false;document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
26471
+ const showDetail=id=>showRecord('/api/tasks/'+id,'task');const showRunDetail=id=>showRecord('/api/runs/'+id,'run');const showTemplateDetail=id=>showRecord('/api/templates/'+id,'template');
26472
+ async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-raw').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26346
26473
  async function copySessionCommand(id){try{const data=await readJson(await fetch('/api/runs/'+id+'/session-command'));await navigator.clipboard.writeText(data.command);showToast(text('feedback.sessionCommandCopied'))}catch(error){showToast(error.message||text('feedback.copyFailed'),'error')}}
26347
26474
  async function copyRunCommand(id){try{await navigator.clipboard.writeText(document.getElementById('command-'+id).textContent);showToast(text('feedback.commandCopied'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26348
26475
  function taskField(name){return document.getElementById('task-'+name)}
@@ -26380,7 +26507,7 @@ function renderLayout(options) {
26380
26507
  async function disableTmpl(id){if(!await ask(text('dialog.disableTemplate'),text('dialog.disableTemplateBody')))return;try{await readJson(await fetch('/api/templates/'+id+'/disable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
26381
26508
  async function deleteTmpl(id){if(!await ask(text('dialog.deleteTemplate'),text('dialog.deleteTemplateBody'),true))return;try{await readJson(await fetch('/api/templates/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(error.message,'error')}}
26382
26509
  async function triggerTmpl(id){if(!await ask(text('dialog.triggerTemplate'),text('dialog.triggerTemplateBody')))return;try{const data=await readJson(await fetch('/api/templates/'+id+'/trigger',{method:'POST'}));showToast(text('feedback.triggered',{id:data.taskId}));setTimeout(()=>location.reload(),550)}catch(error){showToast(error.message,'error')}}
26383
- function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');}
26510
+ function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');if(!hidden)requestAnimationFrame(()=>panel.scrollIntoView({block:'nearest',behavior:'smooth'}));}
26384
26511
  function filterTasks(value){const query=value.trim().toLocaleLowerCase();let visible=0;document.querySelectorAll('[data-task-row]').forEach(row=>{const match=!query||row.dataset.search.toLocaleLowerCase().includes(query);row.hidden=!match;if(match)visible++});const empty=document.getElementById('search-empty');if(empty)empty.hidden=visible!==0;}
26385
26512
  async function clearDatabase(){if(!await askDanger())return;try{const data=await readJson(await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})}));showToast(text('feedback.databaseCleared',{path:data.backupPath}));setTimeout(()=>location.reload(),1000)}catch(error){showToast(error.message,'error')}}
26386
26513
  async function confirmGatewayRestart(runningCount=0){const body=runningCount>0?text('dialog.restartGatewayRunningBody',{count:runningCount}):text('dialog.restartGatewayBody');return await ask(text('dialog.restartGateway'),body)}
@@ -26426,7 +26553,7 @@ var init_ui = __esm({
26426
26553
  "action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
26427
26554
  "action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
26428
26555
  "action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
26429
- "action.copy": "\u590D\u5236 JSON",
26556
+ "action.copy": "\u590D\u5236\u539F\u59CB\u6570\u636E",
26430
26557
  "action.close": "\u5173\u95ED",
26431
26558
  "action.confirm": "\u786E\u8BA4",
26432
26559
  "action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
@@ -26618,9 +26745,46 @@ var init_ui = __esm({
26618
26745
  "theme.light": "\u6D45\u8272",
26619
26746
  "theme.dark": "\u6DF1\u8272",
26620
26747
  "language.label": "\u8BED\u8A00",
26621
- "details.title": "\u6570\u636E\u8BE6\u60C5",
26622
- "details.subtitle": "\u539F\u59CB\u8BB0\u5F55\uFF08JSON\uFF09",
26623
- "details.copySuccess": "JSON \u5DF2\u590D\u5236",
26748
+ "details.title": "\u8BE6\u60C5",
26749
+ "details.subtitle": "\u91CD\u70B9\u4FE1\u606F\u5DF2\u6574\u7406\uFF1B\u539F\u59CB\u6570\u636E\u4EC5\u7528\u4E8E\u6392\u969C\u3002",
26750
+ "details.taskTitle": "\u4EFB\u52A1\u8BE6\u60C5",
26751
+ "details.runTitle": "\u6267\u884C\u8BE6\u60C5",
26752
+ "details.templateTitle": "\u5B9A\u65F6\u4EFB\u52A1\u8BE6\u60C5",
26753
+ "details.raw": "\u67E5\u770B\u539F\u59CB\u6570\u636E\uFF08JSON\uFF09",
26754
+ "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
26755
+ "details.id": "\u7F16\u53F7",
26756
+ "details.project": "\u9879\u76EE\u76EE\u5F55",
26757
+ "details.prompt": "\u63D0\u793A\u8BCD",
26758
+ "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
26759
+ "details.category": "\u5206\u7C7B",
26760
+ "details.batch": "\u6279\u6B21",
26761
+ "details.dependency": "\u4F9D\u8D56\u4EFB\u52A1",
26762
+ "details.importance": "\u91CD\u8981\u7A0B\u5EA6",
26763
+ "details.urgency": "\u7D27\u6025\u7A0B\u5EA6",
26764
+ "details.retryCount": "\u5DF2\u91CD\u8BD5 / \u6700\u591A\u91CD\u8BD5",
26765
+ "details.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
26766
+ "details.timeout": "\u6267\u884C\u8D85\u65F6",
26767
+ "details.createdAt": "\u521B\u5EFA\u65F6\u95F4",
26768
+ "details.updatedAt": "\u66F4\u65B0\u65F6\u95F4",
26769
+ "details.startedAt": "\u5F00\u59CB\u65F6\u95F4",
26770
+ "details.finishedAt": "\u7ED3\u675F\u65F6\u95F4",
26771
+ "details.scheduledAt": "\u8BA1\u5212\u65F6\u95F4",
26772
+ "details.enabled": "\u81EA\u52A8\u8FD0\u884C",
26773
+ "details.scheduleRule": "\u8FD0\u884C\u89C4\u5219",
26774
+ "details.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
26775
+ "details.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
26776
+ "details.lastRun": "\u4E0A\u6B21\u8FD0\u884C",
26777
+ "details.nextRun": "\u4E0B\u6B21\u8FD0\u884C",
26778
+ "details.taskId": "\u6240\u5C5E\u4EFB\u52A1",
26779
+ "details.session": "OpenCode \u4F1A\u8BDD",
26780
+ "details.heartbeat": "\u6700\u8FD1\u5FC3\u8DF3",
26781
+ "details.process": "\u8FDB\u7A0B",
26782
+ "details.history": "\u6267\u884C\u5386\u53F2",
26783
+ "details.noHistory": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
26784
+ "details.none": "\u65E0",
26785
+ "details.default": "\u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4",
26786
+ "details.enabledYes": "\u5DF2\u542F\u7528",
26787
+ "details.enabledNo": "\u5DF2\u505C\u7528",
26624
26788
  "dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
26625
26789
  "dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\u6811\u3002",
26626
26790
  "dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
@@ -26690,7 +26854,7 @@ var init_ui = __esm({
26690
26854
  "action.hideLogs": "Hide log",
26691
26855
  "action.save": "Save settings",
26692
26856
  "action.saveAndRestart": "Save and restart",
26693
- "action.copy": "Copy JSON",
26857
+ "action.copy": "Copy raw data",
26694
26858
  "action.close": "Close",
26695
26859
  "action.confirm": "Confirm",
26696
26860
  "action.clearDatabase": "Clear database",
@@ -26882,9 +27046,46 @@ var init_ui = __esm({
26882
27046
  "theme.light": "Light",
26883
27047
  "theme.dark": "Dark",
26884
27048
  "language.label": "Language",
26885
- "details.title": "Data details",
26886
- "details.subtitle": "Raw record (JSON)",
26887
- "details.copySuccess": "JSON copied",
27049
+ "details.title": "Details",
27050
+ "details.subtitle": "Key information is organized below; raw data is only for troubleshooting.",
27051
+ "details.taskTitle": "Task details",
27052
+ "details.runTitle": "Run details",
27053
+ "details.templateTitle": "Scheduled task details",
27054
+ "details.raw": "View raw data (JSON)",
27055
+ "details.copySuccess": "Raw data copied",
27056
+ "details.id": "ID",
27057
+ "details.project": "Project directory",
27058
+ "details.prompt": "Prompt",
27059
+ "details.result": "Result / failure reason",
27060
+ "details.category": "Category",
27061
+ "details.batch": "Batch",
27062
+ "details.dependency": "Dependency",
27063
+ "details.importance": "Importance",
27064
+ "details.urgency": "Urgency",
27065
+ "details.retryCount": "Retries used / allowed",
27066
+ "details.retryBackoff": "Retry delay",
27067
+ "details.timeout": "Run timeout",
27068
+ "details.createdAt": "Created",
27069
+ "details.updatedAt": "Updated",
27070
+ "details.startedAt": "Started",
27071
+ "details.finishedAt": "Finished",
27072
+ "details.scheduledAt": "Scheduled for",
27073
+ "details.enabled": "Automatic runs",
27074
+ "details.scheduleRule": "Schedule rule",
27075
+ "details.maxInstances": "Automatic scheduling limit",
27076
+ "details.maxRetries": "Failure retries",
27077
+ "details.lastRun": "Last run",
27078
+ "details.nextRun": "Next run",
27079
+ "details.taskId": "Task",
27080
+ "details.session": "OpenCode session",
27081
+ "details.heartbeat": "Latest heartbeat",
27082
+ "details.process": "Processes",
27083
+ "details.history": "Run history",
27084
+ "details.noHistory": "No runs yet",
27085
+ "details.none": "None",
27086
+ "details.default": "Use system default",
27087
+ "details.enabledYes": "Enabled",
27088
+ "details.enabledNo": "Disabled",
26888
27089
  "dialog.cancelTask": "Cancel task #{id}?",
26889
27090
  "dialog.cancelTaskBody": "A running task will terminate its process tree on the next worker poll.",
26890
27091
  "dialog.retryTask": "Retry task #{id}?",
@@ -27125,6 +27326,8 @@ var init_ui = __esm({
27125
27326
  .danger-card h2 .icon { width:17px; height:17px; }
27126
27327
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
27127
27328
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
27329
+ .run-log-row td { padding:0 16px 16px; background:color-mix(in srgb,var(--surface-2) 64%,var(--surface)); }
27330
+ .run-log-row .log-panel { margin:0; box-shadow:none; }
27128
27331
  .log-content { display:grid; gap:14px; padding:16px; }
27129
27332
  .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
27130
27333
  .run-command,.run-output,.run-error,.run-tools { min-width:0; }
@@ -27182,8 +27385,21 @@ var init_ui = __esm({
27182
27385
  .directory-item:active { transform:scale(.99); }
27183
27386
  .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
27184
27387
  .directory-empty { min-height:220px; display:grid; place-items:center; color:var(--text-3); }
27185
- .json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
27186
- background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
27388
+ .detail-view { display:grid; gap:16px; }
27389
+ .detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
27390
+ .detail-item { min-width:0; padding:12px 13px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); }
27391
+ .detail-item.wide { grid-column:1 / -1; }
27392
+ .detail-label { margin-bottom:5px; color:var(--text-3); font-size:10px; font-weight:750; letter-spacing:.045em; text-transform:uppercase; }
27393
+ .detail-value { color:var(--text); font-size:13px; line-height:1.55; overflow-wrap:anywhere; }
27394
+ .detail-value.mono { font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; }
27395
+ .detail-value.long { max-height:240px; margin:0; overflow:auto; white-space:pre-wrap; }
27396
+ .detail-history h3 { margin:0 0 8px; font-size:13px; }
27397
+ .detail-history-list { display:grid; gap:7px; }
27398
+ .detail-history-item { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 11px; border:1px solid var(--border); border-radius:9px; background:var(--surface-2); font-size:11px; }
27399
+ .detail-raw { padding-top:12px; border-top:1px solid var(--border); }
27400
+ .detail-raw summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
27401
+ .json-view { max-height:320px; margin:10px 0 0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
27402
+ background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
27187
27403
  .confirm-copy { color:var(--text-2); margin:0; }
27188
27404
  .confirm-copy strong { display:block; margin-bottom:5px; color:var(--text); font-size:15px; }
27189
27405
  .danger-input { width:100%; height:40px; margin-top:14px; padding:0 12px; border:1px solid var(--border); border-radius:9px; outline:none;
@@ -27247,8 +27463,13 @@ var init_ui = __esm({
27247
27463
  .responsive-table td[data-primary]::before { display:none; }
27248
27464
  .responsive-table .task-prompt { max-width:100%; }
27249
27465
  .responsive-table .actions { justify-content:flex-start; }
27466
+ .responsive-table .run-log-row { padding:0; overflow:hidden; }
27467
+ .responsive-table .run-log-cell { display:block; padding:0; }
27468
+ .responsive-table .run-log-cell::before { display:none; }
27250
27469
  .project-grid { grid-template-columns:1fr; }
27251
27470
  .template-form-grid { grid-template-columns:1fr; }
27471
+ .detail-grid { grid-template-columns:1fr; }
27472
+ .detail-item.wide { grid-column:auto; }
27252
27473
  .form-field-wide { grid-column:auto; }
27253
27474
  .field-action { align-items:stretch; flex-direction:column; }
27254
27475
  .field-action .btn { width:100%; }
@@ -27568,7 +27789,7 @@ function renderRunLog(runId, taskName, log, locale) {
27568
27789
  const errors = presentation.errors.length > 0 ? `<div class="run-error"><strong>${t(locale, "logs.error")}</strong><pre>${esc(presentation.errors.join("\n"))}</pre></div>` : "";
27569
27790
  const tools = presentation.tools.length > 0 ? `<div class="run-tools"><strong>${t(locale, "logs.tools")}</strong><div class="actions">${presentation.tools.map((tool) => `<span class="tag">${esc(tool)}</span>`).join("")}</div></div>` : "";
27570
27791
  const output = `<div class="run-output"><strong>${t(locale, "logs.output")}</strong><pre>${esc(presentation.text || t(locale, "logs.noText"))}</pre></div>`;
27571
- return `<section id="log-${runId}" class="panel log-panel" hidden><div class="panel-head"><h3>Run #${runId} \xB7 ${esc(taskName)}</h3></div><div class="log-content">${command}${errors}${output}${tools}<details class="raw-log"><summary>${t(locale, "logs.raw")}</summary><div class="log-box">${esc(log)}</div></details></div></section>`;
27792
+ return `<section class="panel log-panel"><div class="panel-head"><h3>Run #${runId} \xB7 ${esc(taskName)}</h3></div><div class="log-content">${command}${errors}${output}${tools}<details class="raw-log"><summary>${t(locale, "logs.raw")}</summary><div class="log-box">${esc(log)}</div></details></div></section>`;
27572
27793
  }
27573
27794
  function esc(value) {
27574
27795
  if (!value) return "";
@@ -28008,14 +28229,11 @@ var init_web = __esm({
28008
28229
  const total = Number(totalResult[0]?.count ?? 0);
28009
28230
  const totalPages = Math.max(1, Math.ceil(total / limit));
28010
28231
  if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
28011
- const logs = [];
28012
28232
  const rows = runs.map((run) => {
28013
28233
  const status = safeStatus(run.status);
28014
28234
  const resumable = isValidSessionId(run.sessionId);
28015
- if (run.log) {
28016
- logs.push(renderRunLog(run.id, run.taskName, run.log, locale));
28017
- }
28018
- return `<tr>
28235
+ const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale) : "";
28236
+ return `<tr class="run-summary-row">
28019
28237
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
28020
28238
  <td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model ? `<div style="margin-top:4px"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
28021
28239
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
@@ -28026,7 +28244,7 @@ var init_web = __esm({
28026
28244
  <td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showRunDetail(${run.id})">${t(locale, "action.details")}</button>
28027
28245
  ${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
28028
28246
  ${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
28029
- </tr>`;
28247
+ </tr>${log ? `<tr id="log-${run.id}" class="run-log-row" hidden><td class="run-log-cell" colspan="8">${log}</td></tr>` : ""}`;
28030
28248
  }).join("");
28031
28249
  const body = `
28032
28250
  <div class="stats-grid">
@@ -28036,7 +28254,7 @@ var init_web = __esm({
28036
28254
  ${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
28037
28255
  </div>
28038
28256
  <section class="panel reveal reveal-delay-2">${runs.length === 0 ? emptyState(t(locale, "empty.runs"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.agent")}</th><th>${t(locale, "table.session")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.duration")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}</section>
28039
- ${logs.join("")}${pagination(locale, "/runs", page, totalPages, total)}`;
28257
+ ${pagination(locale, "/runs", page, totalPages, total)}`;
28040
28258
  return c.html(renderLayout({ locale, activeTab: "runs", body }));
28041
28259
  });
28042
28260
  app.get("/system", async (c) => {
@@ -28143,7 +28361,11 @@ var init_web = __esm({
28143
28361
  const task = await TaskService.getById(id);
28144
28362
  if (!task) return c.json({ error: "not found" }, 404);
28145
28363
  const runs = await TaskRunService.listByTaskId(id);
28146
- return c.json({ ...task, _runs: runs });
28364
+ return c.json({
28365
+ ...task,
28366
+ _resultPresentation: task.resultLog ? presentRunLog(task.resultLog) : null,
28367
+ _runs: runs
28368
+ });
28147
28369
  });
28148
28370
  app.get("/api/runs/:id", async (c) => {
28149
28371
  const id = parsePositiveInteger2(c.req.param("id"));
@@ -28648,7 +28870,6 @@ init_db2();
28648
28870
  init_duration();
28649
28871
  init_pm2();
28650
28872
  init_config();
28651
- import { spawnSync as spawnSync4 } from "child_process";
28652
28873
 
28653
28874
  // src/cli/i18n.ts
28654
28875
  function requestedLanguage(argv) {
@@ -28861,6 +29082,118 @@ function parseTaskStatus(value) {
28861
29082
 
28862
29083
  // src/cli/index.ts
28863
29084
  init_update2();
29085
+
29086
+ // src/cli/doctor-smoke.ts
29087
+ init_task_service();
29088
+ init_task_run_service();
29089
+ import { randomUUID as randomUUID2 } from "crypto";
29090
+ import { resolve as resolve5 } from "path";
29091
+ function tail(value, maxLength = 2e3) {
29092
+ if (!value) return null;
29093
+ return value.length <= maxLength ? value : value.slice(-maxLength);
29094
+ }
29095
+ function extractOpenCodeText(log) {
29096
+ if (!log) return "";
29097
+ const parts = [];
29098
+ for (const line of log.split("\n")) {
29099
+ try {
29100
+ const parsed = JSON.parse(line);
29101
+ if (parsed.type === "text" && parsed.part?.type === "text" && typeof parsed.part.text === "string") {
29102
+ parts.push(parsed.part.text);
29103
+ }
29104
+ } catch {
29105
+ }
29106
+ }
29107
+ return parts.join("\n").trim();
29108
+ }
29109
+ async function runDoctorSmoke(options) {
29110
+ if (!options.agent.trim()) throw new Error("smoke agent \u4E0D\u80FD\u4E3A\u7A7A");
29111
+ if (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1e3 || options.timeoutMs > 6048e5) {
29112
+ throw new Error("smoke timeout \u5FC5\u987B\u662F 1 \u79D2\u5230 7 \u5929\u4E4B\u95F4\u7684\u6574\u6570\u6BEB\u79D2\u503C");
29113
+ }
29114
+ const cwd = resolve5(options.cwd);
29115
+ const marker = options.marker ?? `SUPERTASK_SMOKE_${randomUUID2().replaceAll("-", "").toUpperCase()}`;
29116
+ const model = options.model && options.model !== "default" ? options.model : void 0;
29117
+ const startedAt = Date.now();
29118
+ const task = await TaskService.add({
29119
+ name: "[doctor] Gateway real smoke test",
29120
+ agent: options.agent,
29121
+ model,
29122
+ prompt: `\u4E0D\u8981\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\u3002\u53EA\u56DE\u590D\u8FD9\u4E00\u884C\uFF1A${marker}`,
29123
+ cwd,
29124
+ category: "diagnostic",
29125
+ importance: 5,
29126
+ urgency: 5,
29127
+ batchId: `doctor-smoke-${randomUUID2()}`,
29128
+ maxRetries: 0,
29129
+ retryBackoffMs: 0,
29130
+ timeoutMs: options.timeoutMs
29131
+ });
29132
+ const deadline = startedAt + options.timeoutMs;
29133
+ const pollIntervalMs = Math.max(10, options.pollIntervalMs ?? 250);
29134
+ while (Date.now() < deadline) {
29135
+ const current2 = await TaskService.getById(task.id);
29136
+ const run2 = await TaskRunService.getLatestByTaskId(task.id);
29137
+ if (!current2) {
29138
+ return {
29139
+ ok: false,
29140
+ taskId: task.id,
29141
+ runId: run2?.id ?? null,
29142
+ status: "missing",
29143
+ agent: options.agent,
29144
+ model: model ?? null,
29145
+ cwd,
29146
+ durationMs: Date.now() - startedAt,
29147
+ error: "\u5192\u70DF\u4EFB\u52A1\u5728\u5B8C\u6210\u524D\u88AB\u5220\u9664"
29148
+ };
29149
+ }
29150
+ if (current2.status === "done") {
29151
+ const log = run2?.log ?? current2.resultLog;
29152
+ const markerObserved = extractOpenCodeText(log) === marker;
29153
+ return {
29154
+ ok: markerObserved,
29155
+ taskId: task.id,
29156
+ runId: run2?.id ?? null,
29157
+ status: current2.status,
29158
+ agent: options.agent,
29159
+ model: model ?? null,
29160
+ cwd,
29161
+ durationMs: Date.now() - startedAt,
29162
+ error: markerObserved ? null : "OpenCode \u5DF2\u9000\u51FA\u6210\u529F\uFF0C\u4F46\u6A21\u578B\u6587\u672C\u4E0D\u662F\u9884\u671F\u7684\u7CBE\u786E\u6807\u8BB0"
29163
+ };
29164
+ }
29165
+ if (current2.status === "dead_letter" || current2.status === "cancelled") {
29166
+ return {
29167
+ ok: false,
29168
+ taskId: task.id,
29169
+ runId: run2?.id ?? null,
29170
+ status: current2.status,
29171
+ agent: options.agent,
29172
+ model: model ?? null,
29173
+ cwd,
29174
+ durationMs: Date.now() - startedAt,
29175
+ error: tail(run2?.log ?? current2.resultLog) ?? `\u4EFB\u52A1\u8FDB\u5165 ${current2.status}`
29176
+ };
29177
+ }
29178
+ await Bun.sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
29179
+ }
29180
+ const current = await TaskService.getById(task.id);
29181
+ const run = await TaskRunService.getLatestByTaskId(task.id);
29182
+ await TaskService.cancel(task.id);
29183
+ return {
29184
+ ok: false,
29185
+ taskId: task.id,
29186
+ runId: run?.id ?? null,
29187
+ status: current?.status ?? "missing",
29188
+ agent: options.agent,
29189
+ model: model ?? null,
29190
+ cwd,
29191
+ durationMs: Date.now() - startedAt,
29192
+ error: `\u7B49\u5F85 Gateway \u6267\u884C\u8D85\u8FC7 ${options.timeoutMs}ms\uFF0C\u5192\u70DF\u4EFB\u52A1\u5DF2\u8BF7\u6C42\u53D6\u6D88`
29193
+ };
29194
+ }
29195
+
29196
+ // src/cli/index.ts
28864
29197
  var cliLocale = resolveCliLocale();
28865
29198
  var t2 = (zh, en) => cliText(cliLocale, zh, en);
28866
29199
  async function withDb(fn, formatError = (error) => JSON.stringify({
@@ -29194,25 +29527,15 @@ program2.command("config").description(t2("\u663E\u793A\u5F53\u524D\u914D\u7F6E"
29194
29527
  const cfg = loadConfig2();
29195
29528
  console.log(JSON.stringify(cfg, null, 2));
29196
29529
  });
29197
- program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\u5E93\u3001Gateway\u3001Web \u754C\u9762\u548C\u65E5\u5FD7\u8F6E\u8F6C", "diagnose OpenCode, database, Gateway, Dashboard, and log rotation")).option("--json", t2("\u5F3A\u5236\u8F93\u51FA JSON", "force JSON output")).action(async (options) => withDb(async () => {
29530
+ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\u5E93\u3001Gateway\u3001Web \u754C\u9762\u548C\u65E5\u5FD7\u8F6E\u8F6C", "diagnose OpenCode, database, Gateway, Dashboard, and log rotation")).option("--json", t2("\u5F3A\u5236\u8F93\u51FA JSON", "force JSON output")).option("--smoke", t2("\u901A\u8FC7 Gateway \u63D0\u4EA4\u4E00\u4E2A\u771F\u5B9E OpenCode \u4EFB\u52A1\u5E76\u9A8C\u8BC1\u8F93\u51FA", "queue a real OpenCode task through Gateway and verify its output")).option("--smoke-agent <agent>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684 Agent", "Agent used by the real smoke task"), "build").option("--smoke-model <model>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u8DDF\u968F Agent \u914D\u7F6E", "model used by the real smoke task; defaults to the Agent configuration")).option("--smoke-cwd <path>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7684\u9879\u76EE\u76EE\u5F55\uFF1B\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55", "project directory for the real smoke task; defaults to the current directory")).option("--smoke-timeout <duration>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7B49\u5F85\u4E0A\u9650\uFF0C\u5982 2min / 5min", "real smoke task timeout, e.g. 2min / 5min"), "3min").action(async (options) => withDb(async () => {
29198
29531
  const config = loadConfig();
29199
29532
  const database = DatabaseMaintenanceService.check();
29200
29533
  const legacyQuarantinedRuns = await TaskRunService.listLegacyQuarantinedRuns(
29201
29534
  config.watchdog.heartbeatTimeoutMs
29202
29535
  );
29203
- const gateway = getGatewayDiagnostic();
29536
+ const gateway = getGatewayDiagnostic({ probeOpenCode: true });
29204
29537
  const packageVersion2 = getPackageVersion();
29205
- const opencodeBin2 = process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
29206
- const opencodeResult = spawnSync4(opencodeBin2, ["--version"], {
29207
- encoding: "utf8",
29208
- env: process.env
29209
- });
29210
- const opencode = {
29211
- ok: opencodeResult.status === 0,
29212
- executable: opencodeBin2,
29213
- version: opencodeResult.status === 0 ? opencodeResult.stdout.trim() : null,
29214
- error: opencodeResult.status === 0 ? null : opencodeResult.error?.message || opencodeResult.stderr.trim() || t2(`\u9000\u51FA\u7801 ${opencodeResult.status}`, `exit code ${opencodeResult.status}`)
29215
- };
29538
+ const opencode = diagnoseOpenCodeRuntime();
29216
29539
  const plugin = getOpenCodePluginDiagnostic();
29217
29540
  let dashboard = {
29218
29541
  enabled: config.dashboard.enabled,
@@ -29284,6 +29607,12 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29284
29607
  "The current CLI/OpenCode database, config, or OpenCode executable scope does not match the PM2 Gateway"
29285
29608
  ));
29286
29609
  }
29610
+ if (gateway.processFound && gateway.gatewayOpenCode?.ok !== true) {
29611
+ warnings.push(t2(
29612
+ `PM2 \u4FDD\u5B58\u7684 Gateway \u73AF\u5883\u65E0\u6CD5\u6267\u884C OpenCode\uFF1A${gateway.gatewayOpenCode?.error ?? "\u65E0\u6CD5\u8BFB\u53D6\u8FD0\u884C\u73AF\u5883"}`,
29613
+ `The Gateway environment saved by PM2 cannot execute OpenCode: ${gateway.gatewayOpenCode?.error ?? "runtime unavailable"}`
29614
+ ));
29615
+ }
29287
29616
  for (const run of legacyQuarantinedRuns) {
29288
29617
  const cwdHint = run.taskCwd == null ? t2("\uFF08\u65E7\u4EFB\u52A1\u6CA1\u6709 cwd\uFF0C\u8BF7\u5148\u5728 Dashboard \u53D6\u6D88\uFF09", " (the legacy task has no cwd; cancel it in the Dashboard first)") : t2(`\uFF08\u5728 ${run.taskCwd} \u6267\u884C\uFF09`, ` (run in ${run.taskCwd})`);
29289
29618
  const cancel = run.taskStatus === "cancelled" ? "" : t2(`\u5148${cwdHint} supertask cancel --id ${run.taskId}\uFF1B`, `first${cwdHint}: supertask cancel --id ${run.taskId}; `);
@@ -29293,7 +29622,31 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29293
29622
  `Legacy quarantined run #${run.runId}: ${owner}${cancel}after confirming no OpenCode process remains, run supertask run abandon --id ${run.runId} --confirm ABANDON`
29294
29623
  ));
29295
29624
  }
29296
- const ok = opencode.ok && plugin.ok && database.ok && legacyQuarantinedRuns.length === 0 && gateway.pm2Installed && gateway.status === "online" && gateway.ready && gatewayEntryPinned && gatewayVersionMatchesPackage && configuredVersionsMatch && cliVersionMatchesPlugin && gateway.scopeMatches && gateway.logRotationInstalled && gateway.startupConfigured !== false && dashboard.ok;
29625
+ let smoke = null;
29626
+ if (options.smoke) {
29627
+ const gatewayCanExecute = gateway.status === "online" && gateway.ready && gateway.scopeMatches && gateway.gatewayOpenCode?.ok === true;
29628
+ if (!gatewayCanExecute) {
29629
+ smoke = {
29630
+ ok: false,
29631
+ skipped: true,
29632
+ error: t2(
29633
+ "Gateway \u5C1A\u672A\u5C31\u7EEA\uFF0C\u6216\u5176 PM2 \u73AF\u5883\u65E0\u6CD5\u6267\u884C OpenCode\uFF1B\u5DF2\u8DF3\u8FC7\u771F\u5B9E\u4EFB\u52A1",
29634
+ "Gateway is not ready or its PM2 environment cannot execute OpenCode; the real task was skipped"
29635
+ )
29636
+ };
29637
+ } else {
29638
+ const smokeTimeoutMs = parseDuration(options.smokeTimeout);
29639
+ if (smokeTimeoutMs === null) throw new Error("smoke-timeout \u683C\u5F0F\u65E0\u6548");
29640
+ smoke = await runDoctorSmoke({
29641
+ agent: options.smokeAgent,
29642
+ model: options.smokeModel,
29643
+ cwd: options.smokeCwd ?? process.cwd(),
29644
+ timeoutMs: smokeTimeoutMs
29645
+ });
29646
+ }
29647
+ if (!smoke.ok) warnings.push(smoke.error ?? t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u5931\u8D25", "Real smoke task failed"));
29648
+ }
29649
+ const ok = opencode.ok && plugin.ok && database.ok && legacyQuarantinedRuns.length === 0 && gateway.pm2Installed && gateway.status === "online" && gateway.ready && gatewayEntryPinned && gatewayVersionMatchesPackage && configuredVersionsMatch && cliVersionMatchesPlugin && gateway.scopeMatches && gateway.gatewayOpenCode?.ok === true && gateway.logRotationInstalled && gateway.startupConfigured !== false && dashboard.ok && (!options.smoke || smoke?.ok === true);
29297
29650
  const report = {
29298
29651
  ok,
29299
29652
  packageVersion: packageVersion2,
@@ -29305,6 +29658,7 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29305
29658
  legacyQuarantinedRuns,
29306
29659
  gateway,
29307
29660
  dashboard,
29661
+ smoke,
29308
29662
  warnings
29309
29663
  };
29310
29664
  const json = options.json || !process.stdout.isTTY;
@@ -29314,10 +29668,15 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29314
29668
  const mark = (value) => value ? "\u2713" : "\u2717";
29315
29669
  console.log(`SuperTask doctor: ${ok ? t2("\u6B63\u5E38", "healthy") : t2("\u5F02\u5E38", "unhealthy")}`);
29316
29670
  console.log(`${mark(opencode.ok)} OpenCode ${opencode.version ?? opencode.error ?? t2("\u4E0D\u53EF\u7528", "unavailable")}`);
29671
+ console.log(`${mark(gateway.gatewayOpenCode?.ok === true)} ${t2("Gateway \u73AF\u5883\u4E2D\u7684 OpenCode", "OpenCode in Gateway environment")} ${gateway.gatewayOpenCode?.version ?? gateway.gatewayOpenCode?.error ?? t2("\u4E0D\u53EF\u7528", "unavailable")}${gateway.gatewayOpenCode?.executable ? `\uFF0C${gateway.gatewayOpenCode.executable}` : ""}`);
29317
29672
  console.log(`${mark(plugin.ok)} ${t2("OpenCode \u63D2\u4EF6", "OpenCode plugin")} ${plugin.spec || plugin.error || t2("\u672A\u914D\u7F6E", "not configured")}${plugin.cachedVersion ? t2(`\uFF08\u7F13\u5B58 v${plugin.cachedVersion}\uFF09`, ` (cached v${plugin.cachedVersion})`) : ""}`);
29318
29673
  console.log(`${mark(database.ok)} ${t2("\u6570\u636E\u5E93", "Database")} ${database.path}${t2(`\uFF08\u4EFB\u52A1 ${database.counts.tasks}\uFF0C\u8FD0\u884C\u4E2D ${database.runningTasks}\uFF09`, ` (tasks ${database.counts.tasks}, running ${database.runningTasks})`)}`);
29319
29674
  console.log(`${mark(gateway.status === "online" && gateway.ready && gatewayEntryPinned && gatewayVersionMatchesPackage)} Gateway ${gateway.status ?? "missing"}${gateway.pid ? `\uFF0CPID ${gateway.pid}` : ""}${gateway.runningVersion ? `\uFF0Cv${gateway.runningVersion}` : ""}${gateway.gatewayEntry ? `\uFF0C${gateway.gatewayEntry}` : ""}`);
29320
29675
  console.log(`${mark(dashboard.ok)} Dashboard ${dashboard.enabled ? dashboard.url : t2("\u5DF2\u7981\u7528", "disabled")}`);
29676
+ if (options.smoke) {
29677
+ const smokeSummary = smoke && "taskId" in smoke ? `task #${smoke.taskId}${smoke.runId ? ` / run #${smoke.runId}` : ""}\uFF0C${smoke.durationMs}ms` : smoke?.error ?? t2("\u672A\u6267\u884C", "not run");
29678
+ console.log(`${mark(smoke?.ok === true)} ${t2("\u771F\u5B9E Gateway \u5192\u70DF\u4EFB\u52A1", "Real Gateway smoke task")} ${smokeSummary}`);
29679
+ }
29321
29680
  for (const warning of warnings) console.log(`! ${warning}`);
29322
29681
  }
29323
29682
  if (!ok) process.exitCode = 1;