hanoman 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/cli.js +50 -38
- package/dist/server.js +415 -151
- package/package.json +1 -1
- package/web/assets/{ChangelogScreen-CqdDSvcM.js → ChangelogScreen-DV7czksP.js} +1 -1
- package/web/assets/{DalangHanomanScreen-BVl47PiI.js → DalangHanomanScreen-gKHFySwO.js} +1 -1
- package/web/assets/{DocPreviewModal-DQ3XMay1.js → DocPreviewModal-DCal7m9E.js} +1 -1
- package/web/assets/{DocsWorkspace-DVOUvpB5.js → DocsWorkspace-BMAVKbqw.js} +2 -2
- package/web/assets/{IdeScreen-rug2LVDr.js → IdeScreen-ZmtKIwRL.js} +1 -1
- package/web/assets/{LeadScreen-DssA9M8m.js → LeadScreen-BGP15mjr.js} +1 -1
- package/web/assets/{ReviewScreen-C1IA0OXe.js → ReviewScreen-BB-yKNIm.js} +1 -1
- package/web/assets/SchedulerScreen-C1T7kZ5F.js +1 -0
- package/web/assets/SettingsScreen-JT8uQrhn.js +62 -0
- package/web/assets/{TeamScreen-CnLpgpL4.js → TeamScreen-CJ-AeCGM.js} +1 -1
- package/web/assets/TerminalScreen-BTPwK0A4.js +2 -0
- package/web/assets/{TriageScreen-QT5nAssi.js → TriageScreen-HE-JKaXl.js} +1 -1
- package/web/assets/{VpsScreen-C4Aw0EJB.js → VpsScreen-CiRV0mMK.js} +1 -1
- package/web/assets/index-B_pRoVeM.js +1005 -0
- package/web/assets/{live-DSfDhu0-.js → live-B0yZ2azt.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/SchedulerScreen-C3dr6FMf.js +0 -1
- package/web/assets/SettingsScreen-C39ctLm1.js +0 -62
- package/web/assets/TerminalScreen-lw3pei-W.js +0 -2
- package/web/assets/index-Cl9u81Cr.js +0 -1005
package/dist/server.js
CHANGED
|
@@ -4393,7 +4393,7 @@ var init_agent_engine = __esm({
|
|
|
4393
4393
|
zAgentEngine = external_exports.object({
|
|
4394
4394
|
enabled: external_exports.boolean().default(false),
|
|
4395
4395
|
agent: zAgent.default("claude"),
|
|
4396
|
-
model: external_exports.string().default("
|
|
4396
|
+
model: external_exports.string().default("opus"),
|
|
4397
4397
|
effort: external_exports.string().default("xhigh")
|
|
4398
4398
|
});
|
|
4399
4399
|
}
|
|
@@ -4832,7 +4832,7 @@ var init_runtime_defaults = __esm({
|
|
|
4832
4832
|
"use strict";
|
|
4833
4833
|
init_orchestration();
|
|
4834
4834
|
init_zod();
|
|
4835
|
-
RUNTIME_DEFAULTS_VERSION = "2026-09-
|
|
4835
|
+
RUNTIME_DEFAULTS_VERSION = "2026-09-23-v1";
|
|
4836
4836
|
zRuntimeDefaultState = external_exports.enum(["seeded", "user"]);
|
|
4837
4837
|
zBuiltinRuntimeDefaults = external_exports.object({
|
|
4838
4838
|
version: external_exports.string().default(RUNTIME_DEFAULTS_VERSION),
|
|
@@ -4847,7 +4847,7 @@ var init_runtime_defaults = __esm({
|
|
|
4847
4847
|
orchestration: external_exports.record(external_exports.string(), zRuntimeDefaultState).default({})
|
|
4848
4848
|
});
|
|
4849
4849
|
BUILTIN_CLAUDE_RUNTIME_DEFAULTS = {
|
|
4850
|
-
model: "
|
|
4850
|
+
model: "sonnet",
|
|
4851
4851
|
effort: "medium"
|
|
4852
4852
|
};
|
|
4853
4853
|
BUILTIN_CODEX_RUNTIME_DEFAULTS = {
|
|
@@ -4872,11 +4872,11 @@ var init_runtime_defaults = __esm({
|
|
|
4872
4872
|
BUILTIN_ORCHESTRATION_DEFAULTS = {
|
|
4873
4873
|
feature: flow(
|
|
4874
4874
|
{
|
|
4875
|
-
Brainstorm: cell("
|
|
4876
|
-
Objective: cell("
|
|
4877
|
-
Spec: cell("
|
|
4878
|
-
Plan: cell("
|
|
4879
|
-
Execute: cell("
|
|
4875
|
+
Brainstorm: cell("sonnet", "medium"),
|
|
4876
|
+
Objective: cell("sonnet", "medium"),
|
|
4877
|
+
Spec: cell("opus", "medium"),
|
|
4878
|
+
Plan: cell("sonnet", "medium"),
|
|
4879
|
+
Execute: cell("sonnet", "medium")
|
|
4880
4880
|
},
|
|
4881
4881
|
{
|
|
4882
4882
|
Brainstorm: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4888,10 +4888,10 @@ var init_runtime_defaults = __esm({
|
|
|
4888
4888
|
),
|
|
4889
4889
|
qa: flow(
|
|
4890
4890
|
{
|
|
4891
|
-
Audit: cell("
|
|
4892
|
-
Spec: cell("
|
|
4893
|
-
Plan: cell("
|
|
4894
|
-
Execute: cell("
|
|
4891
|
+
Audit: cell("sonnet", "medium"),
|
|
4892
|
+
Spec: cell("opus", "medium"),
|
|
4893
|
+
Plan: cell("sonnet", "medium"),
|
|
4894
|
+
Execute: cell("sonnet", "medium")
|
|
4895
4895
|
},
|
|
4896
4896
|
{
|
|
4897
4897
|
Audit: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4902,9 +4902,9 @@ var init_runtime_defaults = __esm({
|
|
|
4902
4902
|
),
|
|
4903
4903
|
scaffold: flow(
|
|
4904
4904
|
{
|
|
4905
|
-
Brainstorm: cell("
|
|
4906
|
-
Objective: cell("
|
|
4907
|
-
"Doc index": cell("
|
|
4905
|
+
Brainstorm: cell("sonnet", "medium"),
|
|
4906
|
+
Objective: cell("sonnet", "medium"),
|
|
4907
|
+
"Doc index": cell("opus", "medium")
|
|
4908
4908
|
},
|
|
4909
4909
|
{
|
|
4910
4910
|
Brainstorm: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4914,11 +4914,11 @@ var init_runtime_defaults = __esm({
|
|
|
4914
4914
|
),
|
|
4915
4915
|
reverse: flow(
|
|
4916
4916
|
{
|
|
4917
|
-
Scan: cell("
|
|
4918
|
-
"Docs teknis": cell("
|
|
4919
|
-
Wawancara: cell("
|
|
4920
|
-
"Konvensi & index": cell("
|
|
4921
|
-
"Serah terima": cell("
|
|
4917
|
+
Scan: cell("sonnet", "medium"),
|
|
4918
|
+
"Docs teknis": cell("sonnet", "medium"),
|
|
4919
|
+
Wawancara: cell("sonnet", "medium"),
|
|
4920
|
+
"Konvensi & index": cell("sonnet", "medium"),
|
|
4921
|
+
"Serah terima": cell("sonnet", "medium")
|
|
4922
4922
|
},
|
|
4923
4923
|
{
|
|
4924
4924
|
Scan: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4930,8 +4930,8 @@ var init_runtime_defaults = __esm({
|
|
|
4930
4930
|
),
|
|
4931
4931
|
prd: flow(
|
|
4932
4932
|
{
|
|
4933
|
-
Brainstorm: cell("
|
|
4934
|
-
PRD: cell("
|
|
4933
|
+
Brainstorm: cell("sonnet", "medium"),
|
|
4934
|
+
PRD: cell("opus", "medium")
|
|
4935
4935
|
},
|
|
4936
4936
|
{
|
|
4937
4937
|
Brainstorm: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4940,8 +4940,8 @@ var init_runtime_defaults = __esm({
|
|
|
4940
4940
|
),
|
|
4941
4941
|
audit: flow(
|
|
4942
4942
|
{
|
|
4943
|
-
Audit: cell("
|
|
4944
|
-
Laporan: cell("
|
|
4943
|
+
Audit: cell("sonnet", "medium"),
|
|
4944
|
+
Laporan: cell("sonnet", "medium")
|
|
4945
4945
|
},
|
|
4946
4946
|
{
|
|
4947
4947
|
Audit: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4950,8 +4950,8 @@ var init_runtime_defaults = __esm({
|
|
|
4950
4950
|
),
|
|
4951
4951
|
breakdown: flow(
|
|
4952
4952
|
{
|
|
4953
|
-
Analisis: cell("
|
|
4954
|
-
Breakdown: cell("
|
|
4953
|
+
Analisis: cell("opus", "medium"),
|
|
4954
|
+
Breakdown: cell("opus", "medium")
|
|
4955
4955
|
},
|
|
4956
4956
|
{
|
|
4957
4957
|
Analisis: cell("gpt-5.6-sol", "medium"),
|
|
@@ -4960,8 +4960,8 @@ var init_runtime_defaults = __esm({
|
|
|
4960
4960
|
),
|
|
4961
4961
|
goal: flow(
|
|
4962
4962
|
{
|
|
4963
|
-
Goal: cell("
|
|
4964
|
-
Verifikasi: cell("
|
|
4963
|
+
Goal: cell("sonnet", "medium"),
|
|
4964
|
+
Verifikasi: cell("sonnet", "medium")
|
|
4965
4965
|
},
|
|
4966
4966
|
{
|
|
4967
4967
|
Goal: cell("gpt-5.6-terra", "medium"),
|
|
@@ -4969,7 +4969,7 @@ var init_runtime_defaults = __esm({
|
|
|
4969
4969
|
}
|
|
4970
4970
|
),
|
|
4971
4971
|
no_effort: flow(
|
|
4972
|
-
{ Kerjakan: cell("
|
|
4972
|
+
{ Kerjakan: cell("haiku", "low") },
|
|
4973
4973
|
{ Kerjakan: cell("gpt-5.6-terra", "low") },
|
|
4974
4974
|
false
|
|
4975
4975
|
)
|
|
@@ -4995,11 +4995,17 @@ function replaceModelCatalog(claude, codex2) {
|
|
|
4995
4995
|
MODELS = claude;
|
|
4996
4996
|
CODEX_MODELS = codex2;
|
|
4997
4997
|
}
|
|
4998
|
+
function claudeModel(id2) {
|
|
4999
|
+
return MODELS.find((m) => m.id === id2) ?? MODELS.find((m) => m.resolved === id2);
|
|
5000
|
+
}
|
|
5001
|
+
function subagentClaudeModels() {
|
|
5002
|
+
return MODELS.filter((m) => m.id !== CLAUDE_DEFAULT_ALIAS);
|
|
5003
|
+
}
|
|
4998
5004
|
function claudeEfforts(modelId) {
|
|
4999
|
-
return
|
|
5005
|
+
return claudeModel(modelId)?.efforts ?? EFFORTS;
|
|
5000
5006
|
}
|
|
5001
5007
|
function coerceClaudeEffort(modelId, effort2) {
|
|
5002
|
-
const levels =
|
|
5008
|
+
const levels = claudeModel(modelId)?.efforts;
|
|
5003
5009
|
if (!levels?.length || levels.includes(effort2)) return effort2;
|
|
5004
5010
|
return levels.includes("xhigh") ? "xhigh" : levels[0];
|
|
5005
5011
|
}
|
|
@@ -5023,7 +5029,7 @@ function cmpVersion(a, b) {
|
|
|
5023
5029
|
}
|
|
5024
5030
|
return 0;
|
|
5025
5031
|
}
|
|
5026
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5032
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, CLAUDE_DEFAULT_ALIAS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, PHASE_OVERRIDE_LIMITS, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5027
5033
|
var init_entities = __esm({
|
|
5028
5034
|
"../shared/src/entities.ts"() {
|
|
5029
5035
|
"use strict";
|
|
@@ -5151,12 +5157,13 @@ var init_entities = __esm({
|
|
|
5151
5157
|
"fanfare"
|
|
5152
5158
|
];
|
|
5153
5159
|
MODELS = [
|
|
5154
|
-
{ id: "
|
|
5155
|
-
{ id: "
|
|
5156
|
-
{ id: "
|
|
5157
|
-
{ id: "
|
|
5158
|
-
{ id: "
|
|
5160
|
+
{ id: "default", label: "Default (rekomendasi CLI)" },
|
|
5161
|
+
{ id: "opus", label: "Opus" },
|
|
5162
|
+
{ id: "sonnet", label: "Sonnet" },
|
|
5163
|
+
{ id: "haiku", label: "Haiku" },
|
|
5164
|
+
{ id: "fable", label: "Fable" }
|
|
5159
5165
|
];
|
|
5166
|
+
CLAUDE_DEFAULT_ALIAS = "default";
|
|
5160
5167
|
EFFORTS = ["xhigh", "high", "medium", "low", "max", "ultracode"];
|
|
5161
5168
|
E_5_6 = ["ultra", "max", "xhigh", "high", "medium", "low"];
|
|
5162
5169
|
E_LUNA = ["max", "xhigh", "high", "medium", "low"];
|
|
@@ -5207,7 +5214,7 @@ var init_entities = __esm({
|
|
|
5207
5214
|
enabled: external_exports.boolean().default(false),
|
|
5208
5215
|
agent: zAgent.default("claude"),
|
|
5209
5216
|
// Lenient z.string() seperti `model`/`effort` di akar: katalog ditegakkan UI, bukan server.
|
|
5210
|
-
model: external_exports.string().default("
|
|
5217
|
+
model: external_exports.string().default("opus"),
|
|
5211
5218
|
effort: external_exports.string().default("xhigh")
|
|
5212
5219
|
});
|
|
5213
5220
|
CONFLICT_DEFAULTS = zConflict.parse({});
|
|
@@ -5257,7 +5264,7 @@ var init_entities = __esm({
|
|
|
5257
5264
|
enabled: external_exports.boolean().default(false),
|
|
5258
5265
|
brainstormPerMonth: external_exports.number().int().min(0).max(1e3).default(2),
|
|
5259
5266
|
askPerMonth: external_exports.number().int().min(0).max(1e4).default(30),
|
|
5260
|
-
model: external_exports.string().default("
|
|
5267
|
+
model: external_exports.string().default("opus"),
|
|
5261
5268
|
effort: external_exports.string().default("high"),
|
|
5262
5269
|
timeoutSec: external_exports.number().int().min(10).max(900).default(180)
|
|
5263
5270
|
});
|
|
@@ -5266,11 +5273,15 @@ var init_entities = __esm({
|
|
|
5266
5273
|
model: external_exports.string().nullable().default(null),
|
|
5267
5274
|
effort: external_exports.string().nullable().default(null)
|
|
5268
5275
|
});
|
|
5276
|
+
PHASE_OVERRIDE_LIMITS = { model: 200, effort: 64, phase: 64, phases: 32 };
|
|
5269
5277
|
zPhaseOverride = external_exports.object({
|
|
5270
|
-
model: external_exports.string().optional(),
|
|
5271
|
-
effort: external_exports.string().optional()
|
|
5278
|
+
model: external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.model).optional(),
|
|
5279
|
+
effort: external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.effort).optional()
|
|
5272
5280
|
});
|
|
5273
|
-
zPhaseOverrides = external_exports.record(external_exports.string(), zPhaseOverride)
|
|
5281
|
+
zPhaseOverrides = external_exports.record(external_exports.string().min(1).max(PHASE_OVERRIDE_LIMITS.phase), zPhaseOverride).refine(
|
|
5282
|
+
(o) => Object.keys(o).length <= PHASE_OVERRIDE_LIMITS.phases,
|
|
5283
|
+
{ message: `phaseOverrides maksimal ${PHASE_OVERRIDE_LIMITS.phases} fase` }
|
|
5284
|
+
);
|
|
5274
5285
|
zFlowOrchestration = external_exports.object({
|
|
5275
5286
|
enabled: external_exports.boolean().default(true),
|
|
5276
5287
|
claude: external_exports.record(external_exports.string(), zPhaseCell).default({}),
|
|
@@ -5289,7 +5300,7 @@ var init_entities = __esm({
|
|
|
5289
5300
|
});
|
|
5290
5301
|
ORCHESTRATION_DEFAULTS = zOrchestration.parse(BUILTIN_ORCHESTRATION_DEFAULTS);
|
|
5291
5302
|
zSetting = external_exports.object({
|
|
5292
|
-
model: external_exports.string().default("
|
|
5303
|
+
model: external_exports.string().default("sonnet"),
|
|
5293
5304
|
effort: external_exports.string().default("medium"),
|
|
5294
5305
|
autoDefault: external_exports.boolean(),
|
|
5295
5306
|
autoScaffold: external_exports.boolean(),
|
|
@@ -5394,7 +5405,7 @@ function codexNativeAgentsSupported(version) {
|
|
|
5394
5405
|
return parsed ? cmpVersion(parsed, CODEX_NATIVE_AGENTS_MIN_CLIENT) >= 0 : false;
|
|
5395
5406
|
}
|
|
5396
5407
|
function resolvePhasePlan(input) {
|
|
5397
|
-
const cfg = input.orchestration
|
|
5408
|
+
const cfg = (input.orchestration ?? ORCHESTRATION_DEFAULTS)[input.flow];
|
|
5398
5409
|
if (!input.nativeAgents || cfg?.enabled === false) return null;
|
|
5399
5410
|
const cells = cfg?.[input.runtime] ?? {};
|
|
5400
5411
|
const coerce3 = input.runtime === "codex" ? coerceCodexEffort : coerceClaudeEffort;
|
|
@@ -5404,7 +5415,8 @@ function resolvePhasePlan(input) {
|
|
|
5404
5415
|
phases: FLOW_PHASES[input.flow].map((phase) => {
|
|
5405
5416
|
const cell2 = cells[phase];
|
|
5406
5417
|
const override = input.phaseOverrides?.[phase];
|
|
5407
|
-
const
|
|
5418
|
+
const picked = override?.model ?? cell2?.model ?? input.orchestrator.model;
|
|
5419
|
+
const model = input.runtime === "claude" && picked === CLAUDE_DEFAULT_ALIAS ? CLAUDE_SUBAGENT_INHERIT : picked;
|
|
5408
5420
|
return {
|
|
5409
5421
|
phase,
|
|
5410
5422
|
agentName: phaseAgentName(phase),
|
|
@@ -5414,12 +5426,13 @@ function resolvePhasePlan(input) {
|
|
|
5414
5426
|
})
|
|
5415
5427
|
};
|
|
5416
5428
|
}
|
|
5417
|
-
var CODEX_NATIVE_AGENTS_MIN_CLIENT;
|
|
5429
|
+
var CLAUDE_SUBAGENT_INHERIT, CODEX_NATIVE_AGENTS_MIN_CLIENT;
|
|
5418
5430
|
var init_orchestration_plan = __esm({
|
|
5419
5431
|
"../shared/src/orchestration-plan.ts"() {
|
|
5420
5432
|
"use strict";
|
|
5421
5433
|
init_entities();
|
|
5422
5434
|
init_orchestration();
|
|
5435
|
+
CLAUDE_SUBAGENT_INHERIT = "inherit";
|
|
5423
5436
|
CODEX_NATIVE_AGENTS_MIN_CLIENT = "0.151.0";
|
|
5424
5437
|
}
|
|
5425
5438
|
});
|
|
@@ -6318,14 +6331,18 @@ var init_builtin_agents = __esm({
|
|
|
6318
6331
|
|
|
6319
6332
|
// ../shared/src/agent-catalog.ts
|
|
6320
6333
|
function modelsForRuntime(rt) {
|
|
6321
|
-
const claude =
|
|
6334
|
+
const claude = subagentClaudeModels().map((m) => ({ id: m.id, label: m.label, runtime: "claude" }));
|
|
6322
6335
|
const codex2 = CODEX_MODELS.map((m) => ({ id: m.id, label: m.label, runtime: "codex" }));
|
|
6323
6336
|
if (rt === "claude") return claude;
|
|
6324
6337
|
if (rt === "codex") return codex2;
|
|
6325
6338
|
return [...claude, ...codex2];
|
|
6326
6339
|
}
|
|
6340
|
+
function modelKnownForRuntime(rt, model) {
|
|
6341
|
+
if (modelsForRuntime(rt).some((m) => m.id === model)) return true;
|
|
6342
|
+
return rt !== "codex" && model !== "default" && claudeModel(model)?.resolved === model;
|
|
6343
|
+
}
|
|
6327
6344
|
function effortsForRuntimeModel(runtime, model) {
|
|
6328
|
-
if (runtime === "claude" || !runtime && model &&
|
|
6345
|
+
if (runtime === "claude" || !runtime && model && claudeModel(model)) {
|
|
6329
6346
|
return [...claudeEfforts(model ?? "")];
|
|
6330
6347
|
}
|
|
6331
6348
|
const codexModel2 = model ? CODEX_MODELS.find((entry) => entry.id === model) : void 0;
|
|
@@ -10465,7 +10482,7 @@ var init_settings = __esm({
|
|
|
10465
10482
|
{
|
|
10466
10483
|
name: "hanoman_settings_set",
|
|
10467
10484
|
title: "Simpan setelan instance",
|
|
10468
|
-
description: "Menyimpan setelan instance. MENGGANTI blok penuh \u2014 kirim hasil hanoman_settings_get yang sudah diubah, bukan potongan. Setelan ini berlaku untuk SETIAP sesi baru di seluruh workspace. Menyentuh blok Telegram akan memuat ulang gateway.",
|
|
10485
|
+
description: "Menyimpan setelan instance. MENGGANTI blok penuh \u2014 kirim hasil hanoman_settings_get yang sudah diubah, bukan potongan. Panggil hanoman_settings_get TEPAT sebelum tool ini (jangan memakai hasil lama) dan ubah HANYA field yang dimaksud: setiap model/effort/sel orkestrasi yang berbeda dari nilai tersimpan ditandai milik operator dan tak lagi diperbarui default bawaan saat update. Setelan ini berlaku untuk SETIAP sesi baru di seluruh workspace. Menyentuh blok Telegram akan memuat ulang gateway.",
|
|
10469
10486
|
inputSchema: obj({ properties: { settings: WHOLE_BLOCK("setelan") }, required: ["settings"] }),
|
|
10470
10487
|
mode: "write",
|
|
10471
10488
|
capability: "settings:write",
|
|
@@ -13048,11 +13065,14 @@ function phaseSkillsFor(flow2, phase, method) {
|
|
|
13048
13065
|
}
|
|
13049
13066
|
function orchestratorClause(plan, o = {}) {
|
|
13050
13067
|
const codex2 = plan.runtime === "codex";
|
|
13051
|
-
const call = codex2 ? "spawn_agent" :
|
|
13052
|
-
const resume = codex2 ? "send_input ke agent id yang sama" : "SendMessage ke agent ID yang kamu
|
|
13053
|
-
const describeRule = codex2 ? "Deskripsi/label pemanggilan diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas." : "Deskripsi pemanggilan (parameter `description`) diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas.";
|
|
13068
|
+
const call = codex2 ? "spawn_agent" : `tool Agent dengan parameter \`subagent_type\` berisi persis nama agen fase di daftar (mis. \`${plan.phases[0]?.agentName ?? "hanoman-fase-<fase>"}\`)`;
|
|
13069
|
+
const resume = codex2 ? "send_input ke agent id yang sama" : "SendMessage ke agent ID yang kamu catat";
|
|
13070
|
+
const describeRule = codex2 ? "Deskripsi/label pemanggilan diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas." : "Deskripsi pemanggilan (parameter `description`) diisi persis `Fase <Nama Fase>` \u2014 tanpa nomor urut, dan BUKAN baris pertama blok serah-terima di atas.\nJANGAN pernah memanggil tool Agent tanpa `subagent_type` atau dengan `general-purpose`: fase itu jatuh ke agen umum ber-model orchestrator dan model/effort fasenya hilang. Sebelum memanggil, periksa ulang bahwa `subagent_type` = nama agen fase itu; bila tool menolaknya, itu galat langkah 3 \u2014 bukan alasan beralih ke agen umum. Catat agent ID yang dikembalikan hasil pemanggilan: itulah satu-satunya pegangan untuk melanjutkan agen yang SAMA. Melanjutkan agen itu (relay jawaban langkah 4) HANYA lewat SendMessage \u2014 tool Agent tak punya parameter `to`, dan memanggilnya lagi melahirkan agen BARU tanpa konteks (duplikat).";
|
|
13071
|
+
const retry = codex2 ? "" : " (pemanggilan tool Agent BARU dengan `subagent_type` yang sama)";
|
|
13054
13072
|
const askEscalation = codex2 ? "tanyakan di terminal ini lalu tunggu jawaban" : "tanyakan lewat AskUserQuestion";
|
|
13055
|
-
const askDecisions = codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : "lewat AskUserQuestion \u2014 tool itu memuat paling banyak 4 pertanyaan per panggilan, jadi pecah sisanya ke panggilan berikutnya sampai semuanya terjawab
|
|
13073
|
+
const askDecisions = codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : "lewat AskUserQuestion \u2014 tool itu memuat paling banyak 4 pertanyaan per panggilan, jadi pecah sisanya ke panggilan berikutnya sampai semuanya terjawab";
|
|
13074
|
+
const turnTaking = plan.flow === "reverse" || plan.flow === "scaffold" || plan.flow === "prd" ? " Fase yang bergiliran dengan manusia (Wawancara, Brainstorm prd/scaffold) melapor satu topik per putaran \u2014 relay tiap putaran dengan cara yang sama, bawa konteks & opsi dari laporannya, dan biarkan jawaban bebas." : "";
|
|
13075
|
+
const handoff = plan.phases.some((p3) => p3.phase === "Serah terima") ? "Sesudah fase Serah terima tercatat, tampilkan bagian `Ringkasan serah terima:` dari laporannya APA ADANYA ke manusia di terminal ini (teks biasa \u2014 informasi, bukan pertanyaan) sebelum menutup pekerjaan." : "";
|
|
13056
13076
|
const list2 = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${p3.model} \xB7 ${p3.effort}`).join("\n");
|
|
13057
13077
|
return [
|
|
13058
13078
|
"Sesi ini ORCHESTRATOR. Setiap fase dikerjakan subagent fase miliknya dengan model & effort yang sudah terkunci di definisinya \u2014 kamu TIDAK mengerjakan isi fase sendiri.",
|
|
@@ -13070,9 +13090,10 @@ Percobaan: <k>/2
|
|
|
13070
13090
|
|
|
13071
13091
|
${describeRule}`,
|
|
13072
13092
|
'2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.',
|
|
13073
|
-
`3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
|
|
13074
|
-
`4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya
|
|
13093
|
+
`3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama${retry} dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
|
|
13094
|
+
`4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.${turnTaking}`,
|
|
13075
13095
|
o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Plan \u2192 Execute.' : "",
|
|
13096
|
+
handoff,
|
|
13076
13097
|
"DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
|
|
13077
13098
|
'Pekerjaan ini BELUM selesai sampai SEMUA fase di daftar di atas punya baris `done` atau `skipped` di $HANOMAN_PHASE_FILE \u2014 JANGAN commit/push final atau menyatakan tuntas sebelum itu. Periksa dengan `cat "$HANOMAN_PHASE_FILE"` sebelum commit terakhir.'
|
|
13078
13099
|
].filter(Boolean).join("\n\n");
|
|
@@ -13116,6 +13137,9 @@ function continuePrompt(flow2, spec, branchTo, autonomy, verifyScope, method, at
|
|
|
13116
13137
|
return [
|
|
13117
13138
|
head,
|
|
13118
13139
|
`JANGAN mengulang fase awal \u2014 spec & plan sudah ada di ${m.planDir}/**. Lanjutkan HANYA fase Execute lewat subagent fasenya.`,
|
|
13140
|
+
// S3 · berkas fase tak pernah dikosongkan: baris `Execute done` run lama masih ada dan akan
|
|
13141
|
+
// memenuhi gerbang penutup sebelum agen Execute sesi ini bekerja sama sekali.
|
|
13142
|
+
"$HANOMAN_PHASE_FILE masih memuat baris `Execute done` LAMA dari sesi sebelumnya \u2014 baris itu TIDAK berlaku untuk sesi ini dan tidak memenuhi gerbang penutup. Sesudah agen fase Execute SESI INI melapor `Status: selesai`, tulis baris `Execute done` BARU (verifikasi `tail -1`); commit/push final baru sah sesudah baris baru itu.",
|
|
13119
13143
|
orchestratorClause(plan),
|
|
13120
13144
|
autonomyClause(autonomy),
|
|
13121
13145
|
attachmentClause(attachments),
|
|
@@ -13607,7 +13631,7 @@ var init_git = __esm({
|
|
|
13607
13631
|
});
|
|
13608
13632
|
|
|
13609
13633
|
// ../runner/src/settings.ts
|
|
13610
|
-
var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, guardSettings;
|
|
13634
|
+
var EVENT_SPOOL_SCRIPT, EVENT_HOOK_COMMAND, BUSY_SNAPSHOT_SCRIPT, busySnapshotCommand, guardSettings;
|
|
13611
13635
|
var init_settings2 = __esm({
|
|
13612
13636
|
"../runner/src/settings.ts"() {
|
|
13613
13637
|
"use strict";
|
|
@@ -13629,6 +13653,11 @@ var init_settings2 = __esm({
|
|
|
13629
13653
|
'${HANOMAN_EVENT_HOST:+-H "host: $HANOMAN_EVENT_HOST"}',
|
|
13630
13654
|
"--data-binary @- >/dev/null 2>&1; fi; exit 0"
|
|
13631
13655
|
].join(" ");
|
|
13656
|
+
BUSY_SNAPSHOT_SCRIPT = [
|
|
13657
|
+
'let d=""',
|
|
13658
|
+
'process.stdin.on("data",c=>d+=c).on("end",()=>{let n=0;try{n=(JSON.parse(d).background_tasks||[]).filter(t=>t&&t.type==="subagent"&&t.status==="running").length}catch{}require("fs").writeFileSync(process.argv[1],"1\\n".repeat(n))})'
|
|
13659
|
+
].join(";");
|
|
13660
|
+
busySnapshotCommand = (quotedSubFile) => `node -e '${BUSY_SNAPSHOT_SCRIPT}' ${quotedSubFile} >/dev/null 2>&1`;
|
|
13632
13661
|
guardSettings = (decisionFile, goal, eventHook, subagentStatusLine) => {
|
|
13633
13662
|
const hooks2 = {};
|
|
13634
13663
|
if (decisionFile) {
|
|
@@ -13649,11 +13678,7 @@ var init_settings2 = __esm({
|
|
|
13649
13678
|
if (decisionFile) {
|
|
13650
13679
|
const sub = `'${decisionFile.split("'").join("'\\''")}.sub'`;
|
|
13651
13680
|
const f = `'${decisionFile.split("'").join("'\\''")}'`;
|
|
13652
|
-
hooks2.
|
|
13653
|
-
hooks2.SubagentStop.push({ hooks: [{
|
|
13654
|
-
type: "command",
|
|
13655
|
-
command: `[ -s ${sub} ] && { sed '$d' ${sub} > ${sub}.t; mv ${sub}.t ${sub}; }; exit 0`
|
|
13656
|
-
}] });
|
|
13681
|
+
(hooks2.Stop ??= []).push({ hooks: [{ type: "command", command: `${busySnapshotCommand(sub)}; exit 0` }] });
|
|
13657
13682
|
hooks2.Notification = [{ hooks: [{
|
|
13658
13683
|
type: "command",
|
|
13659
13684
|
command: `m=$(cat); if [ -s ${sub} ]; then echo "$m" | grep -qiE 'permission|needs.?input'; else echo "$m" | grep -qiE 'idle|permission|waiting for|needs.?input'; fi && { [ -s ${f} ] || date +%s > ${f}; } || true`
|
|
@@ -13661,7 +13686,7 @@ var init_settings2 = __esm({
|
|
|
13661
13686
|
hooks2.UserPromptSubmit = [{ hooks: [{ type: "command", command: `: > ${f}; : > ${sub}` }] }];
|
|
13662
13687
|
}
|
|
13663
13688
|
}
|
|
13664
|
-
if (goal) hooks2.Stop
|
|
13689
|
+
if (goal) (hooks2.Stop ??= []).push({ hooks: [{ type: "prompt", prompt: goal }] });
|
|
13665
13690
|
return {
|
|
13666
13691
|
hooks: hooks2,
|
|
13667
13692
|
...subagentStatusLine ? { subagentStatusLine: { type: "command", command: subagentStatusLine } } : {}
|
|
@@ -13840,6 +13865,14 @@ var init_agent_cli = __esm({
|
|
|
13840
13865
|
});
|
|
13841
13866
|
|
|
13842
13867
|
// ../runner/src/custom-agents.ts
|
|
13868
|
+
function phasePromptOf(def2, file) {
|
|
13869
|
+
if (def2.context === void 0) return def2.instructions;
|
|
13870
|
+
const body = file && file.context === def2.context ? `Konteks sesi ini (backlog/project/brief/PRD) ada di berkas \`${file.path}\`. Baca berkas itu UTUH dengan tool Read SEBELUM mengerjakan apa pun \u2014 isinya bagian dari instruksimu, bukan lampiran opsional. Berkas itu hanya-baca dan berada di luar worktree; jangan menyalin atau meng-commit-nya.` : def2.context;
|
|
13871
|
+
return `${def2.instructions}
|
|
13872
|
+
|
|
13873
|
+
${PHASE_CONTEXT_HEADER}
|
|
13874
|
+
${body}`;
|
|
13875
|
+
}
|
|
13843
13876
|
function agentPromptOf(def2, roster, runtime = "claude") {
|
|
13844
13877
|
const can = liveMentions(def2, roster);
|
|
13845
13878
|
const contract = [
|
|
@@ -13880,7 +13913,7 @@ function renderAgentsJson(defs, options2 = {}) {
|
|
|
13880
13913
|
if (d.kind === "phase") {
|
|
13881
13914
|
out4[d.name] = {
|
|
13882
13915
|
description: d.description,
|
|
13883
|
-
prompt: d.
|
|
13916
|
+
prompt: phasePromptOf(d, options2.phaseContextFile),
|
|
13884
13917
|
...d.model ? { model: d.model } : {},
|
|
13885
13918
|
...d.effort ? { effort: d.effort } : {}
|
|
13886
13919
|
};
|
|
@@ -13923,13 +13956,14 @@ function agentDelegationClause(defs, runtime = "claude") {
|
|
|
13923
13956
|
""
|
|
13924
13957
|
].join("\n");
|
|
13925
13958
|
}
|
|
13926
|
-
var liveMentions, policyClause, workLimitClause, handoffClause, READ_ONLY_TOOLS;
|
|
13959
|
+
var PHASE_CONTEXT_HEADER, liveMentions, policyClause, workLimitClause, handoffClause, READ_ONLY_TOOLS;
|
|
13927
13960
|
var init_custom_agents = __esm({
|
|
13928
13961
|
"../runner/src/custom-agents.ts"() {
|
|
13929
13962
|
"use strict";
|
|
13930
13963
|
init_src();
|
|
13931
13964
|
init_code_style();
|
|
13932
13965
|
init_prompt();
|
|
13966
|
+
PHASE_CONTEXT_HEADER = "=== KONTEKS ===";
|
|
13933
13967
|
liveMentions = (def2, roster) => {
|
|
13934
13968
|
if (def2.workspacePolicy === "read-only") return [];
|
|
13935
13969
|
const names = new Set(roster.map((r) => r.name));
|
|
@@ -14034,11 +14068,36 @@ function codexNativeVersionProbe(env, codexBin3 = env.HANOMAN_CODEX_BIN ?? "code
|
|
|
14034
14068
|
]
|
|
14035
14069
|
};
|
|
14036
14070
|
}
|
|
14071
|
+
function tomlBasicString(value) {
|
|
14072
|
+
let out4 = '"';
|
|
14073
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
14074
|
+
const code = value.charCodeAt(i);
|
|
14075
|
+
if (code >= 55296 && code <= 56319) {
|
|
14076
|
+
const next = value.charCodeAt(i + 1);
|
|
14077
|
+
if (next >= 56320 && next <= 57343) {
|
|
14078
|
+
out4 += value[i] + value[i + 1];
|
|
14079
|
+
i += 1;
|
|
14080
|
+
} else {
|
|
14081
|
+
out4 += "\uFFFD";
|
|
14082
|
+
}
|
|
14083
|
+
continue;
|
|
14084
|
+
}
|
|
14085
|
+
if (code >= 56320 && code <= 57343) {
|
|
14086
|
+
out4 += "\uFFFD";
|
|
14087
|
+
continue;
|
|
14088
|
+
}
|
|
14089
|
+
const short = TOML_SHORT_ESCAPES[code];
|
|
14090
|
+
if (short) out4 += short;
|
|
14091
|
+
else if (code < 32 || code === 127) out4 += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
14092
|
+
else out4 += value[i];
|
|
14093
|
+
}
|
|
14094
|
+
return `${out4}"`;
|
|
14095
|
+
}
|
|
14037
14096
|
function renderCodexAgentToml(def2, roster, options2 = {}) {
|
|
14038
14097
|
const lines2 = [
|
|
14039
14098
|
`name = ${tomlString(def2.name)}`,
|
|
14040
14099
|
`description = ${tomlString(def2.description)}`,
|
|
14041
|
-
`developer_instructions = ${tomlString(def2.kind === "phase" ? def2
|
|
14100
|
+
`developer_instructions = ${tomlString(def2.kind === "phase" ? phasePromptOf(def2) : agentPromptOf(def2, roster, "codex") + (options2.promptSuffix ?? ""))}`,
|
|
14042
14101
|
...def2.model ? [`model = ${tomlString(def2.model)}`] : [],
|
|
14043
14102
|
...def2.effort ? [`model_reasoning_effort = ${tomlString(def2.effort)}`] : [],
|
|
14044
14103
|
...def2.workspacePolicy === "read-only" ? ['sandbox_mode = "read-only"'] : []
|
|
@@ -14122,7 +14181,7 @@ function materializeCodexAgents(defs, tempDir, options2 = {}) {
|
|
|
14122
14181
|
liveDefs
|
|
14123
14182
|
};
|
|
14124
14183
|
}
|
|
14125
|
-
var shellQuote, tomlString, tomlKey, safeFilename;
|
|
14184
|
+
var shellQuote, TOML_SHORT_ESCAPES, tomlString, tomlKey, safeFilename;
|
|
14126
14185
|
var init_codex_agent_config = __esm({
|
|
14127
14186
|
"../runner/src/codex-agent-config.ts"() {
|
|
14128
14187
|
"use strict";
|
|
@@ -14130,8 +14189,17 @@ var init_codex_agent_config = __esm({
|
|
|
14130
14189
|
init_custom_agents();
|
|
14131
14190
|
init_runtime_profile();
|
|
14132
14191
|
shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
14133
|
-
|
|
14134
|
-
|
|
14192
|
+
TOML_SHORT_ESCAPES = {
|
|
14193
|
+
8: "\\b",
|
|
14194
|
+
9: "\\t",
|
|
14195
|
+
10: "\\n",
|
|
14196
|
+
12: "\\f",
|
|
14197
|
+
13: "\\r",
|
|
14198
|
+
34: '\\"',
|
|
14199
|
+
92: "\\\\"
|
|
14200
|
+
};
|
|
14201
|
+
tomlString = tomlBasicString;
|
|
14202
|
+
tomlKey = tomlBasicString;
|
|
14135
14203
|
safeFilename = (name2) => name2.replace(/[^a-z0-9-]/gi, "-");
|
|
14136
14204
|
}
|
|
14137
14205
|
});
|
|
@@ -14313,6 +14381,14 @@ var init_agent_definition = __esm({
|
|
|
14313
14381
|
});
|
|
14314
14382
|
|
|
14315
14383
|
// ../runner/src/phase-agents.ts
|
|
14384
|
+
function resumeNoteFor(r) {
|
|
14385
|
+
return "=== MELANJUTKAN ===\n" + [
|
|
14386
|
+
"Sesi ini MELANJUTKAN pekerjaan sesi sebelumnya untuk tugas yang sama \u2014 bukan memulai dari nol.",
|
|
14387
|
+
r.worktreeKept ? "Worktree ini adalah worktree sesi sebelumnya apa adanya \u2014 termasuk perubahan yang belum di-commit." : "Worktree ini DIBANGUN ULANG dari tip branch sesi: commit sesi sebelumnya ada, tetapi perubahan yang belum sempat di-commit TIDAK ada.",
|
|
14388
|
+
r.recorded.length ? `Fase yang SUDAH tercatat di $HANOMAN_PHASE_FILE: ${r.recorded.join(" \xB7 ")}.` : "",
|
|
14389
|
+
"Sebelum menulis apa pun: baca `git log --oneline` dan `git status`, lalu artefak fasemu yang mungkin sudah ada (dokumen spec/plan/docs untuk tugas ini). Lanjutkan & perbarui berkas itu \u2014 JANGAN membuat dokumen baru kedua dan JANGAN menulis ulang yang sudah ada."
|
|
14390
|
+
].filter(Boolean).join(" ");
|
|
14391
|
+
}
|
|
14316
14392
|
function fromAuditOf(payload) {
|
|
14317
14393
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
|
|
14318
14394
|
const value = payload.fromAudit;
|
|
@@ -14346,11 +14422,11 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
14346
14422
|
}
|
|
14347
14423
|
}
|
|
14348
14424
|
function projectGuide(flow2, phase, ctx) {
|
|
14349
|
-
if (flow2 === "reverse") return guideLine(REVERSE_PHASE_GUIDE, phase);
|
|
14350
|
-
if (flow2 === "scaffold") return guideLine(SCAFFOLD_PHASE_GUIDE, phase);
|
|
14425
|
+
if (flow2 === "reverse") return forPhaseAgent(guideLine(REVERSE_PHASE_GUIDE, phase));
|
|
14426
|
+
if (flow2 === "scaffold") return forPhaseAgent(guideLine(SCAFFOLD_PHASE_GUIDE, phase));
|
|
14351
14427
|
const slug = ctx.prd?.slug ?? flow2;
|
|
14352
14428
|
const lines2 = flow2 === "prd" ? prdPhaseLines(slug) : breakdownPhaseLines(slug, ctx.prd?.title ?? slug);
|
|
14353
|
-
return lines2[phase] ?? "";
|
|
14429
|
+
return forPhaseAgent(lines2[phase] ?? "");
|
|
14354
14430
|
}
|
|
14355
14431
|
function phaseAgentInstructions(entry, index, plan, ctx) {
|
|
14356
14432
|
const { flow: flow2, method } = ctx;
|
|
@@ -14371,8 +14447,9 @@ ${REVERSE_STANDARD}` : "",
|
|
|
14371
14447
|
PHASE_AGENT_AUTONOMY,
|
|
14372
14448
|
PHASE_AGENT_RULES,
|
|
14373
14449
|
PHASE_AGENT_REPORT,
|
|
14374
|
-
|
|
14375
|
-
|
|
14450
|
+
// S2 · catatan resume kecil & khas sesi → tetap di instruksi; konteks bersama yang besar dirakit
|
|
14451
|
+
// terpisah oleh `phasePromptOf` (T2) supaya tak disalin ke tiap agen.
|
|
14452
|
+
ctx.resume ? resumeNoteFor(ctx.resume) : ""
|
|
14376
14453
|
].filter(Boolean).join("\n\n");
|
|
14377
14454
|
}
|
|
14378
14455
|
function buildPhaseAgents(plan, ctx) {
|
|
@@ -14382,13 +14459,16 @@ function buildPhaseAgents(plan, ctx) {
|
|
|
14382
14459
|
name: entry.agentName,
|
|
14383
14460
|
description: `Fase ${entry.phase} flow ${plan.flow} hanoman \u2014 hanya dipanggil orchestrator sesi ini.`,
|
|
14384
14461
|
instructions: phaseAgentInstructions(entry, index, plan, ctx),
|
|
14462
|
+
// T2 · konteks bersama dibawa TERPISAH (lihat `phasePromptOf`): claude merujuknya lewat satu
|
|
14463
|
+
// berkas di temp dir sesi, codex tetap inline.
|
|
14464
|
+
context: ctx.context,
|
|
14385
14465
|
tools: null,
|
|
14386
14466
|
model: entry.model,
|
|
14387
14467
|
effort: entry.effort,
|
|
14388
14468
|
mentions: []
|
|
14389
14469
|
}));
|
|
14390
14470
|
}
|
|
14391
|
-
var PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE;
|
|
14471
|
+
var PROJECT_FLOWS, DOC_WRITING_PHASES, PHASE_AGENT_AUTONOMY, PHASE_AGENT_RULES, PHASE_AGENT_REPORT, ATTACHMENT_NOTE, TERMINAL_ASK, PHASE_AGENT_ASK, TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF, forPhaseAgent;
|
|
14392
14472
|
var init_phase_agents = __esm({
|
|
14393
14473
|
"../runner/src/phase-agents.ts"() {
|
|
14394
14474
|
"use strict";
|
|
@@ -14403,7 +14483,11 @@ var init_phase_agents = __esm({
|
|
|
14403
14483
|
"- JANGAN menulis `$HANOMAN_PHASE_FILE` \u2014 orchestrator satu-satunya penulis marker fase.",
|
|
14404
14484
|
"- JANGAN `git push` \u2014 push milik orchestrator. Commit artefak fasemu sendiri sebelum melapor.",
|
|
14405
14485
|
`- JANGAN memanggil agen berawalan \`${PHASE_AGENT_PREFIX}\`. Custom agent lain boleh dipanggil bila relevan.`,
|
|
14406
|
-
"- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri."
|
|
14486
|
+
"- Kerjakan HANYA fase ini; fase lain dikerjakan agen fasenya sendiri.",
|
|
14487
|
+
// T1 · ADR-0164 amandemen 2026-09-23 · 22 laporan tanpa `Status:` di audit: agen fase mengakhiri
|
|
14488
|
+
// gilirannya sambil menunggu proses latarnya sendiri, dan teks "menunggu" itu dibaca orchestrator
|
|
14489
|
+
// sebagai laporan final. Giliranmu yang berakhir = laporanmu, jadi menunggu harus terjadi DI DALAM giliran.
|
|
14490
|
+
'- JANGAN mengakhiri giliran atau melapor selama masih menunggu proses/tugas latar milikmu sendiri (test/build/server yang kamu jalankan di latar, subagent yang kamu panggil): tunggu sampai selesai dan baca hasilnya, lalu tulis laporan lengkap berawalan `Status:`. Akhir giliranmu dibaca orchestrator sebagai laporan final \u2014 teks "menunggu proses latar" bukan laporan.'
|
|
14407
14491
|
].join("\n");
|
|
14408
14492
|
PHASE_AGENT_REPORT = [
|
|
14409
14493
|
"Kontrak laporan (wajib, di akhir):",
|
|
@@ -14415,6 +14499,11 @@ var init_phase_agents = __esm({
|
|
|
14415
14499
|
"Klaim tanpa bukti bukan bukti. Maksimal 1200 kata."
|
|
14416
14500
|
].join("\n");
|
|
14417
14501
|
ATTACHMENT_NOTE = "Bila serah-terima menyebut manifest lampiran (`INDEX.md`), baca manifest itu dan lampiran yang relevan di awal fase.";
|
|
14502
|
+
TERMINAL_ASK = /([Aa])jukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya(?=[,.])/;
|
|
14503
|
+
PHASE_AGENT_ASK = "jukan pertanyaannya sebagai butir `Keputusan terbuka:` di laporanmu \u2014 satu topik per putaran, beserta konteks & opsinya, karena kamu tak berhadapan langsung dengan manusia \u2014 lalu tulis `Status: menunggu-keputusan` dan berhenti; jawabannya datang sebagai pesan susulan ke agen yang sama";
|
|
14504
|
+
TERMINAL_HANDOFF = "tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal.";
|
|
14505
|
+
PHASE_AGENT_HANDOFF = "tulis bagian `Ringkasan serah terima:` di laporanmu berisi ringkasan hasil + daftar pertanyaan yang belum terjawab (informasi untuk manusia, BUKAN `Keputusan terbuka:`) \u2014 orchestrator yang menampilkannya ke manusia.";
|
|
14506
|
+
forPhaseAgent = (line) => line.replace(TERMINAL_ASK, (_m, a) => a + PHASE_AGENT_ASK).replace(TERMINAL_HANDOFF, PHASE_AGENT_HANDOFF);
|
|
14418
14507
|
}
|
|
14419
14508
|
});
|
|
14420
14509
|
|
|
@@ -15656,7 +15745,8 @@ function writeSubagentStatusline(dir2) {
|
|
|
15656
15745
|
const script2 = join9(dir2, "subagent-statusline.cjs");
|
|
15657
15746
|
const labels = join9(dir2, "subagent-models.json");
|
|
15658
15747
|
writeFileSync6(script2, SUBAGENT_STATUSLINE_SCRIPT, { mode: 384 });
|
|
15659
|
-
|
|
15748
|
+
const entries3 = MODELS.flatMap((m) => [[m.id, m.label], ...m.resolved ? [[m.resolved, m.label]] : []]);
|
|
15749
|
+
writeFileSync6(labels, JSON.stringify(Object.fromEntries(entries3)), { mode: 384 });
|
|
15660
15750
|
return `node ${JSON.stringify(script2)} ${JSON.stringify(labels)}`;
|
|
15661
15751
|
}
|
|
15662
15752
|
var SUBAGENT_STATUSLINE_SCRIPT;
|
|
@@ -15707,6 +15797,7 @@ __export(src_exports, {
|
|
|
15707
15797
|
GOAL_MAX: () => GOAL_MAX,
|
|
15708
15798
|
GOAL_MAX_BLOCKS: () => GOAL_MAX_BLOCKS,
|
|
15709
15799
|
GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
|
|
15800
|
+
PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
|
|
15710
15801
|
PIPELINES: () => PIPELINES,
|
|
15711
15802
|
PROJECT_METHOD: () => PROJECT_METHOD,
|
|
15712
15803
|
RESUMED_WORKTREE_NOTE: () => RESUMED_WORKTREE_NOTE,
|
|
@@ -15757,6 +15848,7 @@ __export(src_exports, {
|
|
|
15757
15848
|
orchestratorClause: () => orchestratorClause,
|
|
15758
15849
|
parseConfigEnv: () => parseConfigEnv,
|
|
15759
15850
|
phaseAgentInstructions: () => phaseAgentInstructions,
|
|
15851
|
+
phasePromptOf: () => phasePromptOf,
|
|
15760
15852
|
phaseSkillsFor: () => phaseSkillsFor,
|
|
15761
15853
|
prdAuditBlock: () => prdAuditBlock,
|
|
15762
15854
|
prdBriefBlock: () => prdBriefBlock,
|
|
@@ -15794,6 +15886,7 @@ __export(src_exports, {
|
|
|
15794
15886
|
startProjectPrompt: () => startProjectPrompt,
|
|
15795
15887
|
startPrompt: () => startPrompt,
|
|
15796
15888
|
startScaffoldPrompt: () => startScaffoldPrompt,
|
|
15889
|
+
tomlBasicString: () => tomlBasicString,
|
|
15797
15890
|
validateAgentEvalManifest: () => validateAgentEvalManifest,
|
|
15798
15891
|
verifyScopeClause: () => verifyScopeClause,
|
|
15799
15892
|
writeConfigEnv: () => writeConfigEnv,
|
|
@@ -17055,19 +17148,45 @@ import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readF
|
|
|
17055
17148
|
import { stat as stat2 } from "node:fs/promises";
|
|
17056
17149
|
import { dirname as dirname8 } from "node:path";
|
|
17057
17150
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17058
|
-
function
|
|
17059
|
-
const
|
|
17060
|
-
|
|
17151
|
+
function doneSeenOf(id2) {
|
|
17152
|
+
const known2 = phaseDoneSeen.get(id2);
|
|
17153
|
+
if (known2) return known2;
|
|
17154
|
+
const seen2 = /* @__PURE__ */ new Map();
|
|
17155
|
+
try {
|
|
17156
|
+
const raw = tmux("show-options", "-v", "-q", "-t", name(id2), PHASE_DONE_SEEN_OPT).trim();
|
|
17157
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
17158
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
17159
|
+
for (const [phase, at] of Object.entries(parsed))
|
|
17160
|
+
if (typeof at === "number" && Number.isFinite(at)) seen2.set(phase, at);
|
|
17161
|
+
}
|
|
17162
|
+
} catch {
|
|
17163
|
+
}
|
|
17164
|
+
phaseDoneSeen.set(id2, seen2);
|
|
17165
|
+
return seen2;
|
|
17166
|
+
}
|
|
17167
|
+
function trackPhaseDoneSeen(id2, phases2, now) {
|
|
17168
|
+
const seen2 = doneSeenOf(id2);
|
|
17169
|
+
const before2 = JSON.stringify([...seen2]);
|
|
17170
|
+
trackDoneSeen(phases2, seen2, now);
|
|
17171
|
+
if (JSON.stringify([...seen2]) !== before2) {
|
|
17172
|
+
void tmuxAsync("set-option", "-t", name(id2), PHASE_DONE_SEEN_OPT, JSON.stringify(Object.fromEntries(seen2))).catch(() => {
|
|
17173
|
+
});
|
|
17174
|
+
}
|
|
17175
|
+
return seen2;
|
|
17176
|
+
}
|
|
17177
|
+
function phaseView(p3, phases2) {
|
|
17061
17178
|
const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
|
|
17062
|
-
|
|
17179
|
+
if (!roster.length) return phases2;
|
|
17180
|
+
const now = Date.now();
|
|
17181
|
+
return enrichPhases(
|
|
17063
17182
|
phases2,
|
|
17064
17183
|
roster,
|
|
17065
17184
|
phaseInvocations.get(p3.id) ?? [],
|
|
17066
|
-
|
|
17185
|
+
trackPhaseDoneSeen(p3.id, phases2, now),
|
|
17067
17186
|
now,
|
|
17068
17187
|
p3.startedAt * 1e3,
|
|
17069
17188
|
new Set(p3.doneAtBirth ?? [])
|
|
17070
|
-
)
|
|
17189
|
+
);
|
|
17071
17190
|
}
|
|
17072
17191
|
function noTtyPromptEnv() {
|
|
17073
17192
|
const path = askpassDenyPath();
|
|
@@ -17289,6 +17408,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17289
17408
|
const existing = getSession(id2);
|
|
17290
17409
|
if (existing && !existing.exited) return existing;
|
|
17291
17410
|
if (existing) killSession(id2);
|
|
17411
|
+
phaseDoneSeen.delete(id2);
|
|
17292
17412
|
const eventDir = opts.command ? void 0 : sessionEventDir(id2);
|
|
17293
17413
|
if (eventDir) {
|
|
17294
17414
|
mkdirSync7(eventDir, { recursive: true, mode: 448 });
|
|
@@ -17296,26 +17416,30 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17296
17416
|
}
|
|
17297
17417
|
const agentForDefs = opts.agent ?? "claude";
|
|
17298
17418
|
const sessionEffort = agentForDefs === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
|
|
17419
|
+
const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
|
|
17420
|
+
const bornPrompt = (orch) => orch ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
|
|
17299
17421
|
const selectionContext = {
|
|
17300
17422
|
projectId,
|
|
17301
17423
|
runtime: agentForDefs,
|
|
17302
17424
|
flow: opts.flow,
|
|
17303
17425
|
cwd,
|
|
17304
17426
|
baseSha: opts.env?.HANOMAN_BASE_SHA,
|
|
17305
|
-
prompt:
|
|
17427
|
+
prompt: bornPrompt(requestedPhaseDefs.length > 0),
|
|
17306
17428
|
changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
|
|
17307
17429
|
};
|
|
17308
|
-
const
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17430
|
+
const selectCustomDefs = (context, warn) => {
|
|
17431
|
+
const raw = opts.command ? [] : customAgentsFor(context);
|
|
17432
|
+
for (const def2 of raw) {
|
|
17433
|
+
if (warn && isPhaseAgentName(def2.name)) {
|
|
17434
|
+
process.stderr.write(
|
|
17435
|
+
`hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
|
|
17314
17436
|
`
|
|
17315
|
-
|
|
17437
|
+
);
|
|
17438
|
+
}
|
|
17316
17439
|
}
|
|
17317
|
-
|
|
17318
|
-
|
|
17440
|
+
return raw.filter((def2) => !isPhaseAgentName(def2.name));
|
|
17441
|
+
};
|
|
17442
|
+
let customDefs = selectCustomDefs(selectionContext, true);
|
|
17319
17443
|
let rosterBlock = "";
|
|
17320
17444
|
let codexAgentArgs = [];
|
|
17321
17445
|
let agentsFile;
|
|
@@ -17328,14 +17452,32 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17328
17452
|
const tempDir = agentTempDir(id2);
|
|
17329
17453
|
agentConfigDir = tempDir;
|
|
17330
17454
|
mkdirSync7(tempDir, { recursive: true, mode: 448 });
|
|
17331
|
-
|
|
17455
|
+
let hook;
|
|
17456
|
+
const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
|
|
17332
17457
|
const attempt = (phaseDefs) => {
|
|
17333
17458
|
const defs = [...phaseDefs, ...customDefs];
|
|
17334
17459
|
if (defs.length === 0) return [];
|
|
17460
|
+
const readOnlyHook = readOnlyHookFor();
|
|
17335
17461
|
if (agentForDefs === "claude") {
|
|
17336
17462
|
const file = agentsFilePath(id2);
|
|
17337
17463
|
try {
|
|
17338
|
-
|
|
17464
|
+
const shared = phaseDefs.find((d) => d.context !== void 0)?.context;
|
|
17465
|
+
let phaseContextFile;
|
|
17466
|
+
if (shared !== void 0) {
|
|
17467
|
+
const path = phaseContextFilePath(id2);
|
|
17468
|
+
writeFileSync8(path, shared, { mode: 384 });
|
|
17469
|
+
chmodSync6(path, 384);
|
|
17470
|
+
phaseContextFile = { context: shared, path };
|
|
17471
|
+
}
|
|
17472
|
+
const json = renderAgentsJson(defs, {
|
|
17473
|
+
readOnlyHookCommand: readOnlyHook?.command,
|
|
17474
|
+
...phaseContextFile ? { phaseContextFile } : {}
|
|
17475
|
+
});
|
|
17476
|
+
const bytes = Buffer.byteLength(json);
|
|
17477
|
+
if (phaseDefs.length > 0 && bytes > AGENTS_ARG_SAFE_BYTES) {
|
|
17478
|
+
return [`argumen --agents ${bytes} B melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (batas satu argumen exec Linux 128 KiB)`];
|
|
17479
|
+
}
|
|
17480
|
+
writeFileSync8(file, json, { mode: 384 });
|
|
17339
17481
|
} catch (error) {
|
|
17340
17482
|
if (phaseDefs.length === 0) throw error;
|
|
17341
17483
|
return [error instanceof Error ? error.message : String(error)];
|
|
@@ -17371,11 +17513,13 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17371
17513
|
orchestrated = phaseFailReasons.length === 0;
|
|
17372
17514
|
}
|
|
17373
17515
|
if (!orchestrated) {
|
|
17374
|
-
if (requestedPhaseDefs.length > 0)
|
|
17516
|
+
if (requestedPhaseDefs.length > 0) {
|
|
17375
17517
|
process.stderr.write(
|
|
17376
17518
|
`hanoman: agen fase sesi ${id2} gagal dimaterialisasi \u2014 sesi lahir mode tunggal: ${phaseFailReasons.join("; ")}
|
|
17377
17519
|
`
|
|
17378
17520
|
);
|
|
17521
|
+
customDefs = selectCustomDefs({ ...selectionContext, prompt: bornPrompt(false) }, false);
|
|
17522
|
+
}
|
|
17379
17523
|
attempt([]);
|
|
17380
17524
|
}
|
|
17381
17525
|
if (orchestrated && agentForDefs === "claude") {
|
|
@@ -17391,7 +17535,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17391
17535
|
}
|
|
17392
17536
|
let promptArg = "";
|
|
17393
17537
|
let promptFile;
|
|
17394
|
-
const sessionPrompt = orchestrated
|
|
17538
|
+
const sessionPrompt = bornPrompt(orchestrated);
|
|
17395
17539
|
if (!opts.command && sessionPrompt) {
|
|
17396
17540
|
promptFile = promptFilePath(id2);
|
|
17397
17541
|
mkdirSync7(dirname8(promptFile), { recursive: true, mode: 448 });
|
|
@@ -17561,7 +17705,8 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17561
17705
|
}
|
|
17562
17706
|
if (opts.phaseFile) tmux("set-option", "-t", name(id2), "@hanoman_phase_file", opts.phaseFile);
|
|
17563
17707
|
if (opts.phaseFile && opts.flow) {
|
|
17564
|
-
const
|
|
17708
|
+
const rerun = new Set(opts.rerunPhases ?? []);
|
|
17709
|
+
const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => (p3.state === "done" || p3.state === "skipped") && !rerun.has(p3.name)).map((p3) => p3.name);
|
|
17565
17710
|
if (doneAtBirth.length > 0)
|
|
17566
17711
|
tmux("set-option", "-t", name(id2), "@hanoman_done_at_birth", doneAtBirth.join(","));
|
|
17567
17712
|
}
|
|
@@ -17700,8 +17845,7 @@ function open(id2) {
|
|
|
17700
17845
|
scrollback: "",
|
|
17701
17846
|
clients: /* @__PURE__ */ new Set(),
|
|
17702
17847
|
lastPhases: "",
|
|
17703
|
-
pending: ""
|
|
17704
|
-
doneSeenAt: /* @__PURE__ */ new Map()
|
|
17848
|
+
pending: ""
|
|
17705
17849
|
};
|
|
17706
17850
|
pty.onData((d) => {
|
|
17707
17851
|
a.pending += d;
|
|
@@ -17731,13 +17875,14 @@ function end(id2, code) {
|
|
|
17731
17875
|
flushOutput(a);
|
|
17732
17876
|
broadcast(a, { t: "exit", code });
|
|
17733
17877
|
drop(id2);
|
|
17878
|
+
phaseDoneSeen.delete(id2);
|
|
17734
17879
|
}
|
|
17735
17880
|
async function pollPhases(p3, a) {
|
|
17736
17881
|
if (!p3.flow || !p3.phaseFile) return;
|
|
17737
17882
|
const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
|
|
17738
17883
|
const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
|
|
17739
17884
|
if (attached.get(p3.id) !== a) return;
|
|
17740
|
-
const phases2 = phaseView(p3,
|
|
17885
|
+
const phases2 = phaseView(p3, raw);
|
|
17741
17886
|
const json = phaseKey(phases2, complete);
|
|
17742
17887
|
if (json === a.lastPhases) return;
|
|
17743
17888
|
a.lastPhases = json;
|
|
@@ -17814,7 +17959,7 @@ function attachLive(id2, c, p3) {
|
|
|
17814
17959
|
c.send(frame({ t: "alt", on: p3.altScreen }));
|
|
17815
17960
|
if (p3.flow && p3.phaseFile) {
|
|
17816
17961
|
const raw = readPhases(p3.phaseFile, p3.flow);
|
|
17817
|
-
const phases2 = phaseView(p3,
|
|
17962
|
+
const phases2 = phaseView(p3, raw);
|
|
17818
17963
|
const complete = sessionComplete(raw, p3.cwd, p3.specId);
|
|
17819
17964
|
a.lastPhases = phaseKey(phases2, complete);
|
|
17820
17965
|
c.send(frame({ t: "phase", phases: phases2, complete }));
|
|
@@ -17831,6 +17976,7 @@ function resize(id2, cols, rows) {
|
|
|
17831
17976
|
}
|
|
17832
17977
|
function killSession(id2) {
|
|
17833
17978
|
phaseInvocations.delete(id2);
|
|
17979
|
+
phaseDoneSeen.delete(id2);
|
|
17834
17980
|
const p3 = getSession(id2);
|
|
17835
17981
|
if (!p3) return false;
|
|
17836
17982
|
const transcript = captureTranscript(id2);
|
|
@@ -17869,7 +18015,7 @@ function spawnPty(...args) {
|
|
|
17869
18015
|
);
|
|
17870
18016
|
}
|
|
17871
18017
|
}
|
|
17872
|
-
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
18018
|
+
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
17873
18019
|
var init_pty = __esm({
|
|
17874
18020
|
"src/services/pty.ts"() {
|
|
17875
18021
|
"use strict";
|
|
@@ -17929,6 +18075,8 @@ var init_pty = __esm({
|
|
|
17929
18075
|
};
|
|
17930
18076
|
attached = /* @__PURE__ */ new Map();
|
|
17931
18077
|
phaseInvocations = /* @__PURE__ */ new Map();
|
|
18078
|
+
PHASE_DONE_SEEN_OPT = "@hanoman_phase_done_seen";
|
|
18079
|
+
phaseDoneSeen = /* @__PURE__ */ new Map();
|
|
17932
18080
|
claudeBin = () => effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
17933
18081
|
shellBin = () => effectiveStr("HANOMAN_SHELL") ?? process.env.SHELL ?? "/bin/bash";
|
|
17934
18082
|
codexBin = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
@@ -17941,6 +18089,8 @@ var init_pty = __esm({
|
|
|
17941
18089
|
goalStatePath = (id2) => `${tmpdir3()}/hanoman-goal-gates/${id2}.count`;
|
|
17942
18090
|
agentTempDir = (id2) => `${tmpdir3()}/hanoman-agents/${id2}`;
|
|
17943
18091
|
agentsFilePath = (id2) => `${agentTempDir(id2)}/claude.json`;
|
|
18092
|
+
phaseContextFilePath = (id2) => `${agentTempDir(id2)}/phase-context.md`;
|
|
18093
|
+
AGENTS_ARG_SAFE_BYTES = 100 * 1024;
|
|
17944
18094
|
askpassDenyPath = () => `${tmpdir3()}/hanoman-askpass/deny.sh`;
|
|
17945
18095
|
ASKPASS_DENY = `#!/bin/sh
|
|
17946
18096
|
echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
|
|
@@ -19087,9 +19237,28 @@ async function getSetting() {
|
|
|
19087
19237
|
return {
|
|
19088
19238
|
...parsed.data,
|
|
19089
19239
|
model: RETIRED_MODELS[parsed.data.model] ?? parsed.data.model,
|
|
19090
|
-
codex: normalizeCodex(parsed.data.codex)
|
|
19240
|
+
codex: normalizeCodex(parsed.data.codex),
|
|
19241
|
+
orchestration: normalizeOrchestration(parsed.data.orchestration)
|
|
19091
19242
|
};
|
|
19092
19243
|
}
|
|
19244
|
+
function normalizePhaseRuntime(runtime, cell2) {
|
|
19245
|
+
if (!cell2.model) return cell2;
|
|
19246
|
+
const model = runtime === "codex" ? normalizeCodexModel(cell2.model) : normalizeClaudeModel(cell2.model);
|
|
19247
|
+
const effort2 = runtime === "codex" && cell2.effort ? coerceCodexEffort(model, cell2.effort) : cell2.effort;
|
|
19248
|
+
return { ...cell2, model, effort: effort2 };
|
|
19249
|
+
}
|
|
19250
|
+
function normalizeOrchestration(o) {
|
|
19251
|
+
const out4 = {};
|
|
19252
|
+
for (const [flow2, cfg] of Object.entries(o)) {
|
|
19253
|
+
const cells = (runtime) => Object.fromEntries(Object.entries(cfg[runtime]).map(([phase, cell2]) => [phase, normalizePhaseRuntime(runtime, cell2)]));
|
|
19254
|
+
out4[flow2] = { ...cfg, claude: cells("claude"), codex: cells("codex") };
|
|
19255
|
+
}
|
|
19256
|
+
return out4;
|
|
19257
|
+
}
|
|
19258
|
+
function normalizePhaseOverrides(runtime, o) {
|
|
19259
|
+
if (!o) return o;
|
|
19260
|
+
return Object.fromEntries(Object.entries(o).map(([phase, v]) => [phase, normalizePhaseRuntime(runtime, v)]));
|
|
19261
|
+
}
|
|
19093
19262
|
function normalizeCodex(c) {
|
|
19094
19263
|
const model = codexModel(c.model) ? c.model : RETIRED_CODEX_MODELS[c.model] ?? c.model;
|
|
19095
19264
|
return { model, effort: coerceCodexEffort(model, c.effort) };
|
|
@@ -19118,13 +19287,13 @@ async function terminalAgentDefaults(o) {
|
|
|
19118
19287
|
const effort2 = o.effort ?? base2.effort;
|
|
19119
19288
|
return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort2) } : { agent: "claude", model, effort: effort2 };
|
|
19120
19289
|
}
|
|
19121
|
-
var STEP, DEFAULT_SETTING, RETIRED_MODELS;
|
|
19290
|
+
var STEP, DEFAULT_SETTING, normalizeClaudeModel, normalizeCodexModel, RETIRED_MODELS;
|
|
19122
19291
|
var init_settings3 = __esm({
|
|
19123
19292
|
"src/services/settings.ts"() {
|
|
19124
19293
|
"use strict";
|
|
19125
19294
|
init_db();
|
|
19126
19295
|
init_src();
|
|
19127
|
-
STEP = { model: "
|
|
19296
|
+
STEP = { model: "sonnet", effort: "medium" };
|
|
19128
19297
|
DEFAULT_SETTING = {
|
|
19129
19298
|
...STEP,
|
|
19130
19299
|
autoDefault: true,
|
|
@@ -19173,7 +19342,9 @@ var init_settings3 = __esm({
|
|
|
19173
19342
|
builtinRuntimeDefaults: BUILTIN_RUNTIME_DEFAULTS
|
|
19174
19343
|
// seed rekomendasi model/fase; user edit dipertahankan
|
|
19175
19344
|
};
|
|
19176
|
-
|
|
19345
|
+
normalizeClaudeModel = (m) => RETIRED_MODELS[m] ?? m;
|
|
19346
|
+
normalizeCodexModel = (m) => codexModel(m) ? m : RETIRED_CODEX_MODELS[m] ?? m;
|
|
19347
|
+
RETIRED_MODELS = { "claude-opus-4-8": "opus" };
|
|
19177
19348
|
}
|
|
19178
19349
|
});
|
|
19179
19350
|
|
|
@@ -20699,13 +20870,13 @@ function inferLegacyStates(raw) {
|
|
|
20699
20870
|
}
|
|
20700
20871
|
}
|
|
20701
20872
|
}
|
|
20702
|
-
const
|
|
20873
|
+
const claudeModel2 = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
|
|
20703
20874
|
const claudeEffort = !has("effort") || object2.effort === LEGACY_CLAUDE_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
|
|
20704
20875
|
const rawCodexObject = isRecord(rawCodex) ? rawCodex : {};
|
|
20705
20876
|
const codexModel2 = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "model") || rawCodexObject.model === LEGACY_CODEX_RUNTIME_DEFAULTS.model ? "seeded" : "user";
|
|
20706
20877
|
const codexEffort = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "effort") || rawCodexObject.effort === LEGACY_CODEX_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
|
|
20707
20878
|
return {
|
|
20708
|
-
claude: { model:
|
|
20879
|
+
claude: { model: claudeModel2, effort: claudeEffort },
|
|
20709
20880
|
codex: { model: codexModel2, effort: codexEffort },
|
|
20710
20881
|
orchestration
|
|
20711
20882
|
};
|
|
@@ -21578,7 +21749,7 @@ var init_engine_command = __esm({
|
|
|
21578
21749
|
init_src();
|
|
21579
21750
|
AGENT_LABEL = { claude: "Claude Code", codex: "Codex CLI" };
|
|
21580
21751
|
TELEGRAM_CONTROL_KIND = "gateway-control";
|
|
21581
|
-
claudeModels = () => MODELS.
|
|
21752
|
+
claudeModels = () => [...new Set(MODELS.flatMap((m) => m.resolved ? [m.id, m.resolved] : [m.id]))];
|
|
21582
21753
|
codexModels = () => CODEX_MODELS.map((m) => m.id);
|
|
21583
21754
|
effortsFor = (t) => t.agent === "codex" ? codexEfforts(t.model) : claudeEfforts(t.model);
|
|
21584
21755
|
usage = [
|
|
@@ -24246,7 +24417,7 @@ var require_sender = __commonJS({
|
|
|
24246
24417
|
*/
|
|
24247
24418
|
static frame(data, options2) {
|
|
24248
24419
|
let mask;
|
|
24249
|
-
let
|
|
24420
|
+
let merge = false;
|
|
24250
24421
|
let offset = 2;
|
|
24251
24422
|
let skipMasking = false;
|
|
24252
24423
|
if (options2.mask) {
|
|
@@ -24279,7 +24450,7 @@ var require_sender = __commonJS({
|
|
|
24279
24450
|
}
|
|
24280
24451
|
} else {
|
|
24281
24452
|
dataLength = data.length;
|
|
24282
|
-
|
|
24453
|
+
merge = options2.mask && options2.readOnly && !skipMasking;
|
|
24283
24454
|
}
|
|
24284
24455
|
let payloadLength = dataLength;
|
|
24285
24456
|
if (dataLength >= 65536) {
|
|
@@ -24289,7 +24460,7 @@ var require_sender = __commonJS({
|
|
|
24289
24460
|
offset += 2;
|
|
24290
24461
|
payloadLength = 126;
|
|
24291
24462
|
}
|
|
24292
|
-
const target3 = Buffer.allocUnsafe(
|
|
24463
|
+
const target3 = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
|
|
24293
24464
|
target3[0] = options2.fin ? options2.opcode | 128 : options2.opcode;
|
|
24294
24465
|
if (options2.rsv1) target3[0] |= 64;
|
|
24295
24466
|
target3[1] = payloadLength;
|
|
@@ -24306,7 +24477,7 @@ var require_sender = __commonJS({
|
|
|
24306
24477
|
target3[offset - 2] = mask[2];
|
|
24307
24478
|
target3[offset - 1] = mask[3];
|
|
24308
24479
|
if (skipMasking) return [target3, data];
|
|
24309
|
-
if (
|
|
24480
|
+
if (merge) {
|
|
24310
24481
|
applyMask(data, mask, target3, offset, dataLength);
|
|
24311
24482
|
return [target3];
|
|
24312
24483
|
}
|
|
@@ -27501,7 +27672,7 @@ function recommendedModel(row, runtime) {
|
|
|
27501
27672
|
if (!builtin) return null;
|
|
27502
27673
|
const model = builtin.models[runtime];
|
|
27503
27674
|
if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
|
|
27504
|
-
return
|
|
27675
|
+
return modelKnownForRuntime(runtime, model) ? model : null;
|
|
27505
27676
|
}
|
|
27506
27677
|
function toRuntimeDef(row, runtime) {
|
|
27507
27678
|
return { ...toDef(row), model: recommendedModel(row, runtime) };
|
|
@@ -41239,6 +41410,24 @@ async function reconciledSpecIdsSince(cutoff) {
|
|
|
41239
41410
|
});
|
|
41240
41411
|
return rows.map((r) => r.specId);
|
|
41241
41412
|
}
|
|
41413
|
+
async function reconciledRuntimesSince(cutoff) {
|
|
41414
|
+
const rows = await prisma.sessionHistory.findMany({
|
|
41415
|
+
where: { endedReason: RECONCILED, reconciledAt: { gte: cutoff }, specId: { not: null } },
|
|
41416
|
+
select: { specId: true, agent: true, model: true, effort: true },
|
|
41417
|
+
orderBy: { startedAt: "desc" }
|
|
41418
|
+
});
|
|
41419
|
+
const out4 = /* @__PURE__ */ new Map();
|
|
41420
|
+
for (const r of rows) {
|
|
41421
|
+
if (out4.has(r.specId)) continue;
|
|
41422
|
+
out4.set(r.specId, {
|
|
41423
|
+
// `agent` kolom String — nilai asing jatuh ke claude, cermin pembacaan @hanoman_agent (pty.ts).
|
|
41424
|
+
agent: r.agent === "codex" ? "codex" : "claude",
|
|
41425
|
+
...r.model ? { model: r.model } : {},
|
|
41426
|
+
...r.effort ? { effort: r.effort } : {}
|
|
41427
|
+
});
|
|
41428
|
+
}
|
|
41429
|
+
return out4;
|
|
41430
|
+
}
|
|
41242
41431
|
function installSessionHistory() {
|
|
41243
41432
|
registerSessionHooks({
|
|
41244
41433
|
onBirth: (b) => {
|
|
@@ -41905,13 +42094,15 @@ init_pty();
|
|
|
41905
42094
|
// src/services/orchestration.ts
|
|
41906
42095
|
init_src();
|
|
41907
42096
|
init_pty();
|
|
42097
|
+
init_settings3();
|
|
41908
42098
|
function sessionPhasePlan(setting, flow2, agent, orchestrator, phaseOverrides) {
|
|
41909
42099
|
return resolvePhasePlan({
|
|
41910
42100
|
flow: flow2,
|
|
41911
42101
|
runtime: agent,
|
|
41912
42102
|
orchestration: setting.orchestration,
|
|
41913
42103
|
orchestrator,
|
|
41914
|
-
|
|
42104
|
+
// S6 · override transient dinormalisasi sama seperti sel Setting (model pensiun, koersi codex).
|
|
42105
|
+
phaseOverrides: normalizePhaseOverrides(agent, phaseOverrides),
|
|
41915
42106
|
nativeAgents: nativeAgentsAvailable(agent)
|
|
41916
42107
|
});
|
|
41917
42108
|
}
|
|
@@ -42061,7 +42252,9 @@ async function startSpecSession(spec, opts) {
|
|
|
42061
42252
|
method,
|
|
42062
42253
|
verifyScope,
|
|
42063
42254
|
context: isGoalFlow ? goalContext(brief) : specContext(brief),
|
|
42064
|
-
fromAudit: fromAuditOf(spec.payload)
|
|
42255
|
+
fromAudit: fromAuditOf(spec.payload),
|
|
42256
|
+
// S2 · SPEC-394 · subagent tak melihat `resumeClause` orchestrator — ia dapat catatannya sendiri.
|
|
42257
|
+
...resumeCtx ? { resume: resumeCtx } : {}
|
|
42065
42258
|
}) : [];
|
|
42066
42259
|
const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
|
|
42067
42260
|
const s2 = createSession(spec.projectId, worktree, {
|
|
@@ -42077,6 +42270,9 @@ async function startSpecSession(spec, opts) {
|
|
|
42077
42270
|
prompt,
|
|
42078
42271
|
legacyPrompt,
|
|
42079
42272
|
phaseAgents,
|
|
42273
|
+
// S3 · SPEC-172 · berkas fase continue masih memuat `Execute done` run lama; fase yang DIKERJAKAN
|
|
42274
|
+
// ULANG sesi ini tak boleh dihitung `doneAtBirth`, supaya ⚠ bukti tetap bisa menyala.
|
|
42275
|
+
...isContinue && plan ? { rerunPhases: plan.phases.map((p3) => p3.phase) } : {},
|
|
42080
42276
|
env: scopeEnv
|
|
42081
42277
|
});
|
|
42082
42278
|
return resume ? { id: s2.id, resumed: true } : { id: s2.id };
|
|
@@ -42534,6 +42730,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
|
|
|
42534
42730
|
// src/services/session-event-relay.ts
|
|
42535
42731
|
init_session_event_token();
|
|
42536
42732
|
init_session_event_spool();
|
|
42733
|
+
init_ingress_policy();
|
|
42537
42734
|
import { constants as constants3 } from "node:fs";
|
|
42538
42735
|
import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
42539
42736
|
import { join as join25 } from "node:path";
|
|
@@ -42552,18 +42749,22 @@ function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
|
|
|
42552
42749
|
droppedEvents: 0
|
|
42553
42750
|
} };
|
|
42554
42751
|
}
|
|
42555
|
-
async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
|
|
42752
|
+
async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot(), host2 = null) {
|
|
42556
42753
|
const previous = sessionEventRelayStatus(root);
|
|
42557
|
-
const current = { retries: 0, dropped: 0 };
|
|
42754
|
+
const current = { retries: 0, dropped: 0, rejected: /* @__PURE__ */ new Map() };
|
|
42558
42755
|
let delivered = 0;
|
|
42559
42756
|
let failed = false;
|
|
42560
42757
|
try {
|
|
42561
|
-
delivered = await drainSpool(app3, root, current);
|
|
42758
|
+
delivered = await drainSpool(app3, root, current, host2);
|
|
42562
42759
|
return delivered;
|
|
42563
42760
|
} catch (error) {
|
|
42564
42761
|
failed = true;
|
|
42565
42762
|
throw error;
|
|
42566
42763
|
} finally {
|
|
42764
|
+
if (current.rejected.size) {
|
|
42765
|
+
const summary = [...current.rejected].map(([code, n2]) => `${n2}\xD7 ${code}`).join(", ");
|
|
42766
|
+
console.warn(`session event relay: route menolak event (${summary}) \u2014 event dibuang`);
|
|
42767
|
+
}
|
|
42567
42768
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42568
42769
|
observations.set(root, {
|
|
42569
42770
|
state: failed || current.retries > 0 ? "degraded" : "ready",
|
|
@@ -42576,7 +42777,7 @@ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
|
|
|
42576
42777
|
});
|
|
42577
42778
|
}
|
|
42578
42779
|
}
|
|
42579
|
-
async function drainSpool(app3, root, observation) {
|
|
42780
|
+
async function drainSpool(app3, root, observation, host2) {
|
|
42580
42781
|
await mkdir6(root, { recursive: true, mode: 448 });
|
|
42581
42782
|
let delivered = 0;
|
|
42582
42783
|
let examined = 0;
|
|
@@ -42590,8 +42791,10 @@ async function drainSpool(app3, root, observation) {
|
|
|
42590
42791
|
} catch {
|
|
42591
42792
|
continue;
|
|
42592
42793
|
}
|
|
42794
|
+
entries3.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
42593
42795
|
for (const entry of entries3) {
|
|
42594
42796
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
42797
|
+
const eventAt = /^(\d{1,15})-/.exec(entry.name)?.[1];
|
|
42595
42798
|
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
42596
42799
|
const path = join25(dir2, entry.name);
|
|
42597
42800
|
let payload;
|
|
@@ -42623,7 +42826,11 @@ async function drainSpool(app3, root, observation) {
|
|
|
42623
42826
|
url: "/api/session-events",
|
|
42624
42827
|
headers: {
|
|
42625
42828
|
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
42626
|
-
"x-hanoman-session": session.name
|
|
42829
|
+
"x-hanoman-session": session.name,
|
|
42830
|
+
...host2 ? { host: host2 } : {},
|
|
42831
|
+
// S1 · waktu KEJADIAN event (bukan waktu drain) — route memakainya untuk membedakan
|
|
42832
|
+
// replay dari lanjutan relay subagent yang sama.
|
|
42833
|
+
...eventAt ? { "x-hanoman-event-at": eventAt } : {}
|
|
42627
42834
|
},
|
|
42628
42835
|
payload
|
|
42629
42836
|
});
|
|
@@ -42634,7 +42841,10 @@ async function drainSpool(app3, root, observation) {
|
|
|
42634
42841
|
await rm6(path, { force: true }).catch(() => {
|
|
42635
42842
|
});
|
|
42636
42843
|
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
42637
|
-
else
|
|
42844
|
+
else {
|
|
42845
|
+
observation.dropped++;
|
|
42846
|
+
observation.rejected.set(response.statusCode, (observation.rejected.get(response.statusCode) ?? 0) + 1);
|
|
42847
|
+
}
|
|
42638
42848
|
} catch {
|
|
42639
42849
|
observation.retries++;
|
|
42640
42850
|
}
|
|
@@ -42644,12 +42854,13 @@ async function drainSpool(app3, root, observation) {
|
|
|
42644
42854
|
}
|
|
42645
42855
|
function startSessionEventRelay(app3, options2 = {}) {
|
|
42646
42856
|
const root = options2.root ?? sessionEventSpoolRoot();
|
|
42857
|
+
const host2 = controlHost(loadIngressPolicy(options2.env ?? process.env));
|
|
42647
42858
|
let running = false;
|
|
42648
42859
|
const tick6 = async () => {
|
|
42649
42860
|
if (running) return;
|
|
42650
42861
|
running = true;
|
|
42651
42862
|
try {
|
|
42652
|
-
await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root);
|
|
42863
|
+
await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root, host2);
|
|
42653
42864
|
} catch (error) {
|
|
42654
42865
|
console.error("session event relay gagal:", error);
|
|
42655
42866
|
} finally {
|
|
@@ -42762,6 +42973,15 @@ async function startAgentInvocation(input, io = {}) {
|
|
|
42762
42973
|
runtimeInvocationId: input.runtimeInvocationId
|
|
42763
42974
|
} }
|
|
42764
42975
|
});
|
|
42976
|
+
if (existing?.endedAt && startedAt.getTime() > existing.endedAt.getTime()) {
|
|
42977
|
+
const row2 = await prisma.agentInvocation.update({
|
|
42978
|
+
where: { id: existing.id },
|
|
42979
|
+
data: { status: "running", endedAt: null, durationMs: null }
|
|
42980
|
+
});
|
|
42981
|
+
const before3 = snapshot2(input.cwd, io.gitStatus);
|
|
42982
|
+
if (before3 !== null) snapshotHashes.set(keyOf(input), before3);
|
|
42983
|
+
return { row: row2, duplicate: false };
|
|
42984
|
+
}
|
|
42765
42985
|
const row = await prisma.agentInvocation.upsert({
|
|
42766
42986
|
where: { sessionId_runtimeInvocationId: {
|
|
42767
42987
|
sessionId: input.sessionId,
|
|
@@ -42812,7 +43032,8 @@ async function stopAgentInvocation(input, io = {}) {
|
|
|
42812
43032
|
...usage2,
|
|
42813
43033
|
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
42814
43034
|
resultHash: cleanResult === null ? null : hash3(cleanResult),
|
|
42815
|
-
|
|
43035
|
+
// S1 · putaran relay (baris dibuka ulang) tak menghapus perubahan workspace putaran sebelumnya.
|
|
43036
|
+
workspaceChanged: (existing?.workspaceChanged ?? false) || before2 !== void 0 && after !== null && before2 !== after,
|
|
42816
43037
|
// ADR-0164 · effort yang BENAR-BENAR dipakai runtime menang atas nilai start-time.
|
|
42817
43038
|
...input.effort ? { effort: input.effort } : {}
|
|
42818
43039
|
};
|
|
@@ -44402,6 +44623,7 @@ var ensureWorktree = (repoDir, wt, branchFrom) => {
|
|
|
44402
44623
|
var resumeNote = (reused) => reused ? `
|
|
44403
44624
|
|
|
44404
44625
|
${RESUMED_WORKTREE_NOTE}` : "";
|
|
44626
|
+
var phaseResume = (reused, repoDir, id2, flow2) => reused ? { resume: { worktreeKept: true, recorded: readPhases(phaseFilePath(repoDir, id2), flow2).filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => `${p3.name} ${p3.state}`) } } : {};
|
|
44405
44627
|
async function terminal_default(app3, opts) {
|
|
44406
44628
|
app3.get("/terminal/sessions", async () => listSessions());
|
|
44407
44629
|
app3.post("/terminal/sessions", async (req, reply) => {
|
|
@@ -44496,7 +44718,12 @@ async function terminal_default(app3, opts) {
|
|
|
44496
44718
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
44497
44719
|
prompt: plan ? startProjectPrompt("reverse", brief, "reverse-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
44498
44720
|
legacyPrompt,
|
|
44499
|
-
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44721
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44722
|
+
flow: "reverse",
|
|
44723
|
+
method: PROJECT_METHOD,
|
|
44724
|
+
context: projectContext(brief),
|
|
44725
|
+
...phaseResume(reused, repoDir, id2, "reverse")
|
|
44726
|
+
}) : []
|
|
44500
44727
|
});
|
|
44501
44728
|
return { code: 201, body: { id: s3.id } };
|
|
44502
44729
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -44528,7 +44755,12 @@ async function terminal_default(app3, opts) {
|
|
|
44528
44755
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
44529
44756
|
prompt: plan ? startScaffoldPrompt(brief, "scaffold-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
44530
44757
|
legacyPrompt,
|
|
44531
|
-
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44758
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44759
|
+
flow: "scaffold",
|
|
44760
|
+
method: PROJECT_METHOD,
|
|
44761
|
+
context: scaffoldContext(brief),
|
|
44762
|
+
...phaseResume(reused, repoDir, id2, "scaffold")
|
|
44763
|
+
}) : []
|
|
44532
44764
|
});
|
|
44533
44765
|
return { code: 201, body: { id: s3.id } };
|
|
44534
44766
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -44569,7 +44801,8 @@ async function terminal_default(app3, opts) {
|
|
|
44569
44801
|
flow: "prd",
|
|
44570
44802
|
method: PROJECT_METHOD,
|
|
44571
44803
|
context: prdContext(project_, brief, audit),
|
|
44572
|
-
prd: { slug }
|
|
44804
|
+
prd: { slug },
|
|
44805
|
+
...phaseResume(reused, repoDir, id2, "prd")
|
|
44573
44806
|
}) : []
|
|
44574
44807
|
});
|
|
44575
44808
|
return { code: 201, body: { id: s3.id } };
|
|
@@ -44615,7 +44848,8 @@ async function terminal_default(app3, opts) {
|
|
|
44615
44848
|
flow: "breakdown",
|
|
44616
44849
|
method: PROJECT_METHOD,
|
|
44617
44850
|
context: breakdownContext(project_, prd),
|
|
44618
|
-
prd: { slug, title }
|
|
44851
|
+
prd: { slug, title },
|
|
44852
|
+
...phaseResume(reused, repoDir, id2, "breakdown")
|
|
44619
44853
|
}) : []
|
|
44620
44854
|
});
|
|
44621
44855
|
return { code: 201, body: { id: s3.id } };
|
|
@@ -45891,7 +46125,7 @@ async function vps_default(app3) {
|
|
|
45891
46125
|
init_config2();
|
|
45892
46126
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
45893
46127
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
45894
|
-
import { homedir as homedir8 } from "node:os";
|
|
46128
|
+
import { homedir as homedir8, userInfo } from "node:os";
|
|
45895
46129
|
import { join as join31 } from "node:path";
|
|
45896
46130
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
45897
46131
|
var TTL_MS2 = 3e4;
|
|
@@ -45907,19 +46141,30 @@ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
|
45907
46141
|
function credsFile() {
|
|
45908
46142
|
return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir8(), ".claude"), ".credentials.json");
|
|
45909
46143
|
}
|
|
45910
|
-
|
|
45911
|
-
|
|
46144
|
+
var securityRun = (args) => execFileSync5("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
46145
|
+
var osAccount = () => {
|
|
46146
|
+
try {
|
|
46147
|
+
return userInfo().username || null;
|
|
46148
|
+
} catch {
|
|
46149
|
+
return null;
|
|
46150
|
+
}
|
|
46151
|
+
};
|
|
46152
|
+
function keychainAccessToken(run4 = securityRun, account = osAccount()) {
|
|
46153
|
+
const base2 = ["find-generic-password", "-s", "Claude Code-credentials"];
|
|
46154
|
+
for (const scope of account ? [["-a", account], []] : [[]]) {
|
|
45912
46155
|
try {
|
|
45913
|
-
const
|
|
45914
|
-
|
|
45915
|
-
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
45916
|
-
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
45917
|
-
);
|
|
45918
|
-
const tok = JSON.parse(blob)?.claudeAiOauth?.accessToken;
|
|
45919
|
-
if (tok) return tok;
|
|
46156
|
+
const tok = JSON.parse(run4([...base2, ...scope, "-w"]))?.claudeAiOauth?.accessToken;
|
|
46157
|
+
if (typeof tok === "string" && tok) return tok;
|
|
45920
46158
|
} catch {
|
|
45921
46159
|
}
|
|
45922
46160
|
}
|
|
46161
|
+
return null;
|
|
46162
|
+
}
|
|
46163
|
+
function readAccessToken() {
|
|
46164
|
+
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
46165
|
+
const tok = keychainAccessToken();
|
|
46166
|
+
if (tok) return tok;
|
|
46167
|
+
}
|
|
45923
46168
|
try {
|
|
45924
46169
|
return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
45925
46170
|
} catch {
|
|
@@ -46125,19 +46370,25 @@ var claudeRow = external_exports.object({
|
|
|
46125
46370
|
});
|
|
46126
46371
|
function parseClaudeModels(raw) {
|
|
46127
46372
|
const rows = external_exports.array(claudeRow).min(1).max(500).parse(raw);
|
|
46373
|
+
let preferred = null;
|
|
46128
46374
|
const models2 = /* @__PURE__ */ new Map();
|
|
46129
46375
|
for (const row of rows) {
|
|
46376
|
+
const resolved = row.resolvedModel ?? row.value;
|
|
46377
|
+
const head = row.description?.split(" \xB7 ")[0];
|
|
46130
46378
|
const model = {
|
|
46131
|
-
id: row.
|
|
46132
|
-
label: row.
|
|
46379
|
+
id: row.value,
|
|
46380
|
+
label: row.value === "default" && head ? `${row.displayName} \xB7 ${head}` : head || row.displayName,
|
|
46381
|
+
resolved,
|
|
46133
46382
|
...row.supportedEffortLevels ? { efforts: sorted(row.supportedEffortLevels) } : {}
|
|
46134
46383
|
};
|
|
46135
|
-
if (
|
|
46136
|
-
|
|
46137
|
-
|
|
46384
|
+
if (row.value === "default") {
|
|
46385
|
+
preferred ??= model;
|
|
46386
|
+
continue;
|
|
46138
46387
|
}
|
|
46388
|
+
const existing = models2.get(resolved);
|
|
46389
|
+
if (!existing || existing.id === resolved && row.value !== resolved) models2.set(resolved, model);
|
|
46139
46390
|
}
|
|
46140
|
-
return [...models2.values()];
|
|
46391
|
+
return preferred ? [preferred, ...models2.values()] : [...models2.values()];
|
|
46141
46392
|
}
|
|
46142
46393
|
var codexRow = external_exports.object({
|
|
46143
46394
|
slug: id,
|
|
@@ -46171,7 +46422,7 @@ function parseCachedCatalog(raw) {
|
|
|
46171
46422
|
error: external_exports.string().nullable()
|
|
46172
46423
|
});
|
|
46173
46424
|
return external_exports.object({
|
|
46174
|
-
claude: external_exports.array(external_exports.object({ id, label, efforts: efforts.optional() })).min(1).max(1e3),
|
|
46425
|
+
claude: external_exports.array(external_exports.object({ id, label, resolved: id.optional(), efforts: efforts.optional() })).min(1).max(1e3),
|
|
46175
46426
|
codex: external_exports.array(external_exports.object({ id, label, efforts: efforts.min(1), fallback: id, minClient: external_exports.string() })).min(1).max(1e3),
|
|
46176
46427
|
providers: external_exports.object({ claude: status2, codex: status2 })
|
|
46177
46428
|
}).parse(raw);
|
|
@@ -46297,7 +46548,6 @@ async function probeModelCatalog(agent) {
|
|
|
46297
46548
|
}
|
|
46298
46549
|
|
|
46299
46550
|
// src/services/model-catalog.ts
|
|
46300
|
-
var merge = (old, fresh) => [...new Map([...old, ...fresh].map((m) => [m.id, m])).values()];
|
|
46301
46551
|
function createModelCatalogService(deps) {
|
|
46302
46552
|
let state = bundledModelCatalog();
|
|
46303
46553
|
let initialized = false;
|
|
@@ -46324,7 +46574,7 @@ function createModelCatalogService(deps) {
|
|
|
46324
46574
|
try {
|
|
46325
46575
|
const models2 = await deps.probe(agent);
|
|
46326
46576
|
if (!models2.length) throw new Error("empty catalog");
|
|
46327
|
-
state = agent === "claude" ? { ...state, claude:
|
|
46577
|
+
state = agent === "claude" ? { ...state, claude: models2 } : { ...state, codex: models2 };
|
|
46328
46578
|
state = { ...state, providers: { ...state.providers, [agent]: {
|
|
46329
46579
|
source: "cli",
|
|
46330
46580
|
checkedAt,
|
|
@@ -48187,6 +48437,13 @@ init_pty();
|
|
|
48187
48437
|
var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
|
|
48188
48438
|
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
48189
48439
|
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
48440
|
+
var EVENT_AT_PAST_MS = 7 * 24 * 60 * 6e4;
|
|
48441
|
+
var EVENT_AT_FUTURE_MS = 6e4;
|
|
48442
|
+
var eventAtOf = (header2, now = Date.now()) => {
|
|
48443
|
+
if (typeof header2 !== "string" || !/^\d{1,15}$/.test(header2)) return void 0;
|
|
48444
|
+
const ms = Number(header2);
|
|
48445
|
+
return ms >= now - EVENT_AT_PAST_MS && ms <= now + EVENT_AT_FUTURE_MS ? new Date(ms) : void 0;
|
|
48446
|
+
};
|
|
48190
48447
|
async function session_events_default(app3) {
|
|
48191
48448
|
app3.post("/session-events", async (req, reply) => {
|
|
48192
48449
|
const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
|
|
@@ -48219,8 +48476,10 @@ async function session_events_default(app3) {
|
|
|
48219
48476
|
...meta.effort ? { effort: meta.effort } : {},
|
|
48220
48477
|
cwd: s2.cwd
|
|
48221
48478
|
};
|
|
48222
|
-
const
|
|
48479
|
+
const eventAt = eventAtOf(req.headers["x-hanoman-event-at"]);
|
|
48480
|
+
const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation({ ...identity, ...eventAt ? { startedAt: eventAt } : {} }) : await stopAgentInvocation({
|
|
48223
48481
|
...identity,
|
|
48482
|
+
...eventAt ? { endedAt: eventAt } : {},
|
|
48224
48483
|
status: body.status === "interrupted" ? "interrupted" : "completed",
|
|
48225
48484
|
result: boundedString(body.last_assistant_message ?? body.result, 1e6),
|
|
48226
48485
|
transcriptPath: boundedString(
|
|
@@ -49542,7 +49801,7 @@ function toolsProblem(tools, catalogIds) {
|
|
|
49542
49801
|
}
|
|
49543
49802
|
function modelProblem(model, runtime) {
|
|
49544
49803
|
if (!model) return null;
|
|
49545
|
-
const ok2 =
|
|
49804
|
+
const ok2 = modelKnownForRuntime(runtime, model);
|
|
49546
49805
|
return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
|
|
49547
49806
|
}
|
|
49548
49807
|
function effortProblem(effort2, runtime, model) {
|
|
@@ -53163,7 +53422,11 @@ var prodDeps2 = {
|
|
|
53163
53422
|
// dependency ADR-0093 TETAP berlaku (tak diberi `force`). Keputusan sadar risiko: mesin 8 GB
|
|
53164
53423
|
// operator sudah pernah kernel panic akibat sesi paralel berlebih (memori
|
|
53165
53424
|
// mac-mini-8gb-panic-agen-paralel) — operator memilih "semua kembali" di atas throttle.
|
|
53166
|
-
startSpec: (spec) => startSpecSession(spec, {
|
|
53425
|
+
startSpec: (spec, runtime) => startSpecSession(spec, {
|
|
53426
|
+
flow: flowForSource(spec.source),
|
|
53427
|
+
bypassCapacity: true,
|
|
53428
|
+
...runtime ?? {}
|
|
53429
|
+
}),
|
|
53167
53430
|
recordFail: recordFailure
|
|
53168
53431
|
};
|
|
53169
53432
|
async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
@@ -53171,9 +53434,10 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
|
53171
53434
|
const specIds = await reconciledSpecIdsSince(cutoff);
|
|
53172
53435
|
if (!specIds.length) return report;
|
|
53173
53436
|
const specs = await prisma.spec.findMany({ where: { id: { in: specIds }, stage: { not: "done" } } });
|
|
53437
|
+
const runtimes = await reconciledRuntimesSince(cutoff);
|
|
53174
53438
|
for (const spec of specs) {
|
|
53175
53439
|
try {
|
|
53176
|
-
await deps.startSpec(spec);
|
|
53440
|
+
await deps.startSpec(spec, runtimes.get(spec.id));
|
|
53177
53441
|
report.resumed.push(spec.id);
|
|
53178
53442
|
} catch (e) {
|
|
53179
53443
|
const reason = e instanceof Error ? e.message : String(e);
|