opencode-supertask 0.1.35 → 0.1.37

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),
@@ -22875,6 +22909,7 @@ var init_worker = __esm({
22875
22909
  const args = ["run", "--agent", task.agent, "--format", "json"];
22876
22910
  if (model) args.push("-m", model);
22877
22911
  args.push(task.prompt);
22912
+ const cwd = task.cwd || process.cwd();
22878
22913
  const child = spawn(process.execPath, [
22879
22914
  this.launcherEntry(),
22880
22915
  LAUNCH_IDENTITY_ARGUMENT,
@@ -22882,9 +22917,10 @@ var init_worker = __esm({
22882
22917
  this.opencodeBin,
22883
22918
  ...args
22884
22919
  ], {
22885
- cwd: task.cwd || process.cwd(),
22920
+ cwd,
22886
22921
  env: {
22887
22922
  ...process.env,
22923
+ PWD: cwd,
22888
22924
  [MANAGED_RUN_ENV]: MANAGED_RUN_ENV_VALUE
22889
22925
  },
22890
22926
  stdio: ["pipe", "pipe", "pipe", "ipc"],
@@ -22895,7 +22931,7 @@ var init_worker = __esm({
22895
22931
  runId,
22896
22932
  launchIdentity,
22897
22933
  child,
22898
- commandContext: runCommandContext(this.opencodeBin, args, task.cwd || process.cwd()),
22934
+ commandContext: runCommandContext(this.opencodeBin, args, cwd),
22899
22935
  output: "",
22900
22936
  sessionId: null,
22901
22937
  timeoutTimer: null,
@@ -22931,8 +22967,8 @@ var init_worker = __esm({
22931
22967
  }
22932
22968
  });
22933
22969
  let spawnError = null;
22934
- const spawned = new Promise((resolve5, reject) => {
22935
- child.once("spawn", resolve5);
22970
+ const spawned = new Promise((resolve6, reject) => {
22971
+ child.once("spawn", resolve6);
22936
22972
  child.once("error", (error) => {
22937
22973
  spawnError = error;
22938
22974
  reject(error);
@@ -22980,10 +23016,10 @@ var init_worker = __esm({
22980
23016
  return;
22981
23017
  }
22982
23018
  try {
22983
- await new Promise((resolve5, reject) => {
23019
+ await new Promise((resolve6, reject) => {
22984
23020
  child.stdin.end("START\n", (error) => {
22985
23021
  if (error) reject(error);
22986
- else resolve5();
23022
+ else resolve6();
22987
23023
  });
22988
23024
  });
22989
23025
  } catch (error) {
@@ -25997,7 +26033,7 @@ function cleanOutput(value) {
25997
26033
  return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
25998
26034
  }
25999
26035
  function runOpenCode(executable, args, cwd, timeoutMs) {
26000
- return new Promise((resolve5, reject) => {
26036
+ return new Promise((resolve6, reject) => {
26001
26037
  const child = spawn2(executable, args, {
26002
26038
  cwd,
26003
26039
  env: process.env,
@@ -26041,7 +26077,7 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26041
26077
  reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26042
26078
  return;
26043
26079
  }
26044
- resolve5(cleanOutput(stdout));
26080
+ resolve6(cleanOutput(stdout));
26045
26081
  });
26046
26082
  });
26047
26083
  }
@@ -26178,7 +26214,70 @@ function clientMessages(locale) {
26178
26214
  "action.refresh",
26179
26215
  "action.logs",
26180
26216
  "action.hideLogs",
26217
+ "details.title",
26218
+ "details.subtitle",
26219
+ "details.taskTitle",
26220
+ "details.runTitle",
26221
+ "details.templateTitle",
26222
+ "details.raw",
26181
26223
  "details.copySuccess",
26224
+ "details.id",
26225
+ "details.project",
26226
+ "details.prompt",
26227
+ "details.result",
26228
+ "details.category",
26229
+ "details.batch",
26230
+ "details.dependency",
26231
+ "details.importance",
26232
+ "details.urgency",
26233
+ "details.retryCount",
26234
+ "details.retryBackoff",
26235
+ "details.timeout",
26236
+ "details.createdAt",
26237
+ "details.updatedAt",
26238
+ "details.startedAt",
26239
+ "details.finishedAt",
26240
+ "details.scheduledAt",
26241
+ "details.enabled",
26242
+ "details.scheduleRule",
26243
+ "details.maxInstances",
26244
+ "details.maxRetries",
26245
+ "details.lastRun",
26246
+ "details.nextRun",
26247
+ "details.taskId",
26248
+ "details.session",
26249
+ "details.heartbeat",
26250
+ "details.process",
26251
+ "details.history",
26252
+ "details.noHistory",
26253
+ "details.none",
26254
+ "details.default",
26255
+ "details.enabledYes",
26256
+ "details.enabledNo",
26257
+ "table.name",
26258
+ "table.agent",
26259
+ "table.model",
26260
+ "table.status",
26261
+ "table.duration",
26262
+ "template.scheduleType",
26263
+ "status.pending",
26264
+ "status.running",
26265
+ "status.done",
26266
+ "status.failed",
26267
+ "status.dead_letter",
26268
+ "status.cancelled",
26269
+ "status.unknown",
26270
+ "runStatus.running",
26271
+ "runStatus.done",
26272
+ "runStatus.failed",
26273
+ "schedule.cron",
26274
+ "schedule.recurring",
26275
+ "schedule.delayed",
26276
+ "schedule.unknown",
26277
+ "duration.seconds",
26278
+ "duration.minutes",
26279
+ "duration.hours",
26280
+ "duration.days",
26182
26281
  "feedback.copyFailed",
26183
26282
  "dialog.cancelTask",
26184
26283
  "dialog.cancelTaskBody",
@@ -26307,8 +26406,8 @@ function renderLayout(options) {
26307
26406
  <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
26407
  </dialog>
26309
26408
  <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>
26409
+ <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>
26410
+ <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
26411
  <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
26412
  </dialog>
26314
26413
  <dialog id="confirm-dialog">
@@ -26340,9 +26439,39 @@ function renderLayout(options) {
26340
26439
  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
26440
  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
26441
  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')}}
26442
+ 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)}
26443
+ 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'}
26444
+ 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')}
26445
+ function detailScheduleType(value){const key='schedule.'+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('schedule.unknown')}
26446
+ function detailModel(value){return !value||value==='default'?text('details.default'):String(value)}
26447
+ 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)}
26448
+ 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')}
26449
+ 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}
26450
+ 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}
26451
+ function detailFields(type,data){if(type==='task')return [
26452
+ [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}],
26453
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26454
+ [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
26455
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
26456
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
26457
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26458
+ [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
26459
+ ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
26460
+ [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
26461
+ [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26462
+ [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
26463
+ [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
26464
+ ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
26465
+ [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}],
26466
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26467
+ [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
26468
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
26469
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
26470
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.updatedAt'),detailDate(data.updatedAt)]
26471
+ ]}
26472
+ 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')}}
26473
+ const showDetail=id=>showRecord('/api/tasks/'+id,'task');const showRunDetail=id=>showRecord('/api/runs/'+id,'run');const showTemplateDetail=id=>showRecord('/api/templates/'+id,'template');
26474
+ async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-raw').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26346
26475
  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
26476
  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
26477
  function taskField(name){return document.getElementById('task-'+name)}
@@ -26380,7 +26509,7 @@ function renderLayout(options) {
26380
26509
  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
26510
  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
26511
  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');}
26512
+ 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
26513
  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
26514
  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
26515
  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 +26555,7 @@ var init_ui = __esm({
26426
26555
  "action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
26427
26556
  "action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
26428
26557
  "action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
26429
- "action.copy": "\u590D\u5236 JSON",
26558
+ "action.copy": "\u590D\u5236\u539F\u59CB\u6570\u636E",
26430
26559
  "action.close": "\u5173\u95ED",
26431
26560
  "action.confirm": "\u786E\u8BA4",
26432
26561
  "action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
@@ -26618,9 +26747,46 @@ var init_ui = __esm({
26618
26747
  "theme.light": "\u6D45\u8272",
26619
26748
  "theme.dark": "\u6DF1\u8272",
26620
26749
  "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",
26750
+ "details.title": "\u8BE6\u60C5",
26751
+ "details.subtitle": "\u91CD\u70B9\u4FE1\u606F\u5DF2\u6574\u7406\uFF1B\u539F\u59CB\u6570\u636E\u4EC5\u7528\u4E8E\u6392\u969C\u3002",
26752
+ "details.taskTitle": "\u4EFB\u52A1\u8BE6\u60C5",
26753
+ "details.runTitle": "\u6267\u884C\u8BE6\u60C5",
26754
+ "details.templateTitle": "\u5B9A\u65F6\u4EFB\u52A1\u8BE6\u60C5",
26755
+ "details.raw": "\u67E5\u770B\u539F\u59CB\u6570\u636E\uFF08JSON\uFF09",
26756
+ "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
26757
+ "details.id": "\u7F16\u53F7",
26758
+ "details.project": "\u9879\u76EE\u76EE\u5F55",
26759
+ "details.prompt": "\u63D0\u793A\u8BCD",
26760
+ "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
26761
+ "details.category": "\u5206\u7C7B",
26762
+ "details.batch": "\u6279\u6B21",
26763
+ "details.dependency": "\u4F9D\u8D56\u4EFB\u52A1",
26764
+ "details.importance": "\u91CD\u8981\u7A0B\u5EA6",
26765
+ "details.urgency": "\u7D27\u6025\u7A0B\u5EA6",
26766
+ "details.retryCount": "\u5DF2\u91CD\u8BD5 / \u6700\u591A\u91CD\u8BD5",
26767
+ "details.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
26768
+ "details.timeout": "\u6267\u884C\u8D85\u65F6",
26769
+ "details.createdAt": "\u521B\u5EFA\u65F6\u95F4",
26770
+ "details.updatedAt": "\u66F4\u65B0\u65F6\u95F4",
26771
+ "details.startedAt": "\u5F00\u59CB\u65F6\u95F4",
26772
+ "details.finishedAt": "\u7ED3\u675F\u65F6\u95F4",
26773
+ "details.scheduledAt": "\u8BA1\u5212\u65F6\u95F4",
26774
+ "details.enabled": "\u81EA\u52A8\u8FD0\u884C",
26775
+ "details.scheduleRule": "\u8FD0\u884C\u89C4\u5219",
26776
+ "details.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
26777
+ "details.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
26778
+ "details.lastRun": "\u4E0A\u6B21\u8FD0\u884C",
26779
+ "details.nextRun": "\u4E0B\u6B21\u8FD0\u884C",
26780
+ "details.taskId": "\u6240\u5C5E\u4EFB\u52A1",
26781
+ "details.session": "OpenCode \u4F1A\u8BDD",
26782
+ "details.heartbeat": "\u6700\u8FD1\u5FC3\u8DF3",
26783
+ "details.process": "\u8FDB\u7A0B",
26784
+ "details.history": "\u6267\u884C\u5386\u53F2",
26785
+ "details.noHistory": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
26786
+ "details.none": "\u65E0",
26787
+ "details.default": "\u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4",
26788
+ "details.enabledYes": "\u5DF2\u542F\u7528",
26789
+ "details.enabledNo": "\u5DF2\u505C\u7528",
26624
26790
  "dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
26625
26791
  "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
26792
  "dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
@@ -26690,7 +26856,7 @@ var init_ui = __esm({
26690
26856
  "action.hideLogs": "Hide log",
26691
26857
  "action.save": "Save settings",
26692
26858
  "action.saveAndRestart": "Save and restart",
26693
- "action.copy": "Copy JSON",
26859
+ "action.copy": "Copy raw data",
26694
26860
  "action.close": "Close",
26695
26861
  "action.confirm": "Confirm",
26696
26862
  "action.clearDatabase": "Clear database",
@@ -26882,9 +27048,46 @@ var init_ui = __esm({
26882
27048
  "theme.light": "Light",
26883
27049
  "theme.dark": "Dark",
26884
27050
  "language.label": "Language",
26885
- "details.title": "Data details",
26886
- "details.subtitle": "Raw record (JSON)",
26887
- "details.copySuccess": "JSON copied",
27051
+ "details.title": "Details",
27052
+ "details.subtitle": "Key information is organized below; raw data is only for troubleshooting.",
27053
+ "details.taskTitle": "Task details",
27054
+ "details.runTitle": "Run details",
27055
+ "details.templateTitle": "Scheduled task details",
27056
+ "details.raw": "View raw data (JSON)",
27057
+ "details.copySuccess": "Raw data copied",
27058
+ "details.id": "ID",
27059
+ "details.project": "Project directory",
27060
+ "details.prompt": "Prompt",
27061
+ "details.result": "Result / failure reason",
27062
+ "details.category": "Category",
27063
+ "details.batch": "Batch",
27064
+ "details.dependency": "Dependency",
27065
+ "details.importance": "Importance",
27066
+ "details.urgency": "Urgency",
27067
+ "details.retryCount": "Retries used / allowed",
27068
+ "details.retryBackoff": "Retry delay",
27069
+ "details.timeout": "Run timeout",
27070
+ "details.createdAt": "Created",
27071
+ "details.updatedAt": "Updated",
27072
+ "details.startedAt": "Started",
27073
+ "details.finishedAt": "Finished",
27074
+ "details.scheduledAt": "Scheduled for",
27075
+ "details.enabled": "Automatic runs",
27076
+ "details.scheduleRule": "Schedule rule",
27077
+ "details.maxInstances": "Automatic scheduling limit",
27078
+ "details.maxRetries": "Failure retries",
27079
+ "details.lastRun": "Last run",
27080
+ "details.nextRun": "Next run",
27081
+ "details.taskId": "Task",
27082
+ "details.session": "OpenCode session",
27083
+ "details.heartbeat": "Latest heartbeat",
27084
+ "details.process": "Processes",
27085
+ "details.history": "Run history",
27086
+ "details.noHistory": "No runs yet",
27087
+ "details.none": "None",
27088
+ "details.default": "Use system default",
27089
+ "details.enabledYes": "Enabled",
27090
+ "details.enabledNo": "Disabled",
26888
27091
  "dialog.cancelTask": "Cancel task #{id}?",
26889
27092
  "dialog.cancelTaskBody": "A running task will terminate its process tree on the next worker poll.",
26890
27093
  "dialog.retryTask": "Retry task #{id}?",
@@ -27125,6 +27328,8 @@ var init_ui = __esm({
27125
27328
  .danger-card h2 .icon { width:17px; height:17px; }
27126
27329
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
27127
27330
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
27331
+ .run-log-row td { padding:0 16px 16px; background:color-mix(in srgb,var(--surface-2) 64%,var(--surface)); }
27332
+ .run-log-row .log-panel { margin:0; box-shadow:none; }
27128
27333
  .log-content { display:grid; gap:14px; padding:16px; }
27129
27334
  .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
27130
27335
  .run-command,.run-output,.run-error,.run-tools { min-width:0; }
@@ -27182,8 +27387,21 @@ var init_ui = __esm({
27182
27387
  .directory-item:active { transform:scale(.99); }
27183
27388
  .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
27184
27389
  .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; }
27390
+ .detail-view { display:grid; gap:16px; }
27391
+ .detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
27392
+ .detail-item { min-width:0; padding:12px 13px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); }
27393
+ .detail-item.wide { grid-column:1 / -1; }
27394
+ .detail-label { margin-bottom:5px; color:var(--text-3); font-size:10px; font-weight:750; letter-spacing:.045em; text-transform:uppercase; }
27395
+ .detail-value { color:var(--text); font-size:13px; line-height:1.55; overflow-wrap:anywhere; }
27396
+ .detail-value.mono { font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; }
27397
+ .detail-value.long { max-height:240px; margin:0; overflow:auto; white-space:pre-wrap; }
27398
+ .detail-history h3 { margin:0 0 8px; font-size:13px; }
27399
+ .detail-history-list { display:grid; gap:7px; }
27400
+ .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; }
27401
+ .detail-raw { padding-top:12px; border-top:1px solid var(--border); }
27402
+ .detail-raw summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
27403
+ .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);
27404
+ background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
27187
27405
  .confirm-copy { color:var(--text-2); margin:0; }
27188
27406
  .confirm-copy strong { display:block; margin-bottom:5px; color:var(--text); font-size:15px; }
27189
27407
  .danger-input { width:100%; height:40px; margin-top:14px; padding:0 12px; border:1px solid var(--border); border-radius:9px; outline:none;
@@ -27247,8 +27465,13 @@ var init_ui = __esm({
27247
27465
  .responsive-table td[data-primary]::before { display:none; }
27248
27466
  .responsive-table .task-prompt { max-width:100%; }
27249
27467
  .responsive-table .actions { justify-content:flex-start; }
27468
+ .responsive-table .run-log-row { padding:0; overflow:hidden; }
27469
+ .responsive-table .run-log-cell { display:block; padding:0; }
27470
+ .responsive-table .run-log-cell::before { display:none; }
27250
27471
  .project-grid { grid-template-columns:1fr; }
27251
27472
  .template-form-grid { grid-template-columns:1fr; }
27473
+ .detail-grid { grid-template-columns:1fr; }
27474
+ .detail-item.wide { grid-column:auto; }
27252
27475
  .form-field-wide { grid-column:auto; }
27253
27476
  .field-action { align-items:stretch; flex-direction:column; }
27254
27477
  .field-action .btn { width:100%; }
@@ -27568,7 +27791,7 @@ function renderRunLog(runId, taskName, log, locale) {
27568
27791
  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
27792
  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
27793
  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>`;
27794
+ 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
27795
  }
27573
27796
  function esc(value) {
27574
27797
  if (!value) return "";
@@ -28008,14 +28231,11 @@ var init_web = __esm({
28008
28231
  const total = Number(totalResult[0]?.count ?? 0);
28009
28232
  const totalPages = Math.max(1, Math.ceil(total / limit));
28010
28233
  if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
28011
- const logs = [];
28012
28234
  const rows = runs.map((run) => {
28013
28235
  const status = safeStatus(run.status);
28014
28236
  const resumable = isValidSessionId(run.sessionId);
28015
- if (run.log) {
28016
- logs.push(renderRunLog(run.id, run.taskName, run.log, locale));
28017
- }
28018
- return `<tr>
28237
+ const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale) : "";
28238
+ return `<tr class="run-summary-row">
28019
28239
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
28020
28240
  <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
28241
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
@@ -28026,7 +28246,7 @@ var init_web = __esm({
28026
28246
  <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
28247
  ${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
28028
28248
  ${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
28029
- </tr>`;
28249
+ </tr>${log ? `<tr id="log-${run.id}" class="run-log-row" hidden><td class="run-log-cell" colspan="8">${log}</td></tr>` : ""}`;
28030
28250
  }).join("");
28031
28251
  const body = `
28032
28252
  <div class="stats-grid">
@@ -28036,7 +28256,7 @@ var init_web = __esm({
28036
28256
  ${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
28037
28257
  </div>
28038
28258
  <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)}`;
28259
+ ${pagination(locale, "/runs", page, totalPages, total)}`;
28040
28260
  return c.html(renderLayout({ locale, activeTab: "runs", body }));
28041
28261
  });
28042
28262
  app.get("/system", async (c) => {
@@ -28143,7 +28363,11 @@ var init_web = __esm({
28143
28363
  const task = await TaskService.getById(id);
28144
28364
  if (!task) return c.json({ error: "not found" }, 404);
28145
28365
  const runs = await TaskRunService.listByTaskId(id);
28146
- return c.json({ ...task, _runs: runs });
28366
+ return c.json({
28367
+ ...task,
28368
+ _resultPresentation: task.resultLog ? presentRunLog(task.resultLog) : null,
28369
+ _runs: runs
28370
+ });
28147
28371
  });
28148
28372
  app.get("/api/runs/:id", async (c) => {
28149
28373
  const id = parsePositiveInteger2(c.req.param("id"));
@@ -28648,7 +28872,6 @@ init_db2();
28648
28872
  init_duration();
28649
28873
  init_pm2();
28650
28874
  init_config();
28651
- import { spawnSync as spawnSync4 } from "child_process";
28652
28875
 
28653
28876
  // src/cli/i18n.ts
28654
28877
  function requestedLanguage(argv) {
@@ -28861,6 +29084,118 @@ function parseTaskStatus(value) {
28861
29084
 
28862
29085
  // src/cli/index.ts
28863
29086
  init_update2();
29087
+
29088
+ // src/cli/doctor-smoke.ts
29089
+ init_task_service();
29090
+ init_task_run_service();
29091
+ import { randomUUID as randomUUID2 } from "crypto";
29092
+ import { resolve as resolve5 } from "path";
29093
+ function tail(value, maxLength = 2e3) {
29094
+ if (!value) return null;
29095
+ return value.length <= maxLength ? value : value.slice(-maxLength);
29096
+ }
29097
+ function extractOpenCodeText(log) {
29098
+ if (!log) return "";
29099
+ const parts = [];
29100
+ for (const line of log.split("\n")) {
29101
+ try {
29102
+ const parsed = JSON.parse(line);
29103
+ if (parsed.type === "text" && parsed.part?.type === "text" && typeof parsed.part.text === "string") {
29104
+ parts.push(parsed.part.text);
29105
+ }
29106
+ } catch {
29107
+ }
29108
+ }
29109
+ return parts.join("\n").trim();
29110
+ }
29111
+ async function runDoctorSmoke(options) {
29112
+ if (!options.agent.trim()) throw new Error("smoke agent \u4E0D\u80FD\u4E3A\u7A7A");
29113
+ if (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1e3 || options.timeoutMs > 6048e5) {
29114
+ throw new Error("smoke timeout \u5FC5\u987B\u662F 1 \u79D2\u5230 7 \u5929\u4E4B\u95F4\u7684\u6574\u6570\u6BEB\u79D2\u503C");
29115
+ }
29116
+ const cwd = resolve5(options.cwd);
29117
+ const marker = options.marker ?? `SUPERTASK_SMOKE_${randomUUID2().replaceAll("-", "").toUpperCase()}`;
29118
+ const model = options.model && options.model !== "default" ? options.model : void 0;
29119
+ const startedAt = Date.now();
29120
+ const task = await TaskService.add({
29121
+ name: "[doctor] Gateway real smoke test",
29122
+ agent: options.agent,
29123
+ model,
29124
+ prompt: `\u4E0D\u8981\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\u3002\u53EA\u56DE\u590D\u8FD9\u4E00\u884C\uFF1A${marker}`,
29125
+ cwd,
29126
+ category: "diagnostic",
29127
+ importance: 5,
29128
+ urgency: 5,
29129
+ batchId: `doctor-smoke-${randomUUID2()}`,
29130
+ maxRetries: 0,
29131
+ retryBackoffMs: 0,
29132
+ timeoutMs: options.timeoutMs
29133
+ });
29134
+ const deadline = startedAt + options.timeoutMs;
29135
+ const pollIntervalMs = Math.max(10, options.pollIntervalMs ?? 250);
29136
+ while (Date.now() < deadline) {
29137
+ const current2 = await TaskService.getById(task.id);
29138
+ const run2 = await TaskRunService.getLatestByTaskId(task.id);
29139
+ if (!current2) {
29140
+ return {
29141
+ ok: false,
29142
+ taskId: task.id,
29143
+ runId: run2?.id ?? null,
29144
+ status: "missing",
29145
+ agent: options.agent,
29146
+ model: model ?? null,
29147
+ cwd,
29148
+ durationMs: Date.now() - startedAt,
29149
+ error: "\u5192\u70DF\u4EFB\u52A1\u5728\u5B8C\u6210\u524D\u88AB\u5220\u9664"
29150
+ };
29151
+ }
29152
+ if (current2.status === "done") {
29153
+ const log = run2?.log ?? current2.resultLog;
29154
+ const markerObserved = extractOpenCodeText(log) === marker;
29155
+ return {
29156
+ ok: markerObserved,
29157
+ taskId: task.id,
29158
+ runId: run2?.id ?? null,
29159
+ status: current2.status,
29160
+ agent: options.agent,
29161
+ model: model ?? null,
29162
+ cwd,
29163
+ durationMs: Date.now() - startedAt,
29164
+ error: markerObserved ? null : "OpenCode \u5DF2\u9000\u51FA\u6210\u529F\uFF0C\u4F46\u6A21\u578B\u6587\u672C\u4E0D\u662F\u9884\u671F\u7684\u7CBE\u786E\u6807\u8BB0"
29165
+ };
29166
+ }
29167
+ if (current2.status === "dead_letter" || current2.status === "cancelled") {
29168
+ return {
29169
+ ok: false,
29170
+ taskId: task.id,
29171
+ runId: run2?.id ?? null,
29172
+ status: current2.status,
29173
+ agent: options.agent,
29174
+ model: model ?? null,
29175
+ cwd,
29176
+ durationMs: Date.now() - startedAt,
29177
+ error: tail(run2?.log ?? current2.resultLog) ?? `\u4EFB\u52A1\u8FDB\u5165 ${current2.status}`
29178
+ };
29179
+ }
29180
+ await Bun.sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
29181
+ }
29182
+ const current = await TaskService.getById(task.id);
29183
+ const run = await TaskRunService.getLatestByTaskId(task.id);
29184
+ await TaskService.cancel(task.id);
29185
+ return {
29186
+ ok: false,
29187
+ taskId: task.id,
29188
+ runId: run?.id ?? null,
29189
+ status: current?.status ?? "missing",
29190
+ agent: options.agent,
29191
+ model: model ?? null,
29192
+ cwd,
29193
+ durationMs: Date.now() - startedAt,
29194
+ error: `\u7B49\u5F85 Gateway \u6267\u884C\u8D85\u8FC7 ${options.timeoutMs}ms\uFF0C\u5192\u70DF\u4EFB\u52A1\u5DF2\u8BF7\u6C42\u53D6\u6D88`
29195
+ };
29196
+ }
29197
+
29198
+ // src/cli/index.ts
28864
29199
  var cliLocale = resolveCliLocale();
28865
29200
  var t2 = (zh, en) => cliText(cliLocale, zh, en);
28866
29201
  async function withDb(fn, formatError = (error) => JSON.stringify({
@@ -29194,25 +29529,15 @@ program2.command("config").description(t2("\u663E\u793A\u5F53\u524D\u914D\u7F6E"
29194
29529
  const cfg = loadConfig2();
29195
29530
  console.log(JSON.stringify(cfg, null, 2));
29196
29531
  });
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 () => {
29532
+ 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
29533
  const config = loadConfig();
29199
29534
  const database = DatabaseMaintenanceService.check();
29200
29535
  const legacyQuarantinedRuns = await TaskRunService.listLegacyQuarantinedRuns(
29201
29536
  config.watchdog.heartbeatTimeoutMs
29202
29537
  );
29203
- const gateway = getGatewayDiagnostic();
29538
+ const gateway = getGatewayDiagnostic({ probeOpenCode: true });
29204
29539
  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
- };
29540
+ const opencode = diagnoseOpenCodeRuntime();
29216
29541
  const plugin = getOpenCodePluginDiagnostic();
29217
29542
  let dashboard = {
29218
29543
  enabled: config.dashboard.enabled,
@@ -29284,6 +29609,12 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29284
29609
  "The current CLI/OpenCode database, config, or OpenCode executable scope does not match the PM2 Gateway"
29285
29610
  ));
29286
29611
  }
29612
+ if (gateway.processFound && gateway.gatewayOpenCode?.ok !== true) {
29613
+ warnings.push(t2(
29614
+ `PM2 \u4FDD\u5B58\u7684 Gateway \u73AF\u5883\u65E0\u6CD5\u6267\u884C OpenCode\uFF1A${gateway.gatewayOpenCode?.error ?? "\u65E0\u6CD5\u8BFB\u53D6\u8FD0\u884C\u73AF\u5883"}`,
29615
+ `The Gateway environment saved by PM2 cannot execute OpenCode: ${gateway.gatewayOpenCode?.error ?? "runtime unavailable"}`
29616
+ ));
29617
+ }
29287
29618
  for (const run of legacyQuarantinedRuns) {
29288
29619
  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
29620
  const cancel = run.taskStatus === "cancelled" ? "" : t2(`\u5148${cwdHint} supertask cancel --id ${run.taskId}\uFF1B`, `first${cwdHint}: supertask cancel --id ${run.taskId}; `);
@@ -29293,7 +29624,31 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29293
29624
  `Legacy quarantined run #${run.runId}: ${owner}${cancel}after confirming no OpenCode process remains, run supertask run abandon --id ${run.runId} --confirm ABANDON`
29294
29625
  ));
29295
29626
  }
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;
29627
+ let smoke = null;
29628
+ if (options.smoke) {
29629
+ const gatewayCanExecute = gateway.status === "online" && gateway.ready && gateway.scopeMatches && gateway.gatewayOpenCode?.ok === true;
29630
+ if (!gatewayCanExecute) {
29631
+ smoke = {
29632
+ ok: false,
29633
+ skipped: true,
29634
+ error: t2(
29635
+ "Gateway \u5C1A\u672A\u5C31\u7EEA\uFF0C\u6216\u5176 PM2 \u73AF\u5883\u65E0\u6CD5\u6267\u884C OpenCode\uFF1B\u5DF2\u8DF3\u8FC7\u771F\u5B9E\u4EFB\u52A1",
29636
+ "Gateway is not ready or its PM2 environment cannot execute OpenCode; the real task was skipped"
29637
+ )
29638
+ };
29639
+ } else {
29640
+ const smokeTimeoutMs = parseDuration(options.smokeTimeout);
29641
+ if (smokeTimeoutMs === null) throw new Error("smoke-timeout \u683C\u5F0F\u65E0\u6548");
29642
+ smoke = await runDoctorSmoke({
29643
+ agent: options.smokeAgent,
29644
+ model: options.smokeModel,
29645
+ cwd: options.smokeCwd ?? process.cwd(),
29646
+ timeoutMs: smokeTimeoutMs
29647
+ });
29648
+ }
29649
+ if (!smoke.ok) warnings.push(smoke.error ?? t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u5931\u8D25", "Real smoke task failed"));
29650
+ }
29651
+ 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
29652
  const report = {
29298
29653
  ok,
29299
29654
  packageVersion: packageVersion2,
@@ -29305,6 +29660,7 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29305
29660
  legacyQuarantinedRuns,
29306
29661
  gateway,
29307
29662
  dashboard,
29663
+ smoke,
29308
29664
  warnings
29309
29665
  };
29310
29666
  const json = options.json || !process.stdout.isTTY;
@@ -29314,10 +29670,15 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29314
29670
  const mark = (value) => value ? "\u2713" : "\u2717";
29315
29671
  console.log(`SuperTask doctor: ${ok ? t2("\u6B63\u5E38", "healthy") : t2("\u5F02\u5E38", "unhealthy")}`);
29316
29672
  console.log(`${mark(opencode.ok)} OpenCode ${opencode.version ?? opencode.error ?? t2("\u4E0D\u53EF\u7528", "unavailable")}`);
29673
+ 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
29674
  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
29675
  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
29676
  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
29677
  console.log(`${mark(dashboard.ok)} Dashboard ${dashboard.enabled ? dashboard.url : t2("\u5DF2\u7981\u7528", "disabled")}`);
29678
+ if (options.smoke) {
29679
+ 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");
29680
+ console.log(`${mark(smoke?.ok === true)} ${t2("\u771F\u5B9E Gateway \u5192\u70DF\u4EFB\u52A1", "Real Gateway smoke task")} ${smokeSummary}`);
29681
+ }
29321
29682
  for (const warning of warnings) console.log(`! ${warning}`);
29322
29683
  }
29323
29684
  if (!ok) process.exitCode = 1;