oh-my-opencode-slim 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -13
- package/dist/cli/index.js +8 -3
- package/dist/cli/install.d.ts +1 -1
- package/dist/cli/providers.d.ts +4 -4
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/council-schema.d.ts +2 -2
- package/dist/config/schema.d.ts +8 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +1 -1
- package/dist/hooks/task-session-manager/index.d.ts +36 -0
- package/dist/index.js +654 -53
- package/dist/multiplexer/session-manager.d.ts +4 -0
- package/dist/tools/council.d.ts +2 -2
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/session-manager.d.ts +38 -0
- package/dist/utils/session.d.ts +4 -4
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/oh-my-opencode-slim.schema.json +15 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18302,7 +18302,7 @@ var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
|
|
|
18302
18302
|
var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
|
|
18303
18303
|
var DEFAULT_MODELS = {
|
|
18304
18304
|
orchestrator: undefined,
|
|
18305
|
-
oracle: "openai/gpt-5.
|
|
18305
|
+
oracle: "openai/gpt-5.5",
|
|
18306
18306
|
librarian: "openai/gpt-5.4-mini",
|
|
18307
18307
|
explorer: "openai/gpt-5.4-mini",
|
|
18308
18308
|
designer: "openai/gpt-5.4-mini",
|
|
@@ -18316,7 +18316,7 @@ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
|
18316
18316
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
18317
18317
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
18318
18318
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Recall the workflow rules:
|
|
18319
|
-
Understand → choose the best parallelized path based on your agents delegation rules →
|
|
18319
|
+
Understand → choose the best parallelized path based on your capabilities and agents delegation rules → execute → verify.
|
|
18320
18320
|
If delegating, launch the specialist in the same turn you mention it !END!`;
|
|
18321
18321
|
var TMUX_SPAWN_DELAY_MS = 500;
|
|
18322
18322
|
var COUNCILLOR_STAGGER_MS = 250;
|
|
@@ -18525,6 +18525,9 @@ var InterviewConfigSchema = z2.object({
|
|
|
18525
18525
|
port: z2.number().int().min(0).max(65535).default(0),
|
|
18526
18526
|
dashboard: z2.boolean().default(false)
|
|
18527
18527
|
});
|
|
18528
|
+
var SessionManagerConfigSchema = z2.object({
|
|
18529
|
+
maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2)
|
|
18530
|
+
});
|
|
18528
18531
|
var TodoContinuationConfigSchema = z2.object({
|
|
18529
18532
|
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
18530
18533
|
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
@@ -18566,6 +18569,7 @@ var PluginConfigSchema = z2.object({
|
|
|
18566
18569
|
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
18567
18570
|
balanceProviderUsage: z2.boolean().optional(),
|
|
18568
18571
|
showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
|
|
18572
|
+
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18569
18573
|
manualPlan: ManualPlanSchema.optional(),
|
|
18570
18574
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
18571
18575
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
@@ -18575,6 +18579,7 @@ var PluginConfigSchema = z2.object({
|
|
|
18575
18579
|
tmux: TmuxConfigSchema.optional(),
|
|
18576
18580
|
websearch: WebsearchConfigSchema.optional(),
|
|
18577
18581
|
interview: InterviewConfigSchema.optional(),
|
|
18582
|
+
sessionManager: SessionManagerConfigSchema.optional(),
|
|
18578
18583
|
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
18579
18584
|
fallback: FailoverConfigSchema.optional(),
|
|
18580
18585
|
council: CouncilConfigSchema.optional()
|
|
@@ -18660,6 +18665,7 @@ function loadPluginConfig(directory) {
|
|
|
18660
18665
|
tmux: deepMerge(config.tmux, projectConfig.tmux),
|
|
18661
18666
|
multiplexer: deepMerge(config.multiplexer, projectConfig.multiplexer),
|
|
18662
18667
|
interview: deepMerge(config.interview, projectConfig.interview),
|
|
18668
|
+
sessionManager: deepMerge(config.sessionManager, projectConfig.sessionManager),
|
|
18663
18669
|
fallback: deepMerge(config.fallback, projectConfig.fallback),
|
|
18664
18670
|
council: deepMerge(config.council, projectConfig.council)
|
|
18665
18671
|
};
|
|
@@ -18931,6 +18937,12 @@ ${enabledParallelExamples}
|
|
|
18931
18937
|
|
|
18932
18938
|
Balance: respect dependencies, avoid parallelizing what must be sequential.
|
|
18933
18939
|
|
|
18940
|
+
### OpenCode subagent execution model
|
|
18941
|
+
- A delegated specialist runs in a separate child session.
|
|
18942
|
+
- Delegation is blocking for the parent at that point: send work out, then continue that line after results return.
|
|
18943
|
+
- Parallel delegation means launching multiple independent child-session branches.
|
|
18944
|
+
- Only parallelize branches that are truly independent; reconcile dependent steps after delegated results come back.
|
|
18945
|
+
|
|
18934
18946
|
## 5. Execute
|
|
18935
18947
|
1. Break complex tasks into todos
|
|
18936
18948
|
2. Fire parallel research/implementation
|
|
@@ -18938,6 +18950,12 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
|
|
|
18938
18950
|
4. Integrate results
|
|
18939
18951
|
5. Adjust if needed
|
|
18940
18952
|
|
|
18953
|
+
### Session Reuse
|
|
18954
|
+
- Reuse an available specialist session only for clear follow-up work on the same thread.
|
|
18955
|
+
- Prefer a fresh session for unrelated work, even with the same specialist.
|
|
18956
|
+
- If multiple remembered sessions fit, prefer the most recently used matching session.
|
|
18957
|
+
- If reuse is unclear, start a fresh session.
|
|
18958
|
+
|
|
18941
18959
|
### Auto-Continue
|
|
18942
18960
|
When working through multi-step tasks, consider enabling auto-continue to avoid stopping between batches:
|
|
18943
18961
|
- **Enable when:** User requests autonomous/batch work, or you create 4+ todos in a session
|
|
@@ -22057,6 +22075,185 @@ function hasInternalInitiatorMarker(part) {
|
|
|
22057
22075
|
}
|
|
22058
22076
|
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
22059
22077
|
}
|
|
22078
|
+
// src/utils/session-manager.ts
|
|
22079
|
+
function aliasPrefix(agentType) {
|
|
22080
|
+
switch (agentType) {
|
|
22081
|
+
case "explorer":
|
|
22082
|
+
return "exp";
|
|
22083
|
+
case "librarian":
|
|
22084
|
+
return "lib";
|
|
22085
|
+
case "oracle":
|
|
22086
|
+
return "ora";
|
|
22087
|
+
case "designer":
|
|
22088
|
+
return "des";
|
|
22089
|
+
case "fixer":
|
|
22090
|
+
return "fix";
|
|
22091
|
+
case "observer":
|
|
22092
|
+
return "obs";
|
|
22093
|
+
case "council":
|
|
22094
|
+
return "cnc";
|
|
22095
|
+
case "councillor":
|
|
22096
|
+
return "clr";
|
|
22097
|
+
case "orchestrator":
|
|
22098
|
+
return "orc";
|
|
22099
|
+
}
|
|
22100
|
+
}
|
|
22101
|
+
function normalizeWhitespace(value) {
|
|
22102
|
+
return value.replace(/\s+/g, " ").trim();
|
|
22103
|
+
}
|
|
22104
|
+
function deriveTaskSessionLabel(input) {
|
|
22105
|
+
const preferred = normalizeWhitespace(input.description ?? "");
|
|
22106
|
+
if (preferred) {
|
|
22107
|
+
return preferred.slice(0, 48);
|
|
22108
|
+
}
|
|
22109
|
+
const firstPromptLine = (input.prompt ?? "").split(/\r?\n/).map((line) => normalizeWhitespace(line)).find(Boolean);
|
|
22110
|
+
if (firstPromptLine) {
|
|
22111
|
+
return firstPromptLine.slice(0, 48);
|
|
22112
|
+
}
|
|
22113
|
+
return `recent ${input.agentType} task`;
|
|
22114
|
+
}
|
|
22115
|
+
|
|
22116
|
+
class SessionManager {
|
|
22117
|
+
maxSessionsPerAgent;
|
|
22118
|
+
sessionsByParent = new Map;
|
|
22119
|
+
nextAliasIndexByParent = new Map;
|
|
22120
|
+
orderCounter = 0;
|
|
22121
|
+
constructor(maxSessionsPerAgent) {
|
|
22122
|
+
this.maxSessionsPerAgent = maxSessionsPerAgent;
|
|
22123
|
+
}
|
|
22124
|
+
remember(input) {
|
|
22125
|
+
const now = this.nextOrder();
|
|
22126
|
+
const group = this.getAgentGroup(input.parentSessionId, input.agentType, true);
|
|
22127
|
+
if (!group) {
|
|
22128
|
+
throw new Error("Failed to initialize session group");
|
|
22129
|
+
}
|
|
22130
|
+
const existing = group.find((entry) => entry.taskId === input.taskId);
|
|
22131
|
+
if (existing) {
|
|
22132
|
+
existing.label = input.label;
|
|
22133
|
+
existing.lastUsedAt = this.nextOrder();
|
|
22134
|
+
return existing;
|
|
22135
|
+
}
|
|
22136
|
+
const remembered = {
|
|
22137
|
+
alias: this.nextAlias(input.parentSessionId, input.agentType),
|
|
22138
|
+
taskId: input.taskId,
|
|
22139
|
+
agentType: input.agentType,
|
|
22140
|
+
label: input.label,
|
|
22141
|
+
createdAt: now,
|
|
22142
|
+
lastUsedAt: now
|
|
22143
|
+
};
|
|
22144
|
+
group.push(remembered);
|
|
22145
|
+
this.trimGroup(group);
|
|
22146
|
+
return remembered;
|
|
22147
|
+
}
|
|
22148
|
+
markUsed(parentSessionId, agentType, key) {
|
|
22149
|
+
const group = this.getAgentGroup(parentSessionId, agentType, false);
|
|
22150
|
+
const match = group?.find((entry) => entry.alias === key || entry.taskId === key);
|
|
22151
|
+
if (match) {
|
|
22152
|
+
match.lastUsedAt = this.nextOrder();
|
|
22153
|
+
}
|
|
22154
|
+
}
|
|
22155
|
+
resolve(parentSessionId, agentType, key) {
|
|
22156
|
+
const group = this.getAgentGroup(parentSessionId, agentType, false);
|
|
22157
|
+
return group?.find((entry) => entry.alias === key || entry.taskId === key);
|
|
22158
|
+
}
|
|
22159
|
+
drop(parentSessionId, agentType, key) {
|
|
22160
|
+
const group = this.getAgentGroup(parentSessionId, agentType, false);
|
|
22161
|
+
if (!group)
|
|
22162
|
+
return;
|
|
22163
|
+
const next = group.filter((entry) => entry.alias !== key && entry.taskId !== key);
|
|
22164
|
+
this.setAgentGroup(parentSessionId, agentType, next);
|
|
22165
|
+
}
|
|
22166
|
+
dropTask(taskId) {
|
|
22167
|
+
for (const [parentSessionId, groups] of this.sessionsByParent.entries()) {
|
|
22168
|
+
for (const [agentType, group] of groups.entries()) {
|
|
22169
|
+
const next = group.filter((entry) => entry.taskId !== taskId);
|
|
22170
|
+
this.setAgentGroup(parentSessionId, agentType, next);
|
|
22171
|
+
}
|
|
22172
|
+
}
|
|
22173
|
+
}
|
|
22174
|
+
clearParent(parentSessionId) {
|
|
22175
|
+
this.sessionsByParent.delete(parentSessionId);
|
|
22176
|
+
this.nextAliasIndexByParent.delete(parentSessionId);
|
|
22177
|
+
}
|
|
22178
|
+
formatForPrompt(parentSessionId) {
|
|
22179
|
+
const groups = this.sessionsByParent.get(parentSessionId);
|
|
22180
|
+
if (!groups || groups.size === 0)
|
|
22181
|
+
return;
|
|
22182
|
+
const lines = [...groups.entries()].map(([agentType, entries]) => [
|
|
22183
|
+
agentType,
|
|
22184
|
+
[...entries].sort((a, b) => b.lastUsedAt - a.lastUsedAt)
|
|
22185
|
+
]).filter(([, entries]) => entries.length > 0).sort((a, b) => b[1][0].lastUsedAt - a[1][0].lastUsedAt).map(([agentType, entries]) => `- ${agentType}: ${entries.map((entry) => `${entry.alias} ${entry.label}`).join("; ")}`);
|
|
22186
|
+
if (lines.length === 0)
|
|
22187
|
+
return;
|
|
22188
|
+
return [
|
|
22189
|
+
"### Resumable Sessions",
|
|
22190
|
+
"Reuse only for clear continuation of the same thread. Otherwise start fresh.",
|
|
22191
|
+
"",
|
|
22192
|
+
...lines
|
|
22193
|
+
].join(`
|
|
22194
|
+
`);
|
|
22195
|
+
}
|
|
22196
|
+
getAgentGroup(parentSessionId, agentType, create) {
|
|
22197
|
+
let groups = this.sessionsByParent.get(parentSessionId);
|
|
22198
|
+
if (!groups && create) {
|
|
22199
|
+
groups = new Map;
|
|
22200
|
+
this.sessionsByParent.set(parentSessionId, groups);
|
|
22201
|
+
}
|
|
22202
|
+
let group = groups?.get(agentType);
|
|
22203
|
+
if (!group && create && groups) {
|
|
22204
|
+
group = [];
|
|
22205
|
+
groups.set(agentType, group);
|
|
22206
|
+
}
|
|
22207
|
+
return group;
|
|
22208
|
+
}
|
|
22209
|
+
setAgentGroup(parentSessionId, agentType, entries) {
|
|
22210
|
+
const groups = this.sessionsByParent.get(parentSessionId);
|
|
22211
|
+
if (!groups)
|
|
22212
|
+
return;
|
|
22213
|
+
if (entries.length === 0) {
|
|
22214
|
+
groups.delete(agentType);
|
|
22215
|
+
if (groups.size === 0) {
|
|
22216
|
+
this.sessionsByParent.delete(parentSessionId);
|
|
22217
|
+
this.nextAliasIndexByParent.delete(parentSessionId);
|
|
22218
|
+
}
|
|
22219
|
+
return;
|
|
22220
|
+
}
|
|
22221
|
+
groups.set(agentType, entries);
|
|
22222
|
+
}
|
|
22223
|
+
nextAlias(parentSessionId, agentType) {
|
|
22224
|
+
let counters = this.nextAliasIndexByParent.get(parentSessionId);
|
|
22225
|
+
if (!counters) {
|
|
22226
|
+
counters = new Map;
|
|
22227
|
+
this.nextAliasIndexByParent.set(parentSessionId, counters);
|
|
22228
|
+
}
|
|
22229
|
+
const next = (counters.get(agentType) ?? 0) + 1;
|
|
22230
|
+
counters.set(agentType, next);
|
|
22231
|
+
return `${aliasPrefix(agentType)}-${next}`;
|
|
22232
|
+
}
|
|
22233
|
+
trimGroup(group) {
|
|
22234
|
+
group.sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
22235
|
+
if (group.length > this.maxSessionsPerAgent) {
|
|
22236
|
+
group.length = this.maxSessionsPerAgent;
|
|
22237
|
+
}
|
|
22238
|
+
}
|
|
22239
|
+
nextOrder() {
|
|
22240
|
+
this.orderCounter += 1;
|
|
22241
|
+
return this.orderCounter;
|
|
22242
|
+
}
|
|
22243
|
+
}
|
|
22244
|
+
// src/utils/task.ts
|
|
22245
|
+
function parseTaskIdFromTaskOutput(output) {
|
|
22246
|
+
const lines = output.split(/\r?\n/);
|
|
22247
|
+
for (const line of lines) {
|
|
22248
|
+
const trimmed = line.trim();
|
|
22249
|
+
const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
|
|
22250
|
+
if (!match) {
|
|
22251
|
+
continue;
|
|
22252
|
+
}
|
|
22253
|
+
return match[1];
|
|
22254
|
+
}
|
|
22255
|
+
return;
|
|
22256
|
+
}
|
|
22060
22257
|
// src/utils/zip-extractor.ts
|
|
22061
22258
|
import { spawnSync } from "node:child_process";
|
|
22062
22259
|
import { release } from "node:os";
|
|
@@ -22372,6 +22569,8 @@ var RATE_LIMIT_PATTERNS = [
|
|
|
22372
22569
|
/too many requests/i,
|
|
22373
22570
|
/quota.?exceeded/i,
|
|
22374
22571
|
/usage.?exceeded/i,
|
|
22572
|
+
/ExceededBudget/i,
|
|
22573
|
+
/over.?budget/i,
|
|
22375
22574
|
/usage limit/i,
|
|
22376
22575
|
/overloaded/i,
|
|
22377
22576
|
/resource.?exhausted/i,
|
|
@@ -22450,7 +22649,7 @@ class ForegroundFallbackManager {
|
|
|
22450
22649
|
if (!props?.sessionID || props.status?.type !== "retry")
|
|
22451
22650
|
break;
|
|
22452
22651
|
const msg = props.status.message?.toLowerCase() ?? "";
|
|
22453
|
-
if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
|
|
22652
|
+
if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("exceededbudget") || msg.includes("over budget") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
|
|
22454
22653
|
await this.tryFallback(props.sessionID);
|
|
22455
22654
|
}
|
|
22456
22655
|
break;
|
|
@@ -22880,6 +23079,154 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
22880
23079
|
}
|
|
22881
23080
|
};
|
|
22882
23081
|
}
|
|
23082
|
+
// src/hooks/task-session-manager/index.ts
|
|
23083
|
+
var AGENT_NAME_SET = new Set([
|
|
23084
|
+
"orchestrator",
|
|
23085
|
+
"oracle",
|
|
23086
|
+
"designer",
|
|
23087
|
+
"explorer",
|
|
23088
|
+
"librarian",
|
|
23089
|
+
"fixer",
|
|
23090
|
+
"observer",
|
|
23091
|
+
"council",
|
|
23092
|
+
"councillor"
|
|
23093
|
+
]);
|
|
23094
|
+
var MAX_PENDING_TASK_CALLS = 100;
|
|
23095
|
+
function isAgentName(value) {
|
|
23096
|
+
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
23097
|
+
}
|
|
23098
|
+
function isObjectRecord(value) {
|
|
23099
|
+
return typeof value === "object" && value !== null;
|
|
23100
|
+
}
|
|
23101
|
+
function createTaskSessionManagerHook(_ctx, options) {
|
|
23102
|
+
const sessionManager = new SessionManager(options.maxSessionsPerAgent);
|
|
23103
|
+
const pendingCalls = new Map;
|
|
23104
|
+
const pendingCallOrder = [];
|
|
23105
|
+
function isMissingRememberedSessionError(output) {
|
|
23106
|
+
const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
|
|
23107
|
+
return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
|
|
23108
|
+
}
|
|
23109
|
+
function rememberPendingCall(call) {
|
|
23110
|
+
const existingIndex = pendingCallOrder.indexOf(call.callId);
|
|
23111
|
+
if (existingIndex >= 0) {
|
|
23112
|
+
pendingCallOrder.splice(existingIndex, 1);
|
|
23113
|
+
}
|
|
23114
|
+
pendingCalls.set(call.callId, call);
|
|
23115
|
+
pendingCallOrder.push(call.callId);
|
|
23116
|
+
while (pendingCallOrder.length > MAX_PENDING_TASK_CALLS) {
|
|
23117
|
+
const evictedCallId = pendingCallOrder.shift();
|
|
23118
|
+
if (!evictedCallId) {
|
|
23119
|
+
break;
|
|
23120
|
+
}
|
|
23121
|
+
pendingCalls.delete(evictedCallId);
|
|
23122
|
+
}
|
|
23123
|
+
}
|
|
23124
|
+
function takePendingCall(callId) {
|
|
23125
|
+
if (!callId)
|
|
23126
|
+
return;
|
|
23127
|
+
const pending = pendingCalls.get(callId);
|
|
23128
|
+
pendingCalls.delete(callId);
|
|
23129
|
+
const orderIndex = pendingCallOrder.indexOf(callId);
|
|
23130
|
+
if (orderIndex >= 0) {
|
|
23131
|
+
pendingCallOrder.splice(orderIndex, 1);
|
|
23132
|
+
}
|
|
23133
|
+
return pending;
|
|
23134
|
+
}
|
|
23135
|
+
return {
|
|
23136
|
+
"tool.execute.before": async (input, output) => {
|
|
23137
|
+
if (input.tool.toLowerCase() !== "task")
|
|
23138
|
+
return;
|
|
23139
|
+
if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
|
|
23140
|
+
return;
|
|
23141
|
+
}
|
|
23142
|
+
if (!isObjectRecord(output.args))
|
|
23143
|
+
return;
|
|
23144
|
+
const args = output.args;
|
|
23145
|
+
if (!isAgentName(args.subagent_type))
|
|
23146
|
+
return;
|
|
23147
|
+
const label = deriveTaskSessionLabel({
|
|
23148
|
+
description: typeof args.description === "string" ? args.description : undefined,
|
|
23149
|
+
prompt: typeof args.prompt === "string" ? args.prompt : undefined,
|
|
23150
|
+
agentType: args.subagent_type
|
|
23151
|
+
});
|
|
23152
|
+
if (input.callID) {
|
|
23153
|
+
rememberPendingCall({
|
|
23154
|
+
callId: input.callID,
|
|
23155
|
+
parentSessionId: input.sessionID,
|
|
23156
|
+
agentType: args.subagent_type,
|
|
23157
|
+
label
|
|
23158
|
+
});
|
|
23159
|
+
}
|
|
23160
|
+
if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
|
|
23161
|
+
return;
|
|
23162
|
+
}
|
|
23163
|
+
const requested = args.task_id.trim();
|
|
23164
|
+
const remembered = sessionManager.resolve(input.sessionID, args.subagent_type, requested);
|
|
23165
|
+
if (!remembered) {
|
|
23166
|
+
delete args.task_id;
|
|
23167
|
+
return;
|
|
23168
|
+
}
|
|
23169
|
+
args.task_id = remembered.taskId;
|
|
23170
|
+
sessionManager.markUsed(input.sessionID, args.subagent_type, remembered.taskId);
|
|
23171
|
+
if (input.callID) {
|
|
23172
|
+
rememberPendingCall({
|
|
23173
|
+
callId: input.callID,
|
|
23174
|
+
parentSessionId: input.sessionID,
|
|
23175
|
+
agentType: args.subagent_type,
|
|
23176
|
+
label,
|
|
23177
|
+
resumedTaskId: remembered.taskId
|
|
23178
|
+
});
|
|
23179
|
+
}
|
|
23180
|
+
},
|
|
23181
|
+
"tool.execute.after": async (input, output) => {
|
|
23182
|
+
if (input.tool.toLowerCase() !== "task")
|
|
23183
|
+
return;
|
|
23184
|
+
const pending = takePendingCall(input.callID);
|
|
23185
|
+
if (!pending || typeof output.output !== "string")
|
|
23186
|
+
return;
|
|
23187
|
+
const taskId = parseTaskIdFromTaskOutput(output.output);
|
|
23188
|
+
if (!taskId) {
|
|
23189
|
+
if (pending.resumedTaskId && isMissingRememberedSessionError(output.output)) {
|
|
23190
|
+
sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId);
|
|
23191
|
+
}
|
|
23192
|
+
return;
|
|
23193
|
+
}
|
|
23194
|
+
if (pending.resumedTaskId && pending.resumedTaskId !== taskId) {
|
|
23195
|
+
sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId);
|
|
23196
|
+
}
|
|
23197
|
+
sessionManager.remember({
|
|
23198
|
+
parentSessionId: pending.parentSessionId,
|
|
23199
|
+
taskId,
|
|
23200
|
+
agentType: pending.agentType,
|
|
23201
|
+
label: pending.label
|
|
23202
|
+
});
|
|
23203
|
+
},
|
|
23204
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
23205
|
+
if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
|
|
23206
|
+
return;
|
|
23207
|
+
}
|
|
23208
|
+
const reminder = sessionManager.formatForPrompt(input.sessionID);
|
|
23209
|
+
if (!reminder)
|
|
23210
|
+
return;
|
|
23211
|
+
output.system.push(reminder);
|
|
23212
|
+
},
|
|
23213
|
+
event: async (input) => {
|
|
23214
|
+
if (input.event.type !== "session.deleted")
|
|
23215
|
+
return;
|
|
23216
|
+
const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
|
|
23217
|
+
if (!sessionId)
|
|
23218
|
+
return;
|
|
23219
|
+
sessionManager.clearParent(sessionId);
|
|
23220
|
+
sessionManager.dropTask(sessionId);
|
|
23221
|
+
for (const [callId, pending] of pendingCalls.entries()) {
|
|
23222
|
+
if (pending.parentSessionId !== sessionId) {
|
|
23223
|
+
continue;
|
|
23224
|
+
}
|
|
23225
|
+
takePendingCall(callId);
|
|
23226
|
+
}
|
|
23227
|
+
}
|
|
23228
|
+
};
|
|
23229
|
+
}
|
|
22883
23230
|
// src/hooks/todo-continuation/index.ts
|
|
22884
23231
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
22885
23232
|
|
|
@@ -23764,6 +24111,7 @@ async function readJsonBody(request) {
|
|
|
23764
24111
|
}
|
|
23765
24112
|
|
|
23766
24113
|
// src/interview/ui.ts
|
|
24114
|
+
var BRAND_LOGO_URL = "https://ohmyopencodeslim.com/android-chrome-512x512.png";
|
|
23767
24115
|
function escapeHtml(value) {
|
|
23768
24116
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23769
24117
|
}
|
|
@@ -23861,12 +24209,8 @@ function sharedStyles() {
|
|
|
23861
24209
|
}
|
|
23862
24210
|
.footer { margin-top: 32px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.4); }`;
|
|
23863
24211
|
}
|
|
23864
|
-
function
|
|
23865
|
-
return `<
|
|
23866
|
-
<rect x="12" y="12" width="120" height="120" rx="32" fill="rgba(255,255,255,0.08)" stroke="rgba(255,255,255,0.18)" stroke-width="2"/>
|
|
23867
|
-
<path d="M50 48h18c16 0 26 10 26 24s-10 24-26 24H50z" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
23868
|
-
<path d="M74 48h20c10 0 18 8 18 18v12c0 10-8 18-18 18H74" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" opacity="0.65"/>
|
|
23869
|
-
</svg>`;
|
|
24212
|
+
function brandImage(size) {
|
|
24213
|
+
return `<img class="brand-mark" src="${BRAND_LOGO_URL}" alt="Oh My Opencode Slim" width="${size}" height="${size}" />`;
|
|
23870
24214
|
}
|
|
23871
24215
|
function renderDashboardPage(interviews, files, outputFolder) {
|
|
23872
24216
|
const activeHtml = interviews.length === 0 ? "" : interviews.map((item) => {
|
|
@@ -24075,7 +24419,7 @@ function renderDashboardPage(interviews, files, outputFolder) {
|
|
|
24075
24419
|
<body>
|
|
24076
24420
|
<div class="wrap">
|
|
24077
24421
|
<div class="brand-header">
|
|
24078
|
-
${
|
|
24422
|
+
${brandImage(96)}
|
|
24079
24423
|
<h1>Interviews</h1>
|
|
24080
24424
|
<p class="muted">${totalCount} item${totalCount === 1 ? "" : "s"}</p>
|
|
24081
24425
|
</div>
|
|
@@ -24276,6 +24620,37 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24276
24620
|
}
|
|
24277
24621
|
|
|
24278
24622
|
.options { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
|
|
24623
|
+
.question-hint {
|
|
24624
|
+
display: flex;
|
|
24625
|
+
flex-wrap: wrap;
|
|
24626
|
+
gap: 8px;
|
|
24627
|
+
margin: -4px 0 16px;
|
|
24628
|
+
color: rgba(255,255,255,0.5);
|
|
24629
|
+
font-size: 13px;
|
|
24630
|
+
line-height: 1.5;
|
|
24631
|
+
transition: color 0.2s ease;
|
|
24632
|
+
}
|
|
24633
|
+
.active-question .question-hint {
|
|
24634
|
+
color: rgba(255,255,255,0.78);
|
|
24635
|
+
}
|
|
24636
|
+
.hint-chip {
|
|
24637
|
+
display: inline-flex;
|
|
24638
|
+
align-items: center;
|
|
24639
|
+
gap: 6px;
|
|
24640
|
+
padding: 4px 10px;
|
|
24641
|
+
border-radius: 999px;
|
|
24642
|
+
background: rgba(255,255,255,0.06);
|
|
24643
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
24644
|
+
}
|
|
24645
|
+
.hint-chip kbd {
|
|
24646
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
24647
|
+
font-size: 12px;
|
|
24648
|
+
padding: 2px 6px;
|
|
24649
|
+
border-radius: 6px;
|
|
24650
|
+
background: rgba(255,255,255,0.12);
|
|
24651
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
24652
|
+
color: rgba(255,255,255,0.95);
|
|
24653
|
+
}
|
|
24279
24654
|
|
|
24280
24655
|
.option {
|
|
24281
24656
|
border: 1px solid rgba(255,255,255,0.1);
|
|
@@ -24555,7 +24930,7 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24555
24930
|
<div class="wrap">
|
|
24556
24931
|
<a href="/" class="back-link">← All Interviews</a>
|
|
24557
24932
|
<div class="brand-header">
|
|
24558
|
-
${
|
|
24933
|
+
${brandImage(144)}
|
|
24559
24934
|
</div>
|
|
24560
24935
|
<h1 id="idea">Connecting...</h1>
|
|
24561
24936
|
<p class="muted" id="summary">Preparing interview session</p>
|
|
@@ -24596,7 +24971,15 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24596
24971
|
${clipboardHelperJs()}
|
|
24597
24972
|
const interviewId = ${JSON.stringify(interviewId).replace(/</g, "\\u003c")};
|
|
24598
24973
|
const resumeSlug = ${JSON.stringify(resumeSlug).replace(/</g, "\\u003c")};
|
|
24599
|
-
const state = {
|
|
24974
|
+
const state = {
|
|
24975
|
+
data: null,
|
|
24976
|
+
answers: {},
|
|
24977
|
+
activeQuestionIndex: 0,
|
|
24978
|
+
lastQuestionIds: [],
|
|
24979
|
+
lastSig: null,
|
|
24980
|
+
customMode: {},
|
|
24981
|
+
isSubmitting: false,
|
|
24982
|
+
};
|
|
24600
24983
|
|
|
24601
24984
|
function updateSubmitButton() {
|
|
24602
24985
|
const button = document.getElementById('submitButton');
|
|
@@ -24609,7 +24992,11 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24609
24992
|
const allAnswered = questions.every((question) =>
|
|
24610
24993
|
(state.answers[question.id] || '').trim().length > 0,
|
|
24611
24994
|
);
|
|
24612
|
-
button.disabled =
|
|
24995
|
+
button.disabled =
|
|
24996
|
+
state.data.isBusy ||
|
|
24997
|
+
state.isSubmitting ||
|
|
24998
|
+
!questions.length ||
|
|
24999
|
+
!allAnswered;
|
|
24613
25000
|
const hideSubmit = ['completed', 'session-disconnected'];
|
|
24614
25001
|
button.style.display = hideSubmit.includes(state.data.mode) ? 'none' : '';
|
|
24615
25002
|
|
|
@@ -24760,6 +25147,54 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24760
25147
|
});
|
|
24761
25148
|
}
|
|
24762
25149
|
|
|
25150
|
+
function scrollToActiveQuestion(behavior) {
|
|
25151
|
+
const questions = state.data?.questions || [];
|
|
25152
|
+
const activeQ = questions[state.activeQuestionIndex];
|
|
25153
|
+
if (!activeQ) return;
|
|
25154
|
+
|
|
25155
|
+
const wrapper = document.getElementById('question-' + activeQ.id);
|
|
25156
|
+
if (wrapper) {
|
|
25157
|
+
wrapper.scrollIntoView({ behavior, block: 'center' });
|
|
25158
|
+
}
|
|
25159
|
+
}
|
|
25160
|
+
|
|
25161
|
+
function syncActiveQuestionIndex(questions) {
|
|
25162
|
+
if (!questions.length) {
|
|
25163
|
+
state.activeQuestionIndex = 0;
|
|
25164
|
+
state.lastQuestionIds = [];
|
|
25165
|
+
return;
|
|
25166
|
+
}
|
|
25167
|
+
|
|
25168
|
+
const nextQuestionIds = questions.map((question) => question.id);
|
|
25169
|
+
const previousQuestionIds = state.lastQuestionIds || [];
|
|
25170
|
+
const activeQuestionId = previousQuestionIds[state.activeQuestionIndex];
|
|
25171
|
+
const nextActiveIndex = activeQuestionId
|
|
25172
|
+
? nextQuestionIds.indexOf(activeQuestionId)
|
|
25173
|
+
: -1;
|
|
25174
|
+
|
|
25175
|
+
if (nextActiveIndex >= 0) {
|
|
25176
|
+
state.activeQuestionIndex = nextActiveIndex;
|
|
25177
|
+
} else {
|
|
25178
|
+
state.activeQuestionIndex = 0;
|
|
25179
|
+
}
|
|
25180
|
+
|
|
25181
|
+
state.lastQuestionIds = nextQuestionIds;
|
|
25182
|
+
}
|
|
25183
|
+
|
|
25184
|
+
function isTextEntryTarget(target) {
|
|
25185
|
+
return target &&
|
|
25186
|
+
(target.tagName === 'TEXTAREA' ||
|
|
25187
|
+
target.tagName === 'INPUT' ||
|
|
25188
|
+
target.isContentEditable);
|
|
25189
|
+
}
|
|
25190
|
+
|
|
25191
|
+
function isShortcutBlockedTarget(target) {
|
|
25192
|
+
if (!target) return false;
|
|
25193
|
+
return !!target.closest(
|
|
25194
|
+
'button, a, select, summary, textarea, input, [contenteditable="true"]',
|
|
25195
|
+
);
|
|
25196
|
+
}
|
|
25197
|
+
|
|
24763
25198
|
document.addEventListener('keydown', (e) => {
|
|
24764
25199
|
const isSubmitShortcut =
|
|
24765
25200
|
(e.key === 'Enter' && (e.metaKey || e.ctrlKey)) ||
|
|
@@ -24773,12 +25208,41 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24773
25208
|
return;
|
|
24774
25209
|
}
|
|
24775
25210
|
|
|
24776
|
-
if (e.target
|
|
25211
|
+
if (isTextEntryTarget(e.target)) return;
|
|
24777
25212
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
24778
25213
|
|
|
24779
25214
|
const questions = state.data?.questions || [];
|
|
24780
25215
|
if (!questions.length) return;
|
|
24781
25216
|
|
|
25217
|
+
if (e.key === 'Enter') {
|
|
25218
|
+
if (e.repeat) {
|
|
25219
|
+
e.preventDefault();
|
|
25220
|
+
return;
|
|
25221
|
+
}
|
|
25222
|
+
if (isShortcutBlockedTarget(e.target)) return;
|
|
25223
|
+
if (state.data.isBusy || state.isSubmitting) return;
|
|
25224
|
+
|
|
25225
|
+
const activeQ = questions[state.activeQuestionIndex];
|
|
25226
|
+
if (!activeQ) return;
|
|
25227
|
+
|
|
25228
|
+
const answer = (state.answers[activeQ.id] || '').trim();
|
|
25229
|
+
if (!answer) return;
|
|
25230
|
+
|
|
25231
|
+
const isLastQuestion =
|
|
25232
|
+
state.activeQuestionIndex === questions.length - 1;
|
|
25233
|
+
if (isLastQuestion) {
|
|
25234
|
+
const submitBtn = document.getElementById('submitButton');
|
|
25235
|
+
if (submitBtn && !submitBtn.disabled) {
|
|
25236
|
+
submitBtn.click();
|
|
25237
|
+
}
|
|
25238
|
+
} else {
|
|
25239
|
+
advanceToNextQuestion(activeQ.id);
|
|
25240
|
+
}
|
|
25241
|
+
|
|
25242
|
+
e.preventDefault();
|
|
25243
|
+
return;
|
|
25244
|
+
}
|
|
25245
|
+
|
|
24782
25246
|
const num = parseInt(e.key, 10);
|
|
24783
25247
|
if (num >= 1 && num <= 9) {
|
|
24784
25248
|
const activeQ = questions[state.activeQuestionIndex];
|
|
@@ -24946,6 +25410,10 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24946
25410
|
function renderQuestions(questions) {
|
|
24947
25411
|
const sig = JSON.stringify([questions, state.data?.mode]);
|
|
24948
25412
|
const container = document.getElementById('questions');
|
|
25413
|
+
const previousActiveQuestionId =
|
|
25414
|
+
state.lastQuestionIds[state.activeQuestionIndex];
|
|
25415
|
+
|
|
25416
|
+
syncActiveQuestionIndex(questions);
|
|
24949
25417
|
|
|
24950
25418
|
if (state.lastSig === sig) {
|
|
24951
25419
|
questions.forEach((q) => updateOptionsDOM(q.id));
|
|
@@ -24986,6 +25454,15 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
24986
25454
|
wrapper.appendChild(title);
|
|
24987
25455
|
|
|
24988
25456
|
const predefined = question.options || [];
|
|
25457
|
+
if (predefined.length) {
|
|
25458
|
+
const hint = document.createElement('div');
|
|
25459
|
+
hint.className = 'question-hint';
|
|
25460
|
+
hint.innerHTML =
|
|
25461
|
+
'<span class="hint-chip"><kbd>1-9</kbd><span>Choose an option</span></span>' +
|
|
25462
|
+
'<span class="hint-chip"><kbd>Enter</kbd><span>Accept selected answer</span></span>';
|
|
25463
|
+
wrapper.appendChild(hint);
|
|
25464
|
+
}
|
|
25465
|
+
|
|
24989
25466
|
if (predefined.length) {
|
|
24990
25467
|
const options = document.createElement('div');
|
|
24991
25468
|
options.className = 'options';
|
|
@@ -25021,6 +25498,13 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
25021
25498
|
|
|
25022
25499
|
updateActiveQuestionFocus();
|
|
25023
25500
|
questions.forEach(q => updateOptionsDOM(q.id));
|
|
25501
|
+
const currentActiveQuestionId = questions[state.activeQuestionIndex]?.id;
|
|
25502
|
+
if (
|
|
25503
|
+
questions.length > 0 &&
|
|
25504
|
+
previousActiveQuestionId !== currentActiveQuestionId
|
|
25505
|
+
) {
|
|
25506
|
+
scrollToActiveQuestion('smooth');
|
|
25507
|
+
}
|
|
25024
25508
|
}
|
|
25025
25509
|
|
|
25026
25510
|
function render(data) {
|
|
@@ -25094,7 +25578,8 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
25094
25578
|
}
|
|
25095
25579
|
|
|
25096
25580
|
document.getElementById('submitButton').addEventListener('click', async () => {
|
|
25097
|
-
if (!state.data) return;
|
|
25581
|
+
if (!state.data || state.isSubmitting) return;
|
|
25582
|
+
document.getElementById('submitButton').blur();
|
|
25098
25583
|
const answers = (state.data.questions || []).map((question) => {
|
|
25099
25584
|
return {
|
|
25100
25585
|
questionId: question.id,
|
|
@@ -25102,6 +25587,9 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
25102
25587
|
};
|
|
25103
25588
|
});
|
|
25104
25589
|
|
|
25590
|
+
state.isSubmitting = true;
|
|
25591
|
+
updateSubmitButton();
|
|
25592
|
+
|
|
25105
25593
|
const overlay = document.getElementById('loadingOverlay');
|
|
25106
25594
|
const overlayText = document.getElementById('loadingText');
|
|
25107
25595
|
overlay.classList.add('active');
|
|
@@ -25119,6 +25607,8 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
25119
25607
|
} catch (err) {
|
|
25120
25608
|
document.getElementById('submitStatus').textContent = 'Error submitting answers.';
|
|
25121
25609
|
}
|
|
25610
|
+
state.isSubmitting = false;
|
|
25611
|
+
updateSubmitButton();
|
|
25122
25612
|
try {
|
|
25123
25613
|
await refresh();
|
|
25124
25614
|
} catch (_error) {
|
|
@@ -26438,6 +26928,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
26438
26928
|
const activeInterviewIds = new Map;
|
|
26439
26929
|
const interviewsById = new Map;
|
|
26440
26930
|
const sessionBusy = new Map;
|
|
26931
|
+
const sessionModel = new Map;
|
|
26441
26932
|
const browserOpened = new Set;
|
|
26442
26933
|
let resolveBaseUrl = null;
|
|
26443
26934
|
let onStateChange = null;
|
|
@@ -26693,10 +27184,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
26693
27184
|
}
|
|
26694
27185
|
await appendInterviewAnswers(interview, state.questions, answers);
|
|
26695
27186
|
const prompt = buildAnswerPrompt(answers, state.questions, maxQuestions);
|
|
27187
|
+
const model = sessionModel.get(interview.sessionID);
|
|
26696
27188
|
await ctx.client.session.promptAsync({
|
|
26697
27189
|
path: { id: interview.sessionID },
|
|
26698
27190
|
body: {
|
|
26699
|
-
parts: [createInternalAgentTextPart(prompt)]
|
|
27191
|
+
parts: [createInternalAgentTextPart(prompt)],
|
|
27192
|
+
...model ? { model: parseModelReference(model) ?? undefined } : {}
|
|
26700
27193
|
}
|
|
26701
27194
|
});
|
|
26702
27195
|
promptSent = true;
|
|
@@ -26746,12 +27239,23 @@ function createInterviewService(ctx, config, deps) {
|
|
|
26746
27239
|
}
|
|
26747
27240
|
return;
|
|
26748
27241
|
}
|
|
27242
|
+
if (event.type === "message.updated") {
|
|
27243
|
+
const info = properties;
|
|
27244
|
+
const sessionID = info?.info?.sessionID;
|
|
27245
|
+
const providerID = info?.info?.providerID;
|
|
27246
|
+
const modelID = info?.info?.modelID;
|
|
27247
|
+
if (sessionID && providerID && modelID) {
|
|
27248
|
+
sessionModel.set(sessionID, `${providerID}/${modelID}`);
|
|
27249
|
+
}
|
|
27250
|
+
return;
|
|
27251
|
+
}
|
|
26749
27252
|
if (event.type === "session.deleted") {
|
|
26750
27253
|
const deletedSessionId = (properties.info?.id ?? properties.sessionID) || null;
|
|
26751
27254
|
if (!deletedSessionId) {
|
|
26752
27255
|
return;
|
|
26753
27256
|
}
|
|
26754
27257
|
sessionBusy.delete(deletedSessionId);
|
|
27258
|
+
sessionModel.delete(deletedSessionId);
|
|
26755
27259
|
const interviewId = activeInterviewIds.get(deletedSessionId);
|
|
26756
27260
|
if (!interviewId) {
|
|
26757
27261
|
return;
|
|
@@ -26848,10 +27352,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
26848
27352
|
].join(`
|
|
26849
27353
|
`);
|
|
26850
27354
|
}
|
|
27355
|
+
const model = sessionModel.get(interview.sessionID);
|
|
26851
27356
|
await ctx.client.session.promptAsync({
|
|
26852
27357
|
path: { id: interview.sessionID },
|
|
26853
27358
|
body: {
|
|
26854
|
-
parts: [createInternalAgentTextPart(prompt)]
|
|
27359
|
+
parts: [createInternalAgentTextPart(prompt)],
|
|
27360
|
+
...model ? { model: parseModelReference(model) ?? undefined } : {}
|
|
26855
27361
|
}
|
|
26856
27362
|
});
|
|
26857
27363
|
promptSent = true;
|
|
@@ -27825,6 +28331,8 @@ class MultiplexerSessionManager {
|
|
|
27825
28331
|
directory;
|
|
27826
28332
|
multiplexer = null;
|
|
27827
28333
|
sessions = new Map;
|
|
28334
|
+
knownSessions = new Map;
|
|
28335
|
+
spawningSessions = new Set;
|
|
27828
28336
|
pollInterval;
|
|
27829
28337
|
enabled = false;
|
|
27830
28338
|
constructor(ctx, config) {
|
|
@@ -27853,45 +28361,59 @@ class MultiplexerSessionManager {
|
|
|
27853
28361
|
const parentId = info.parentID;
|
|
27854
28362
|
const title = info.title ?? "Subagent";
|
|
27855
28363
|
const directory = info.directory ?? this.directory;
|
|
27856
|
-
|
|
27857
|
-
|
|
28364
|
+
this.knownSessions.set(sessionId, {
|
|
28365
|
+
parentId,
|
|
28366
|
+
title,
|
|
28367
|
+
directory
|
|
28368
|
+
});
|
|
28369
|
+
if (this.isTrackedOrSpawning(sessionId)) {
|
|
28370
|
+
log("[multiplexer-session-manager] session already tracked or spawning", {
|
|
27858
28371
|
sessionId
|
|
27859
28372
|
});
|
|
27860
28373
|
return;
|
|
27861
28374
|
}
|
|
27862
|
-
|
|
27863
|
-
|
|
27864
|
-
|
|
27865
|
-
|
|
27866
|
-
|
|
27867
|
-
|
|
27868
|
-
|
|
27869
|
-
|
|
27870
|
-
|
|
27871
|
-
|
|
27872
|
-
|
|
27873
|
-
|
|
27874
|
-
|
|
27875
|
-
log("[multiplexer-session-manager] failed to spawn pane", {
|
|
27876
|
-
error: String(err)
|
|
27877
|
-
});
|
|
27878
|
-
return { success: false, paneId: undefined };
|
|
27879
|
-
});
|
|
27880
|
-
if (paneResult.success && paneResult.paneId) {
|
|
27881
|
-
const now = Date.now();
|
|
27882
|
-
this.sessions.set(sessionId, {
|
|
28375
|
+
this.spawningSessions.add(sessionId);
|
|
28376
|
+
try {
|
|
28377
|
+
const serverRunning = await isServerRunning(this.serverUrl);
|
|
28378
|
+
if (!serverRunning) {
|
|
28379
|
+
log("[multiplexer-session-manager] server not running, skipping", {
|
|
28380
|
+
serverUrl: this.serverUrl
|
|
28381
|
+
});
|
|
28382
|
+
return;
|
|
28383
|
+
}
|
|
28384
|
+
if (this.sessions.has(sessionId)) {
|
|
28385
|
+
return;
|
|
28386
|
+
}
|
|
28387
|
+
log("[multiplexer-session-manager] child session created, spawning pane", {
|
|
27883
28388
|
sessionId,
|
|
27884
|
-
paneId: paneResult.paneId,
|
|
27885
28389
|
parentId,
|
|
27886
|
-
title
|
|
27887
|
-
createdAt: now,
|
|
27888
|
-
lastSeenAt: now
|
|
28390
|
+
title
|
|
27889
28391
|
});
|
|
27890
|
-
|
|
27891
|
-
|
|
27892
|
-
|
|
28392
|
+
const paneResult = await this.multiplexer.spawnPane(sessionId, title, this.serverUrl, directory).catch((err) => {
|
|
28393
|
+
log("[multiplexer-session-manager] failed to spawn pane", {
|
|
28394
|
+
error: String(err)
|
|
28395
|
+
});
|
|
28396
|
+
return { success: false, paneId: undefined };
|
|
27893
28397
|
});
|
|
27894
|
-
|
|
28398
|
+
if (paneResult.success && paneResult.paneId) {
|
|
28399
|
+
const now = Date.now();
|
|
28400
|
+
this.sessions.set(sessionId, {
|
|
28401
|
+
sessionId,
|
|
28402
|
+
paneId: paneResult.paneId,
|
|
28403
|
+
parentId,
|
|
28404
|
+
title,
|
|
28405
|
+
directory,
|
|
28406
|
+
createdAt: now,
|
|
28407
|
+
lastSeenAt: now
|
|
28408
|
+
});
|
|
28409
|
+
log("[multiplexer-session-manager] pane spawned", {
|
|
28410
|
+
sessionId,
|
|
28411
|
+
paneId: paneResult.paneId
|
|
28412
|
+
});
|
|
28413
|
+
this.startPolling();
|
|
28414
|
+
}
|
|
28415
|
+
} finally {
|
|
28416
|
+
this.spawningSessions.delete(sessionId);
|
|
27895
28417
|
}
|
|
27896
28418
|
}
|
|
27897
28419
|
async onSessionStatus(event) {
|
|
@@ -27904,6 +28426,10 @@ class MultiplexerSessionManager {
|
|
|
27904
28426
|
return;
|
|
27905
28427
|
if (event.properties?.status?.type === "idle") {
|
|
27906
28428
|
await this.closeSession(sessionId);
|
|
28429
|
+
return;
|
|
28430
|
+
}
|
|
28431
|
+
if (event.properties?.status?.type === "busy") {
|
|
28432
|
+
await this.respawnIfKnown(sessionId);
|
|
27907
28433
|
}
|
|
27908
28434
|
}
|
|
27909
28435
|
async onSessionDeleted(event) {
|
|
@@ -27918,6 +28444,7 @@ class MultiplexerSessionManager {
|
|
|
27918
28444
|
sessionId
|
|
27919
28445
|
});
|
|
27920
28446
|
await this.closeSession(sessionId);
|
|
28447
|
+
this.knownSessions.delete(sessionId);
|
|
27921
28448
|
}
|
|
27922
28449
|
startPolling() {
|
|
27923
28450
|
if (this.pollInterval)
|
|
@@ -27978,6 +28505,61 @@ class MultiplexerSessionManager {
|
|
|
27978
28505
|
this.stopPolling();
|
|
27979
28506
|
}
|
|
27980
28507
|
}
|
|
28508
|
+
async respawnIfKnown(sessionId) {
|
|
28509
|
+
if (!this.enabled || !this.multiplexer)
|
|
28510
|
+
return;
|
|
28511
|
+
if (this.isTrackedOrSpawning(sessionId))
|
|
28512
|
+
return;
|
|
28513
|
+
const known = this.knownSessions.get(sessionId);
|
|
28514
|
+
if (!known)
|
|
28515
|
+
return;
|
|
28516
|
+
this.spawningSessions.add(sessionId);
|
|
28517
|
+
try {
|
|
28518
|
+
const serverRunning = await isServerRunning(this.serverUrl);
|
|
28519
|
+
if (!serverRunning) {
|
|
28520
|
+
log("[multiplexer-session-manager] server not running, skipping busy respawn", {
|
|
28521
|
+
serverUrl: this.serverUrl,
|
|
28522
|
+
sessionId
|
|
28523
|
+
});
|
|
28524
|
+
return;
|
|
28525
|
+
}
|
|
28526
|
+
if (this.sessions.has(sessionId))
|
|
28527
|
+
return;
|
|
28528
|
+
log("[multiplexer-session-manager] child session busy again, respawning pane", {
|
|
28529
|
+
sessionId,
|
|
28530
|
+
parentId: known.parentId,
|
|
28531
|
+
title: known.title
|
|
28532
|
+
});
|
|
28533
|
+
const paneResult = await this.multiplexer.spawnPane(sessionId, known.title, this.serverUrl, known.directory).catch((err) => {
|
|
28534
|
+
log("[multiplexer-session-manager] failed to respawn pane", {
|
|
28535
|
+
error: String(err)
|
|
28536
|
+
});
|
|
28537
|
+
return { success: false, paneId: undefined };
|
|
28538
|
+
});
|
|
28539
|
+
if (!paneResult.success || !paneResult.paneId)
|
|
28540
|
+
return;
|
|
28541
|
+
const now = Date.now();
|
|
28542
|
+
this.sessions.set(sessionId, {
|
|
28543
|
+
sessionId,
|
|
28544
|
+
paneId: paneResult.paneId,
|
|
28545
|
+
parentId: known.parentId,
|
|
28546
|
+
title: known.title,
|
|
28547
|
+
directory: known.directory,
|
|
28548
|
+
createdAt: now,
|
|
28549
|
+
lastSeenAt: now
|
|
28550
|
+
});
|
|
28551
|
+
log("[multiplexer-session-manager] pane respawned on busy", {
|
|
28552
|
+
sessionId,
|
|
28553
|
+
paneId: paneResult.paneId
|
|
28554
|
+
});
|
|
28555
|
+
this.startPolling();
|
|
28556
|
+
} finally {
|
|
28557
|
+
this.spawningSessions.delete(sessionId);
|
|
28558
|
+
}
|
|
28559
|
+
}
|
|
28560
|
+
isTrackedOrSpawning(sessionId) {
|
|
28561
|
+
return this.sessions.has(sessionId) || this.spawningSessions.has(sessionId);
|
|
28562
|
+
}
|
|
27981
28563
|
async cleanup() {
|
|
27982
28564
|
this.stopPolling();
|
|
27983
28565
|
if (this.sessions.size > 0 && this.multiplexer) {
|
|
@@ -27992,6 +28574,8 @@ class MultiplexerSessionManager {
|
|
|
27992
28574
|
await Promise.all(closePromises);
|
|
27993
28575
|
this.sessions.clear();
|
|
27994
28576
|
}
|
|
28577
|
+
this.knownSessions.clear();
|
|
28578
|
+
this.spawningSessions.clear();
|
|
27995
28579
|
log("[multiplexer-session-manager] cleanup complete");
|
|
27996
28580
|
}
|
|
27997
28581
|
}
|
|
@@ -31099,6 +31683,17 @@ class SubagentDepthTracker {
|
|
|
31099
31683
|
}
|
|
31100
31684
|
}
|
|
31101
31685
|
|
|
31686
|
+
// src/utils/system-collapse.ts
|
|
31687
|
+
function collapseSystemInPlace(system2) {
|
|
31688
|
+
const joined = system2.join(`
|
|
31689
|
+
|
|
31690
|
+
`);
|
|
31691
|
+
system2.length = 0;
|
|
31692
|
+
if (joined) {
|
|
31693
|
+
system2.push(joined);
|
|
31694
|
+
}
|
|
31695
|
+
}
|
|
31696
|
+
|
|
31102
31697
|
// src/index.ts
|
|
31103
31698
|
async function appLog(ctx, level, message) {
|
|
31104
31699
|
try {
|
|
@@ -31148,6 +31743,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31148
31743
|
let jsonErrorRecoveryHook;
|
|
31149
31744
|
let foregroundFallback;
|
|
31150
31745
|
let todoContinuationHook;
|
|
31746
|
+
let taskSessionManagerHook;
|
|
31151
31747
|
let interviewManager;
|
|
31152
31748
|
let presetManager;
|
|
31153
31749
|
let councilTools;
|
|
@@ -31207,7 +31803,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31207
31803
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig);
|
|
31208
31804
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
31209
31805
|
showStartupToast: config.showStartupToast ?? true,
|
|
31210
|
-
autoUpdate: true
|
|
31806
|
+
autoUpdate: config.autoUpdate ?? true
|
|
31211
31807
|
});
|
|
31212
31808
|
phaseReminderHook = createPhaseReminderHook();
|
|
31213
31809
|
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
@@ -31226,6 +31822,10 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31226
31822
|
autoEnable: config.todoContinuation?.autoEnable ?? false,
|
|
31227
31823
|
autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4
|
|
31228
31824
|
});
|
|
31825
|
+
taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
|
|
31826
|
+
maxSessionsPerAgent: config.sessionManager?.maxSessionsPerAgent ?? 2,
|
|
31827
|
+
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
31828
|
+
});
|
|
31229
31829
|
interviewManager = createInterviewManager(ctx, config);
|
|
31230
31830
|
presetManager = createPresetManager(ctx, config);
|
|
31231
31831
|
toolCount = Object.keys(councilTools).length + Object.keys(todoContinuationHook.tool).length + 1 + 2;
|
|
@@ -31401,6 +32001,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31401
32001
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
31402
32002
|
await interviewManager.handleEvent(input);
|
|
31403
32003
|
await postFileToolNudgeHook.event(input);
|
|
32004
|
+
await taskSessionManagerHook.event(input);
|
|
31404
32005
|
if (input.event.type === "session.deleted") {
|
|
31405
32006
|
const props = input.event.properties;
|
|
31406
32007
|
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
@@ -31414,6 +32015,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31414
32015
|
},
|
|
31415
32016
|
"tool.execute.before": async (input, output) => {
|
|
31416
32017
|
await applyPatchHook["tool.execute.before"](input, output);
|
|
32018
|
+
await taskSessionManagerHook["tool.execute.before"](input, output);
|
|
31417
32019
|
},
|
|
31418
32020
|
"command.execute.before": async (input, output) => {
|
|
31419
32021
|
await todoContinuationHook.handleCommandExecuteBefore(input, output);
|
|
@@ -31449,10 +32051,8 @@ ${output.system[0]}` : "");
|
|
|
31449
32051
|
}
|
|
31450
32052
|
await todoContinuationHook.handleChatSystemTransform(input, output);
|
|
31451
32053
|
await postFileToolNudgeHook["experimental.chat.system.transform"](input, output);
|
|
31452
|
-
|
|
31453
|
-
|
|
31454
|
-
`);
|
|
31455
|
-
output.system = joined ? [joined] : [];
|
|
32054
|
+
await taskSessionManagerHook["experimental.chat.system.transform"](input, output);
|
|
32055
|
+
collapseSystemInPlace(output.system);
|
|
31456
32056
|
},
|
|
31457
32057
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
31458
32058
|
const typedOutput = output;
|
|
@@ -31484,6 +32084,7 @@ ${output.system[0]}` : "");
|
|
|
31484
32084
|
await jsonErrorRecoveryHook["tool.execute.after"](input, output);
|
|
31485
32085
|
await todoContinuationHook.handleToolExecuteAfter(input);
|
|
31486
32086
|
await postFileToolNudgeHook["tool.execute.after"](input, output);
|
|
32087
|
+
await taskSessionManagerHook["tool.execute.after"](input, output);
|
|
31487
32088
|
}
|
|
31488
32089
|
};
|
|
31489
32090
|
};
|