@dsh-workbench/shared 0.2.3 → 0.2.5

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.
@@ -16,7 +16,7 @@ export interface OpencliResult {
16
16
  readonly value: unknown;
17
17
  readonly at: number;
18
18
  }
19
- export type OpencliSession = 'persistent' | 'ephemeral';
19
+ export type OpencliSession = 'persistent' | 'ephemeral' | 'none';
20
20
  export declare function opencliCommandLine(command: string, argv: readonly string[], session?: OpencliSession): string;
21
21
  export interface Opencli {
22
22
  json(args: readonly string[], options?: {
package/dist/i18n/en.js CHANGED
@@ -29,20 +29,32 @@ export const en = {
29
29
  'session.new.label': 'Start a session in the current project',
30
30
  'session.empty': 'No sessions in this project yet.',
31
31
  'session.noProject': 'Add a project first.',
32
+ 'session.list.label': 'Sessions',
33
+ 'session.copy': 'Copy',
32
34
  'session.pinned': 'Pinned',
33
35
  'session.actions': 'Actions for {name}',
34
36
  'status.idle': 'Idle',
35
37
  'status.running': 'Running',
36
38
  'status.waitingApproval': 'Waiting for approval',
37
- 'status.planReview': 'Waiting for plan review',
39
+ 'status.planReview': 'Plan awaiting review',
38
40
  'status.waitingAnswer': 'Waiting for your answer',
41
+ 'status.compact.approval': 'Approval',
42
+ 'status.compact.planReview': 'Plan review',
43
+ 'status.compact.answer': 'Answer',
39
44
  'status.completed': 'Completed',
40
45
  'status.subagentsRunning.one': '{n} subagent running',
41
46
  'status.subagentsRunning.other': '{n} subagents running',
47
+ 'hover.copied': 'Copied',
42
48
  'time.now': 'just now',
43
49
  'time.minutes': '{n} min ago',
44
50
  'time.hours': '{n} h ago',
45
51
  'time.days': '{n} d ago',
52
+ 'time.ago': '{t} ago',
53
+ 'time.short.minutes': '{n}min',
54
+ 'time.short.hours': '{n}h',
55
+ 'time.short.days': '{n}d',
56
+ 'time.short.months': '{n}mo',
57
+ 'time.short.years': '{n}y',
46
58
  'panels.title': 'Panel modules',
47
59
  'panels.groupBoardless': 'No-board modules',
48
60
  'panels.groupPlugins': 'opencli plugins',
@@ -31,20 +31,32 @@ export declare const dictionaries: {
31
31
  readonly 'session.new.label': "在当前项目新建会话";
32
32
  readonly 'session.empty': "这个项目还没有会话。";
33
33
  readonly 'session.noProject': "先添加一个项目。";
34
+ readonly 'session.list.label': "会话";
35
+ readonly 'session.copy': "复制";
34
36
  readonly 'session.pinned': "已置顶";
35
37
  readonly 'session.actions': "{name} 的操作";
36
38
  readonly 'status.idle': "空闲";
37
- readonly 'status.running': "运行中";
38
- readonly 'status.waitingApproval': "等待授权";
39
- readonly 'status.planReview': "等待计划确认";
39
+ readonly 'status.running': "进行中";
40
+ readonly 'status.waitingApproval': "等待审批";
41
+ readonly 'status.planReview': "计划待审";
40
42
  readonly 'status.waitingAnswer': "等待回答";
43
+ readonly 'status.compact.approval': "待审批";
44
+ readonly 'status.compact.planReview': "计划待审";
45
+ readonly 'status.compact.answer': "待回答";
41
46
  readonly 'status.completed': "已完成";
42
47
  readonly 'status.subagentsRunning.one': "{n} 个子代理运行中";
43
48
  readonly 'status.subagentsRunning.other': "{n} 个子代理运行中";
49
+ readonly 'hover.copied': "已复制";
44
50
  readonly 'time.now': "刚刚";
45
51
  readonly 'time.minutes': "{n} 分钟前";
46
52
  readonly 'time.hours': "{n} 小时前";
47
53
  readonly 'time.days': "{n} 天前";
54
+ readonly 'time.ago': "{t}前";
55
+ readonly 'time.short.minutes': "{n}分钟";
56
+ readonly 'time.short.hours': "{n}小时";
57
+ readonly 'time.short.days': "{n}天";
58
+ readonly 'time.short.months': "{n}个月";
59
+ readonly 'time.short.years': "{n}年";
48
60
  readonly 'panels.title': "面板模块";
49
61
  readonly 'panels.groupBoardless': "无看板模块";
50
62
  readonly 'panels.groupPlugins': "opencli 插件";
@@ -346,7 +358,7 @@ export declare const dictionaries: {
346
358
  readonly 'panel.conversation': "对话";
347
359
  readonly 'panels.label': "全局面板";
348
360
  };
349
- en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.add.busy" | "project.add.failed" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.group.workspaces" | "project.group.workshop" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.groupBoardless" | "panels.groupPlugins" | "panels.market.defaultHint" | "panels.market.defaultLabel" | "panels.market.defaultLocked" | "panels.followAll" | "panels.followAllConfirmTitle" | "panels.followAllConfirmDetail" | "panels.followAllConfirmAck" | "panels.followAllConfirmYes" | "panels.followAllConfirmNo" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.notActive" | "panels.market.notUsable" | "panels.market.broken" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.needCommands" | "panels.market.localMark" | "panels.market.localHint" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.sectionPlugins" | "panels.market.moduleOutdated" | "panels.market.moduleRefused" | "panels.market.moduleRefusedDetail" | "panels.market.moduleRestartHint" | "panels.market.workbenchName" | "panels.market.workbenchLocal" | "panels.market.pluginKeptInUse" | "panels.market.pluginKeptUnsure" | "panels.market.pluginKeptHeld" | "panels.market.pluginKeptUnnamed" | "panels.market.pluginBroken" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNotWorkbenchModule" | "panels.market.add.problemNotModuleOrPlugin" | "panels.market.add.failureNotModuleOrPlugin" | "panels.market.add.installedPlugin" | "panels.market.add.pluginPreview" | "panels.market.add.pluginAlias" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureInstalled" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.classifying" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
361
+ en: Record<"time.now" | "time.ago" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.add.busy" | "project.add.failed" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.group.workspaces" | "project.group.workshop" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.list.label" | "session.copy" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.compact.approval" | "status.compact.planReview" | "status.compact.answer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "hover.copied" | "time.short.minutes" | "time.short.hours" | "time.short.days" | "time.short.months" | "time.short.years" | "panels.title" | "panels.groupBoardless" | "panels.groupPlugins" | "panels.market.defaultHint" | "panels.market.defaultLabel" | "panels.market.defaultLocked" | "panels.followAll" | "panels.followAllConfirmTitle" | "panels.followAllConfirmDetail" | "panels.followAllConfirmAck" | "panels.followAllConfirmYes" | "panels.followAllConfirmNo" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.notActive" | "panels.market.notUsable" | "panels.market.broken" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.needCommands" | "panels.market.localMark" | "panels.market.localHint" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.sectionPlugins" | "panels.market.moduleOutdated" | "panels.market.moduleRefused" | "panels.market.moduleRefusedDetail" | "panels.market.moduleRestartHint" | "panels.market.workbenchName" | "panels.market.workbenchLocal" | "panels.market.pluginKeptInUse" | "panels.market.pluginKeptUnsure" | "panels.market.pluginKeptHeld" | "panels.market.pluginKeptUnnamed" | "panels.market.pluginBroken" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNotWorkbenchModule" | "panels.market.add.problemNotModuleOrPlugin" | "panels.market.add.failureNotModuleOrPlugin" | "panels.market.add.installedPlugin" | "panels.market.add.pluginPreview" | "panels.market.add.pluginAlias" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureInstalled" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.classifying" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
350
362
  };
351
363
  export type { WorkbenchMessages };
352
364
  export interface Dictionaries {
package/dist/i18n/zh.d.ts CHANGED
@@ -29,20 +29,32 @@ export declare const zh: {
29
29
  readonly 'session.new.label': "在当前项目新建会话";
30
30
  readonly 'session.empty': "这个项目还没有会话。";
31
31
  readonly 'session.noProject': "先添加一个项目。";
32
+ readonly 'session.list.label': "会话";
33
+ readonly 'session.copy': "复制";
32
34
  readonly 'session.pinned': "已置顶";
33
35
  readonly 'session.actions': "{name} 的操作";
34
36
  readonly 'status.idle': "空闲";
35
- readonly 'status.running': "运行中";
36
- readonly 'status.waitingApproval': "等待授权";
37
- readonly 'status.planReview': "等待计划确认";
37
+ readonly 'status.running': "进行中";
38
+ readonly 'status.waitingApproval': "等待审批";
39
+ readonly 'status.planReview': "计划待审";
38
40
  readonly 'status.waitingAnswer': "等待回答";
41
+ readonly 'status.compact.approval': "待审批";
42
+ readonly 'status.compact.planReview': "计划待审";
43
+ readonly 'status.compact.answer': "待回答";
39
44
  readonly 'status.completed': "已完成";
40
45
  readonly 'status.subagentsRunning.one': "{n} 个子代理运行中";
41
46
  readonly 'status.subagentsRunning.other': "{n} 个子代理运行中";
47
+ readonly 'hover.copied': "已复制";
42
48
  readonly 'time.now': "刚刚";
43
49
  readonly 'time.minutes': "{n} 分钟前";
44
50
  readonly 'time.hours': "{n} 小时前";
45
51
  readonly 'time.days': "{n} 天前";
52
+ readonly 'time.ago': "{t}前";
53
+ readonly 'time.short.minutes': "{n}分钟";
54
+ readonly 'time.short.hours': "{n}小时";
55
+ readonly 'time.short.days': "{n}天";
56
+ readonly 'time.short.months': "{n}个月";
57
+ readonly 'time.short.years': "{n}年";
46
58
  readonly 'panels.title': "面板模块";
47
59
  readonly 'panels.groupBoardless': "无看板模块";
48
60
  readonly 'panels.groupPlugins': "opencli 插件";
package/dist/i18n/zh.js CHANGED
@@ -29,20 +29,32 @@ export const zh = {
29
29
  'session.new.label': '在当前项目新建会话',
30
30
  'session.empty': '这个项目还没有会话。',
31
31
  'session.noProject': '先添加一个项目。',
32
+ 'session.list.label': '会话',
33
+ 'session.copy': '复制',
32
34
  'session.pinned': '已置顶',
33
35
  'session.actions': '{name} 的操作',
34
36
  'status.idle': '空闲',
35
- 'status.running': '运行中',
36
- 'status.waitingApproval': '等待授权',
37
- 'status.planReview': '等待计划确认',
37
+ 'status.running': '进行中',
38
+ 'status.waitingApproval': '等待审批',
39
+ 'status.planReview': '计划待审',
38
40
  'status.waitingAnswer': '等待回答',
41
+ 'status.compact.approval': '待审批',
42
+ 'status.compact.planReview': '计划待审',
43
+ 'status.compact.answer': '待回答',
39
44
  'status.completed': '已完成',
40
45
  'status.subagentsRunning.one': '{n} 个子代理运行中',
41
46
  'status.subagentsRunning.other': '{n} 个子代理运行中',
47
+ 'hover.copied': '已复制',
42
48
  'time.now': '刚刚',
43
49
  'time.minutes': '{n} 分钟前',
44
50
  'time.hours': '{n} 小时前',
45
51
  'time.days': '{n} 天前',
52
+ 'time.ago': '{t}前',
53
+ 'time.short.minutes': '{n}分钟',
54
+ 'time.short.hours': '{n}小时',
55
+ 'time.short.days': '{n}天',
56
+ 'time.short.months': '{n}个月',
57
+ 'time.short.years': '{n}年',
46
58
  'panels.title': '面板模块',
47
59
  'panels.groupBoardless': '无看板模块',
48
60
  'panels.groupPlugins': 'opencli 插件',
package/dist/lib/text.js CHANGED
@@ -7,6 +7,23 @@ export function tailLines(text, lines = 6) {
7
7
  .split('\n');
8
8
  return all.slice(-lines).join('\n');
9
9
  }
10
+ function candidateStarts(raw) {
11
+ const starts = [];
12
+ for (let index = 0; index < raw.length; index += 1) {
13
+ const char = raw[index];
14
+ if (char !== '[' && char !== '{')
15
+ continue;
16
+ if (index === 0 || raw[index - 1] === '\n')
17
+ starts.push(index);
18
+ }
19
+ const earliest = [raw.indexOf('['), raw.indexOf('{')].filter((index) => index >= 0);
20
+ if (earliest.length > 0) {
21
+ const head = Math.min(...earliest);
22
+ if (!starts.includes(head))
23
+ starts.push(head);
24
+ }
25
+ return starts;
26
+ }
10
27
  export function parseJsonLoose(text) {
11
28
  const raw = String(text ?? '').trim();
12
29
  if (!raw)
@@ -15,21 +32,19 @@ export function parseJsonLoose(text) {
15
32
  return JSON.parse(raw);
16
33
  }
17
34
  catch {
18
- const starts = [raw.indexOf('['), raw.indexOf('{')].filter((index) => index >= 0);
19
- if (starts.length === 0)
20
- return undefined;
21
- const from = Math.min(...starts);
22
- const open = raw[from];
23
- const close = open === '[' ? ']' : '}';
24
- const to = raw.lastIndexOf(close);
25
- if (to <= from)
26
- return undefined;
27
- try {
28
- return JSON.parse(raw.slice(from, to + 1));
29
- }
30
- catch {
31
- return undefined;
35
+ for (const from of candidateStarts(raw)) {
36
+ const close = raw[from] === '[' ? ']' : '}';
37
+ const to = raw.lastIndexOf(close);
38
+ if (to <= from)
39
+ continue;
40
+ try {
41
+ return JSON.parse(raw.slice(from, to + 1));
42
+ }
43
+ catch {
44
+ continue;
45
+ }
32
46
  }
47
+ return undefined;
33
48
  }
34
49
  }
35
50
  const MARKDOWN_IMAGE = /!\[[^\]]*\]\([^)]*\)/g;
package/dist/lib/ui.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  export declare function cx(...parts: Array<string | false | null | undefined>): string;
2
2
  export type TimeTranslate = (key: string, params?: Record<string, unknown>) => string;
3
+ export type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years';
4
+ export interface RelativeTimeBucket {
5
+ readonly unit: RelativeTimeUnit;
6
+ readonly n: number;
7
+ }
8
+ export declare function relativeTimeBucket(epochMs: number, now?: number): RelativeTimeBucket | null;
9
+ export declare function relativeTimeCompact(epochMs: number, t: TimeTranslate, now?: number): string;
10
+ export declare function relativeTimeAgo(epochMs: number, t: TimeTranslate, now?: number): string;
3
11
  export declare function relativeTime(epochMs: number, t: TimeTranslate): string;
4
12
  export declare function shortenPath(path: unknown): string;
5
13
  export declare function parseLocalTimestamp(text: string | null | undefined): number;
package/dist/lib/ui.js CHANGED
@@ -2,6 +2,40 @@ export function cx(...parts) {
2
2
  return parts.filter(Boolean).join(' ');
3
3
  }
4
4
  const MINUTE = 60 * 1000;
5
+ const HOUR = 60 * MINUTE;
6
+ const DAY = 24 * HOUR;
7
+ const MONTH = 30 * DAY;
8
+ const YEAR = 365 * DAY;
9
+ export function relativeTimeBucket(epochMs, now = Date.now()) {
10
+ if (!Number.isFinite(epochMs) || !Number.isFinite(now))
11
+ return null;
12
+ const diff = Math.max(0, now - epochMs);
13
+ if (diff < MINUTE)
14
+ return { unit: 'now', n: 0 };
15
+ if (diff < HOUR)
16
+ return { unit: 'minutes', n: Math.floor(diff / MINUTE) };
17
+ if (diff < DAY)
18
+ return { unit: 'hours', n: Math.floor(diff / HOUR) };
19
+ if (diff < MONTH)
20
+ return { unit: 'days', n: Math.floor(diff / DAY) };
21
+ if (diff < YEAR)
22
+ return { unit: 'months', n: Math.floor(diff / MONTH) };
23
+ return { unit: 'years', n: Math.floor(diff / YEAR) };
24
+ }
25
+ function bucketLabel(bucket, t) {
26
+ return bucket.unit === 'now' ? t('time.now') : t(`time.short.${bucket.unit}`, { n: bucket.n });
27
+ }
28
+ export function relativeTimeCompact(epochMs, t, now) {
29
+ const bucket = relativeTimeBucket(epochMs, now);
30
+ return bucket === null ? '' : bucketLabel(bucket, t);
31
+ }
32
+ export function relativeTimeAgo(epochMs, t, now) {
33
+ const bucket = relativeTimeBucket(epochMs, now);
34
+ if (bucket === null)
35
+ return '';
36
+ const label = bucketLabel(bucket, t);
37
+ return bucket.unit === 'now' ? label : t('time.ago', { t: label });
38
+ }
5
39
  export function relativeTime(epochMs, t) {
6
40
  if (!Number.isFinite(epochMs))
7
41
  return '';
@@ -25,6 +25,7 @@ export interface OpencliCall {
25
25
  args: readonly string[];
26
26
  key?: string;
27
27
  workdir?: string;
28
+ session?: string;
28
29
  refresh?: boolean;
29
30
  }
30
31
  export declare function createFakeOpencli(replies?: Array<unknown | Error> | ((call: OpencliCall, index: number) => unknown | Error)): {
@@ -47,6 +47,7 @@ export function createFakeOpencli(replies = []) {
47
47
  args,
48
48
  ...(options.key === undefined ? {} : { key: options.key }),
49
49
  ...(options.workdir === undefined ? {} : { workdir: options.workdir }),
50
+ ...(options.session === undefined ? {} : { session: options.session }),
50
51
  };
51
52
  calls.push(call);
52
53
  const index = calls.length - 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@ export interface OpencliResult {
36
36
  readonly at: number;
37
37
  }
38
38
 
39
- export type OpencliSession = 'persistent' | 'ephemeral';
39
+ export type OpencliSession = 'persistent' | 'ephemeral' | 'none';
40
40
 
41
41
  export function opencliCommandLine(
42
42
  command: string,
package/src/i18n/en.ts CHANGED
@@ -32,21 +32,33 @@ export const en: Record<keyof WorkbenchMessages, string> = {
32
32
  'session.new.label': 'Start a session in the current project',
33
33
  'session.empty': 'No sessions in this project yet.',
34
34
  'session.noProject': 'Add a project first.',
35
+ 'session.list.label': 'Sessions',
36
+ 'session.copy': 'Copy',
35
37
  'session.pinned': 'Pinned',
36
38
  'session.actions': 'Actions for {name}',
37
39
  'status.idle': 'Idle',
38
40
  'status.running': 'Running',
39
41
  'status.waitingApproval': 'Waiting for approval',
40
- 'status.planReview': 'Waiting for plan review',
42
+ 'status.planReview': 'Plan awaiting review',
41
43
  'status.waitingAnswer': 'Waiting for your answer',
44
+ 'status.compact.approval': 'Approval',
45
+ 'status.compact.planReview': 'Plan review',
46
+ 'status.compact.answer': 'Answer',
42
47
  'status.completed': 'Completed',
43
48
  'status.subagentsRunning.one': '{n} subagent running',
44
49
  'status.subagentsRunning.other': '{n} subagents running',
45
50
 
51
+ 'hover.copied': 'Copied',
46
52
  'time.now': 'just now',
47
53
  'time.minutes': '{n} min ago',
48
54
  'time.hours': '{n} h ago',
49
55
  'time.days': '{n} d ago',
56
+ 'time.ago': '{t} ago',
57
+ 'time.short.minutes': '{n}min',
58
+ 'time.short.hours': '{n}h',
59
+ 'time.short.days': '{n}d',
60
+ 'time.short.months': '{n}mo',
61
+ 'time.short.years': '{n}y',
50
62
 
51
63
  'panels.title': 'Panel modules',
52
64
  'panels.groupBoardless': 'No-board modules',
package/src/i18n/zh.ts CHANGED
@@ -30,21 +30,33 @@ export const zh = {
30
30
  'session.new.label': '在当前项目新建会话',
31
31
  'session.empty': '这个项目还没有会话。',
32
32
  'session.noProject': '先添加一个项目。',
33
+ 'session.list.label': '会话',
34
+ 'session.copy': '复制',
33
35
  'session.pinned': '已置顶',
34
36
  'session.actions': '{name} 的操作',
35
37
  'status.idle': '空闲',
36
- 'status.running': '运行中',
37
- 'status.waitingApproval': '等待授权',
38
- 'status.planReview': '等待计划确认',
38
+ 'status.running': '进行中',
39
+ 'status.waitingApproval': '等待审批',
40
+ 'status.planReview': '计划待审',
39
41
  'status.waitingAnswer': '等待回答',
42
+ 'status.compact.approval': '待审批',
43
+ 'status.compact.planReview': '计划待审',
44
+ 'status.compact.answer': '待回答',
40
45
  'status.completed': '已完成',
41
46
  'status.subagentsRunning.one': '{n} 个子代理运行中',
42
47
  'status.subagentsRunning.other': '{n} 个子代理运行中',
43
48
 
49
+ 'hover.copied': '已复制',
44
50
  'time.now': '刚刚',
45
51
  'time.minutes': '{n} 分钟前',
46
52
  'time.hours': '{n} 小时前',
47
53
  'time.days': '{n} 天前',
54
+ 'time.ago': '{t}前',
55
+ 'time.short.minutes': '{n}分钟',
56
+ 'time.short.hours': '{n}小时',
57
+ 'time.short.days': '{n}天',
58
+ 'time.short.months': '{n}个月',
59
+ 'time.short.years': '{n}年',
48
60
 
49
61
  'panels.title': '面板模块',
50
62
  'panels.groupBoardless': '无看板模块',
@@ -58,6 +58,28 @@ describe('parseJsonLoose', () => {
58
58
  expect(parseJsonLoose('[1,2]\n完成')).toEqual([1, 2]);
59
59
  });
60
60
 
61
+ it('★ 噪音行自己带方括号时也不认错起点(真机:pagoda 的调试命令先打两行带 [] 的话)', () => {
62
+ const stdout = [
63
+ '数据源 [dify_dataset] 调试成功',
64
+ '结构类型: 3, 字段: code, name',
65
+ '[',
66
+ ' {',
67
+ ' "code": "dify_dataset",',
68
+ ' "success": "成功",',
69
+ ' "detail": "structure=3, keys=[code, name], fields=[{code=知识库ID}]"',
70
+ ' }',
71
+ ']',
72
+ ].join('\n');
73
+
74
+ expect(parseJsonLoose(stdout)).toEqual([
75
+ {
76
+ code: 'dify_dataset',
77
+ success: '成功',
78
+ detail: 'structure=3, keys=[code, name], fields=[{code=知识库ID}]',
79
+ },
80
+ ]);
81
+ });
82
+
61
83
  it('解析不出来给 undefined,让调用方报 coding-bad-output', () => {
62
84
  expect(parseJsonLoose('')).toBeUndefined();
63
85
  expect(parseJsonLoose('not json at all')).toBeUndefined();
package/src/lib/text.ts CHANGED
@@ -9,24 +9,38 @@ export function tailLines(text: string | null | undefined, lines = 6): string {
9
9
  return all.slice(-lines).join('\n');
10
10
  }
11
11
 
12
+ function candidateStarts(raw: string): number[] {
13
+ const starts: number[] = [];
14
+ for (let index = 0; index < raw.length; index += 1) {
15
+ const char = raw[index];
16
+ if (char !== '[' && char !== '{') continue;
17
+ if (index === 0 || raw[index - 1] === '\n') starts.push(index);
18
+ }
19
+ const earliest = [raw.indexOf('['), raw.indexOf('{')].filter((index) => index >= 0);
20
+ if (earliest.length > 0) {
21
+ const head = Math.min(...earliest);
22
+ if (!starts.includes(head)) starts.push(head);
23
+ }
24
+ return starts;
25
+ }
26
+
12
27
  export function parseJsonLoose(text: string | null | undefined): unknown {
13
28
  const raw = String(text ?? '').trim();
14
29
  if (!raw) return undefined;
15
30
  try {
16
31
  return JSON.parse(raw) as unknown;
17
32
  } catch {
18
- const starts = [raw.indexOf('['), raw.indexOf('{')].filter((index) => index >= 0);
19
- if (starts.length === 0) return undefined;
20
- const from = Math.min(...starts);
21
- const open = raw[from];
22
- const close = open === '[' ? ']' : '}';
23
- const to = raw.lastIndexOf(close);
24
- if (to <= from) return undefined;
25
- try {
26
- return JSON.parse(raw.slice(from, to + 1)) as unknown;
27
- } catch {
28
- return undefined;
33
+ for (const from of candidateStarts(raw)) {
34
+ const close = raw[from] === '[' ? ']' : '}';
35
+ const to = raw.lastIndexOf(close);
36
+ if (to <= from) continue;
37
+ try {
38
+ return JSON.parse(raw.slice(from, to + 1)) as unknown;
39
+ } catch {
40
+ continue;
41
+ }
29
42
  }
43
+ return undefined;
30
44
  }
31
45
  }
32
46
 
@@ -3,12 +3,17 @@ import {
3
3
  cx,
4
4
  parseLocalTimestamp,
5
5
  relativeTime,
6
+ relativeTimeAgo,
7
+ relativeTimeBucket,
8
+ relativeTimeCompact,
6
9
  shortenPath,
7
10
  type TimeTranslate,
8
11
  } from './ui.js';
9
12
 
10
- const t: TimeTranslate = (key, params) =>
11
- params === undefined ? key : `${key}:${String(params.n ?? '')}`;
13
+ const t: TimeTranslate = (key, params) => {
14
+ if (params === undefined) return key;
15
+ return `${key}:${String(params.n ?? params.t ?? '')}`;
16
+ };
12
17
 
13
18
  const NOW = Date.parse('2026-09-27T12:00:00.000Z');
14
19
 
@@ -59,6 +64,48 @@ describe('relativeTime', () => {
59
64
  });
60
65
  });
61
66
 
67
+ describe('relativeTimeBucket / relativeTimeCompact / relativeTimeAgo(会话行那一列)', () => {
68
+ const at = (ago: number): number => NOW - ago;
69
+ const MIN = 60_000;
70
+ const HOUR = 3_600_000;
71
+ const DAY = 86_400_000;
72
+
73
+ it('★ 档位与 dsh 的 relative-time 逐条一致:now / minutes / hours / days / months / years', () => {
74
+ expect(relativeTimeBucket(at(30_000), NOW)).toEqual({ unit: 'now', n: 0 });
75
+ expect(relativeTimeBucket(at(3 * MIN), NOW)).toEqual({ unit: 'minutes', n: 3 });
76
+ expect(relativeTimeBucket(at(5 * HOUR), NOW)).toEqual({ unit: 'hours', n: 5 });
77
+ expect(relativeTimeBucket(at(3 * DAY), NOW)).toEqual({ unit: 'days', n: 3 });
78
+ expect(relativeTimeBucket(at(40 * DAY), NOW)).toEqual({ unit: 'months', n: 1 });
79
+ expect(relativeTimeBucket(at(400 * DAY), NOW)).toEqual({ unit: 'years', n: 1 });
80
+ });
81
+
82
+ it('★ 边界在 30 天与 365 天那一侧(dsh 的分档,不是我们的 7 天)', () => {
83
+ expect(relativeTimeBucket(at(29 * DAY), NOW)?.unit).toBe('days');
84
+ expect(relativeTimeBucket(at(30 * DAY), NOW)?.unit).toBe('months');
85
+ expect(relativeTimeBucket(at(364 * DAY), NOW)?.unit).toBe('months');
86
+ expect(relativeTimeBucket(at(365 * DAY), NOW)?.unit).toBe('years');
87
+ });
88
+
89
+ it('未来时间钳到 now(不当成负档)', () => {
90
+ expect(relativeTimeBucket(NOW + MIN, NOW)).toEqual({ unit: 'now', n: 0 });
91
+ });
92
+
93
+ it('非法时间戳:桶给 null、两句都给空串', () => {
94
+ expect(relativeTimeBucket(Number.NaN, NOW)).toBeNull();
95
+ expect(relativeTimeBucket(NOW, Number.NaN)).toBeNull();
96
+ expect(relativeTimeCompact(Number.NaN, t, NOW)).toBe('');
97
+ expect(relativeTimeAgo(Number.NaN, t, NOW)).toBe('');
98
+ });
99
+
100
+ it('★ 行里是紧凑写法,悬停卡才补「前」;now 那一档两处都不加', () => {
101
+ expect(relativeTimeCompact(at(3 * MIN), t, NOW)).toBe('time.short.minutes:3');
102
+ expect(relativeTimeCompact(at(40 * DAY), t, NOW)).toBe('time.short.months:1');
103
+ expect(relativeTimeAgo(at(3 * MIN), t, NOW)).toBe('time.ago:time.short.minutes:3');
104
+ expect(relativeTimeCompact(at(30_000), t, NOW)).toBe('time.now');
105
+ expect(relativeTimeAgo(at(30_000), t, NOW)).toBe('time.now');
106
+ });
107
+ });
108
+
62
109
  describe('shortenPath', () => {
63
110
  it('把用户主目录缩成 ~', () => {
64
111
  expect(shortenPath('/Users/you/dev/app')).toBe('~/dev/app');
package/src/lib/ui.ts CHANGED
@@ -3,9 +3,47 @@ export function cx(...parts: Array<string | false | null | undefined>): string {
3
3
  }
4
4
 
5
5
  const MINUTE = 60 * 1000;
6
+ const HOUR = 60 * MINUTE;
7
+ const DAY = 24 * HOUR;
8
+ const MONTH = 30 * DAY;
9
+ const YEAR = 365 * DAY;
6
10
 
7
11
  export type TimeTranslate = (key: string, params?: Record<string, unknown>) => string;
8
12
 
13
+ export type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years';
14
+
15
+ export interface RelativeTimeBucket {
16
+ readonly unit: RelativeTimeUnit;
17
+ readonly n: number;
18
+ }
19
+
20
+ export function relativeTimeBucket(epochMs: number, now: number = Date.now()): RelativeTimeBucket | null {
21
+ if (!Number.isFinite(epochMs) || !Number.isFinite(now)) return null;
22
+ const diff = Math.max(0, now - epochMs);
23
+ if (diff < MINUTE) return { unit: 'now', n: 0 };
24
+ if (diff < HOUR) return { unit: 'minutes', n: Math.floor(diff / MINUTE) };
25
+ if (diff < DAY) return { unit: 'hours', n: Math.floor(diff / HOUR) };
26
+ if (diff < MONTH) return { unit: 'days', n: Math.floor(diff / DAY) };
27
+ if (diff < YEAR) return { unit: 'months', n: Math.floor(diff / MONTH) };
28
+ return { unit: 'years', n: Math.floor(diff / YEAR) };
29
+ }
30
+
31
+ function bucketLabel(bucket: RelativeTimeBucket, t: TimeTranslate): string {
32
+ return bucket.unit === 'now' ? t('time.now') : t(`time.short.${bucket.unit}`, { n: bucket.n });
33
+ }
34
+
35
+ export function relativeTimeCompact(epochMs: number, t: TimeTranslate, now?: number): string {
36
+ const bucket = relativeTimeBucket(epochMs, now);
37
+ return bucket === null ? '' : bucketLabel(bucket, t);
38
+ }
39
+
40
+ export function relativeTimeAgo(epochMs: number, t: TimeTranslate, now?: number): string {
41
+ const bucket = relativeTimeBucket(epochMs, now);
42
+ if (bucket === null) return '';
43
+ const label = bucketLabel(bucket, t);
44
+ return bucket.unit === 'now' ? label : t('time.ago', { t: label });
45
+ }
46
+
9
47
  export function relativeTime(epochMs: number, t: TimeTranslate): string {
10
48
  if (!Number.isFinite(epochMs)) return '';
11
49
  const diff = Date.now() - epochMs;
@@ -70,6 +70,7 @@ export interface OpencliCall {
70
70
  args: readonly string[];
71
71
  key?: string;
72
72
  workdir?: string;
73
+ session?: string;
73
74
  refresh?: boolean;
74
75
  }
75
76
 
@@ -84,6 +85,7 @@ export function createFakeOpencli(
84
85
  args,
85
86
  ...(options.key === undefined ? {} : { key: options.key }),
86
87
  ...(options.workdir === undefined ? {} : { workdir: options.workdir }),
88
+ ...(options.session === undefined ? {} : { session: options.session }),
87
89
  };
88
90
  calls.push(call);
89
91
  const index = calls.length - 1;