hanoman 0.9.2 → 0.9.4
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 +443 -154
- 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,
|
|
@@ -16451,9 +16544,10 @@ var init_ssrf = __esm({
|
|
|
16451
16544
|
});
|
|
16452
16545
|
|
|
16453
16546
|
// src/services/safe-outbound-request.ts
|
|
16454
|
-
import {
|
|
16547
|
+
import { resolve4 as resolve42, resolve6 as resolve62 } from "node:dns/promises";
|
|
16455
16548
|
import { request as httpRequest } from "node:http";
|
|
16456
16549
|
import { request as httpsRequest } from "node:https";
|
|
16550
|
+
import { isIP } from "node:net";
|
|
16457
16551
|
import { createGunzip } from "node:zlib";
|
|
16458
16552
|
async function pinnedRequest(input) {
|
|
16459
16553
|
return new Promise((resolve24, reject2) => {
|
|
@@ -16520,10 +16614,26 @@ async function pinnedRequest(input) {
|
|
|
16520
16614
|
req.end();
|
|
16521
16615
|
});
|
|
16522
16616
|
}
|
|
16617
|
+
function withLookupTimeout(p3, ms) {
|
|
16618
|
+
return new Promise((resolve24, reject2) => {
|
|
16619
|
+
const timer10 = setTimeout(() => reject2(Object.assign(new Error("outbound timeout"), { name: "AbortError" })), ms);
|
|
16620
|
+
timer10.unref?.();
|
|
16621
|
+
p3.then((v) => {
|
|
16622
|
+
clearTimeout(timer10);
|
|
16623
|
+
resolve24(v);
|
|
16624
|
+
}, (e) => {
|
|
16625
|
+
clearTimeout(timer10);
|
|
16626
|
+
reject2(e);
|
|
16627
|
+
});
|
|
16628
|
+
});
|
|
16629
|
+
}
|
|
16523
16630
|
async function safeRequest(options2, deps = {}) {
|
|
16524
16631
|
if (options2.url.protocol !== "http:" && options2.url.protocol !== "https:") throw new Error("outbound scheme ditolak");
|
|
16525
16632
|
if (options2.url.username || options2.url.password) throw new Error("outbound credential URL ditolak");
|
|
16526
|
-
const addresses = await (
|
|
16633
|
+
const addresses = await withLookupTimeout(
|
|
16634
|
+
(deps.lookupAll ?? defaultLookup2)(options2.url.hostname),
|
|
16635
|
+
options2.connectMs
|
|
16636
|
+
);
|
|
16527
16637
|
if (!addresses.length) throw new Error("DNS tak mengembalikan alamat");
|
|
16528
16638
|
if (!options2.allowPrivate) {
|
|
16529
16639
|
const blocked = addresses.find((row) => isBlockedAddress(row.address));
|
|
@@ -16537,7 +16647,15 @@ var init_safe_outbound_request = __esm({
|
|
|
16537
16647
|
"src/services/safe-outbound-request.ts"() {
|
|
16538
16648
|
"use strict";
|
|
16539
16649
|
init_ssrf();
|
|
16540
|
-
defaultLookup2 = async (host2) =>
|
|
16650
|
+
defaultLookup2 = async (host2) => {
|
|
16651
|
+
const literal = isIP(host2);
|
|
16652
|
+
if (literal) return [{ address: host2, family: literal }];
|
|
16653
|
+
const [v4, v6] = await Promise.allSettled([resolve42(host2), resolve62(host2)]);
|
|
16654
|
+
const out4 = [];
|
|
16655
|
+
if (v4.status === "fulfilled") out4.push(...v4.value.map((address) => ({ address, family: 4 })));
|
|
16656
|
+
if (v6.status === "fulfilled") out4.push(...v6.value.map((address) => ({ address, family: 6 })));
|
|
16657
|
+
return out4;
|
|
16658
|
+
};
|
|
16541
16659
|
}
|
|
16542
16660
|
});
|
|
16543
16661
|
|
|
@@ -17055,19 +17173,45 @@ import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readF
|
|
|
17055
17173
|
import { stat as stat2 } from "node:fs/promises";
|
|
17056
17174
|
import { dirname as dirname8 } from "node:path";
|
|
17057
17175
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17058
|
-
function
|
|
17059
|
-
const
|
|
17060
|
-
|
|
17176
|
+
function doneSeenOf(id2) {
|
|
17177
|
+
const known2 = phaseDoneSeen.get(id2);
|
|
17178
|
+
if (known2) return known2;
|
|
17179
|
+
const seen2 = /* @__PURE__ */ new Map();
|
|
17180
|
+
try {
|
|
17181
|
+
const raw = tmux("show-options", "-v", "-q", "-t", name(id2), PHASE_DONE_SEEN_OPT).trim();
|
|
17182
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
17183
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
17184
|
+
for (const [phase, at] of Object.entries(parsed))
|
|
17185
|
+
if (typeof at === "number" && Number.isFinite(at)) seen2.set(phase, at);
|
|
17186
|
+
}
|
|
17187
|
+
} catch {
|
|
17188
|
+
}
|
|
17189
|
+
phaseDoneSeen.set(id2, seen2);
|
|
17190
|
+
return seen2;
|
|
17191
|
+
}
|
|
17192
|
+
function trackPhaseDoneSeen(id2, phases2, now) {
|
|
17193
|
+
const seen2 = doneSeenOf(id2);
|
|
17194
|
+
const before2 = JSON.stringify([...seen2]);
|
|
17195
|
+
trackDoneSeen(phases2, seen2, now);
|
|
17196
|
+
if (JSON.stringify([...seen2]) !== before2) {
|
|
17197
|
+
void tmuxAsync("set-option", "-t", name(id2), PHASE_DONE_SEEN_OPT, JSON.stringify(Object.fromEntries(seen2))).catch(() => {
|
|
17198
|
+
});
|
|
17199
|
+
}
|
|
17200
|
+
return seen2;
|
|
17201
|
+
}
|
|
17202
|
+
function phaseView(p3, phases2) {
|
|
17061
17203
|
const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
|
|
17062
|
-
|
|
17204
|
+
if (!roster.length) return phases2;
|
|
17205
|
+
const now = Date.now();
|
|
17206
|
+
return enrichPhases(
|
|
17063
17207
|
phases2,
|
|
17064
17208
|
roster,
|
|
17065
17209
|
phaseInvocations.get(p3.id) ?? [],
|
|
17066
|
-
|
|
17210
|
+
trackPhaseDoneSeen(p3.id, phases2, now),
|
|
17067
17211
|
now,
|
|
17068
17212
|
p3.startedAt * 1e3,
|
|
17069
17213
|
new Set(p3.doneAtBirth ?? [])
|
|
17070
|
-
)
|
|
17214
|
+
);
|
|
17071
17215
|
}
|
|
17072
17216
|
function noTtyPromptEnv() {
|
|
17073
17217
|
const path = askpassDenyPath();
|
|
@@ -17289,6 +17433,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17289
17433
|
const existing = getSession(id2);
|
|
17290
17434
|
if (existing && !existing.exited) return existing;
|
|
17291
17435
|
if (existing) killSession(id2);
|
|
17436
|
+
phaseDoneSeen.delete(id2);
|
|
17292
17437
|
const eventDir = opts.command ? void 0 : sessionEventDir(id2);
|
|
17293
17438
|
if (eventDir) {
|
|
17294
17439
|
mkdirSync7(eventDir, { recursive: true, mode: 448 });
|
|
@@ -17296,26 +17441,30 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17296
17441
|
}
|
|
17297
17442
|
const agentForDefs = opts.agent ?? "claude";
|
|
17298
17443
|
const sessionEffort = agentForDefs === "codex" && opts.model && opts.effort ? coerceCodexEffort(opts.model, opts.effort) : opts.effort;
|
|
17444
|
+
const requestedPhaseDefs = opts.command ? [] : opts.phaseAgents ?? [];
|
|
17445
|
+
const bornPrompt = (orch) => orch ? opts.prompt : opts.legacyPrompt ?? opts.prompt;
|
|
17299
17446
|
const selectionContext = {
|
|
17300
17447
|
projectId,
|
|
17301
17448
|
runtime: agentForDefs,
|
|
17302
17449
|
flow: opts.flow,
|
|
17303
17450
|
cwd,
|
|
17304
17451
|
baseSha: opts.env?.HANOMAN_BASE_SHA,
|
|
17305
|
-
prompt:
|
|
17452
|
+
prompt: bornPrompt(requestedPhaseDefs.length > 0),
|
|
17306
17453
|
changedFiles: opts.command ? [] : collectChangedFiles(cwd, opts.env?.HANOMAN_BASE_SHA)
|
|
17307
17454
|
};
|
|
17308
|
-
const
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17455
|
+
const selectCustomDefs = (context, warn) => {
|
|
17456
|
+
const raw = opts.command ? [] : customAgentsFor(context);
|
|
17457
|
+
for (const def2 of raw) {
|
|
17458
|
+
if (warn && isPhaseAgentName(def2.name)) {
|
|
17459
|
+
process.stderr.write(
|
|
17460
|
+
`hanoman: custom agent ${def2.name} diabaikan \u2014 awalan hanoman-fase- dicadangkan (ADR-0164)
|
|
17314
17461
|
`
|
|
17315
|
-
|
|
17462
|
+
);
|
|
17463
|
+
}
|
|
17316
17464
|
}
|
|
17317
|
-
|
|
17318
|
-
|
|
17465
|
+
return raw.filter((def2) => !isPhaseAgentName(def2.name));
|
|
17466
|
+
};
|
|
17467
|
+
let customDefs = selectCustomDefs(selectionContext, true);
|
|
17319
17468
|
let rosterBlock = "";
|
|
17320
17469
|
let codexAgentArgs = [];
|
|
17321
17470
|
let agentsFile;
|
|
@@ -17328,14 +17477,32 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17328
17477
|
const tempDir = agentTempDir(id2);
|
|
17329
17478
|
agentConfigDir = tempDir;
|
|
17330
17479
|
mkdirSync7(tempDir, { recursive: true, mode: 448 });
|
|
17331
|
-
|
|
17480
|
+
let hook;
|
|
17481
|
+
const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
|
|
17332
17482
|
const attempt = (phaseDefs) => {
|
|
17333
17483
|
const defs = [...phaseDefs, ...customDefs];
|
|
17334
17484
|
if (defs.length === 0) return [];
|
|
17485
|
+
const readOnlyHook = readOnlyHookFor();
|
|
17335
17486
|
if (agentForDefs === "claude") {
|
|
17336
17487
|
const file = agentsFilePath(id2);
|
|
17337
17488
|
try {
|
|
17338
|
-
|
|
17489
|
+
const shared = phaseDefs.find((d) => d.context !== void 0)?.context;
|
|
17490
|
+
let phaseContextFile;
|
|
17491
|
+
if (shared !== void 0) {
|
|
17492
|
+
const path = phaseContextFilePath(id2);
|
|
17493
|
+
writeFileSync8(path, shared, { mode: 384 });
|
|
17494
|
+
chmodSync6(path, 384);
|
|
17495
|
+
phaseContextFile = { context: shared, path };
|
|
17496
|
+
}
|
|
17497
|
+
const json = renderAgentsJson(defs, {
|
|
17498
|
+
readOnlyHookCommand: readOnlyHook?.command,
|
|
17499
|
+
...phaseContextFile ? { phaseContextFile } : {}
|
|
17500
|
+
});
|
|
17501
|
+
const bytes = Buffer.byteLength(json);
|
|
17502
|
+
if (phaseDefs.length > 0 && bytes > AGENTS_ARG_SAFE_BYTES) {
|
|
17503
|
+
return [`argumen --agents ${bytes} B melewati ambang aman ${AGENTS_ARG_SAFE_BYTES} B (batas satu argumen exec Linux 128 KiB)`];
|
|
17504
|
+
}
|
|
17505
|
+
writeFileSync8(file, json, { mode: 384 });
|
|
17339
17506
|
} catch (error) {
|
|
17340
17507
|
if (phaseDefs.length === 0) throw error;
|
|
17341
17508
|
return [error instanceof Error ? error.message : String(error)];
|
|
@@ -17371,11 +17538,13 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17371
17538
|
orchestrated = phaseFailReasons.length === 0;
|
|
17372
17539
|
}
|
|
17373
17540
|
if (!orchestrated) {
|
|
17374
|
-
if (requestedPhaseDefs.length > 0)
|
|
17541
|
+
if (requestedPhaseDefs.length > 0) {
|
|
17375
17542
|
process.stderr.write(
|
|
17376
17543
|
`hanoman: agen fase sesi ${id2} gagal dimaterialisasi \u2014 sesi lahir mode tunggal: ${phaseFailReasons.join("; ")}
|
|
17377
17544
|
`
|
|
17378
17545
|
);
|
|
17546
|
+
customDefs = selectCustomDefs({ ...selectionContext, prompt: bornPrompt(false) }, false);
|
|
17547
|
+
}
|
|
17379
17548
|
attempt([]);
|
|
17380
17549
|
}
|
|
17381
17550
|
if (orchestrated && agentForDefs === "claude") {
|
|
@@ -17391,7 +17560,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17391
17560
|
}
|
|
17392
17561
|
let promptArg = "";
|
|
17393
17562
|
let promptFile;
|
|
17394
|
-
const sessionPrompt = orchestrated
|
|
17563
|
+
const sessionPrompt = bornPrompt(orchestrated);
|
|
17395
17564
|
if (!opts.command && sessionPrompt) {
|
|
17396
17565
|
promptFile = promptFilePath(id2);
|
|
17397
17566
|
mkdirSync7(dirname8(promptFile), { recursive: true, mode: 448 });
|
|
@@ -17561,7 +17730,8 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
17561
17730
|
}
|
|
17562
17731
|
if (opts.phaseFile) tmux("set-option", "-t", name(id2), "@hanoman_phase_file", opts.phaseFile);
|
|
17563
17732
|
if (opts.phaseFile && opts.flow) {
|
|
17564
|
-
const
|
|
17733
|
+
const rerun = new Set(opts.rerunPhases ?? []);
|
|
17734
|
+
const doneAtBirth = readPhases(opts.phaseFile, opts.flow).filter((p3) => (p3.state === "done" || p3.state === "skipped") && !rerun.has(p3.name)).map((p3) => p3.name);
|
|
17565
17735
|
if (doneAtBirth.length > 0)
|
|
17566
17736
|
tmux("set-option", "-t", name(id2), "@hanoman_done_at_birth", doneAtBirth.join(","));
|
|
17567
17737
|
}
|
|
@@ -17700,8 +17870,7 @@ function open(id2) {
|
|
|
17700
17870
|
scrollback: "",
|
|
17701
17871
|
clients: /* @__PURE__ */ new Set(),
|
|
17702
17872
|
lastPhases: "",
|
|
17703
|
-
pending: ""
|
|
17704
|
-
doneSeenAt: /* @__PURE__ */ new Map()
|
|
17873
|
+
pending: ""
|
|
17705
17874
|
};
|
|
17706
17875
|
pty.onData((d) => {
|
|
17707
17876
|
a.pending += d;
|
|
@@ -17731,13 +17900,14 @@ function end(id2, code) {
|
|
|
17731
17900
|
flushOutput(a);
|
|
17732
17901
|
broadcast(a, { t: "exit", code });
|
|
17733
17902
|
drop(id2);
|
|
17903
|
+
phaseDoneSeen.delete(id2);
|
|
17734
17904
|
}
|
|
17735
17905
|
async function pollPhases(p3, a) {
|
|
17736
17906
|
if (!p3.flow || !p3.phaseFile) return;
|
|
17737
17907
|
const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
|
|
17738
17908
|
const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
|
|
17739
17909
|
if (attached.get(p3.id) !== a) return;
|
|
17740
|
-
const phases2 = phaseView(p3,
|
|
17910
|
+
const phases2 = phaseView(p3, raw);
|
|
17741
17911
|
const json = phaseKey(phases2, complete);
|
|
17742
17912
|
if (json === a.lastPhases) return;
|
|
17743
17913
|
a.lastPhases = json;
|
|
@@ -17814,7 +17984,7 @@ function attachLive(id2, c, p3) {
|
|
|
17814
17984
|
c.send(frame({ t: "alt", on: p3.altScreen }));
|
|
17815
17985
|
if (p3.flow && p3.phaseFile) {
|
|
17816
17986
|
const raw = readPhases(p3.phaseFile, p3.flow);
|
|
17817
|
-
const phases2 = phaseView(p3,
|
|
17987
|
+
const phases2 = phaseView(p3, raw);
|
|
17818
17988
|
const complete = sessionComplete(raw, p3.cwd, p3.specId);
|
|
17819
17989
|
a.lastPhases = phaseKey(phases2, complete);
|
|
17820
17990
|
c.send(frame({ t: "phase", phases: phases2, complete }));
|
|
@@ -17831,6 +18001,7 @@ function resize(id2, cols, rows) {
|
|
|
17831
18001
|
}
|
|
17832
18002
|
function killSession(id2) {
|
|
17833
18003
|
phaseInvocations.delete(id2);
|
|
18004
|
+
phaseDoneSeen.delete(id2);
|
|
17834
18005
|
const p3 = getSession(id2);
|
|
17835
18006
|
if (!p3) return false;
|
|
17836
18007
|
const transcript = captureTranscript(id2);
|
|
@@ -17869,7 +18040,7 @@ function spawnPty(...args) {
|
|
|
17869
18040
|
);
|
|
17870
18041
|
}
|
|
17871
18042
|
}
|
|
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;
|
|
18043
|
+
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
18044
|
var init_pty = __esm({
|
|
17874
18045
|
"src/services/pty.ts"() {
|
|
17875
18046
|
"use strict";
|
|
@@ -17929,6 +18100,8 @@ var init_pty = __esm({
|
|
|
17929
18100
|
};
|
|
17930
18101
|
attached = /* @__PURE__ */ new Map();
|
|
17931
18102
|
phaseInvocations = /* @__PURE__ */ new Map();
|
|
18103
|
+
PHASE_DONE_SEEN_OPT = "@hanoman_phase_done_seen";
|
|
18104
|
+
phaseDoneSeen = /* @__PURE__ */ new Map();
|
|
17932
18105
|
claudeBin = () => effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
17933
18106
|
shellBin = () => effectiveStr("HANOMAN_SHELL") ?? process.env.SHELL ?? "/bin/bash";
|
|
17934
18107
|
codexBin = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
|
|
@@ -17941,6 +18114,8 @@ var init_pty = __esm({
|
|
|
17941
18114
|
goalStatePath = (id2) => `${tmpdir3()}/hanoman-goal-gates/${id2}.count`;
|
|
17942
18115
|
agentTempDir = (id2) => `${tmpdir3()}/hanoman-agents/${id2}`;
|
|
17943
18116
|
agentsFilePath = (id2) => `${agentTempDir(id2)}/claude.json`;
|
|
18117
|
+
phaseContextFilePath = (id2) => `${agentTempDir(id2)}/phase-context.md`;
|
|
18118
|
+
AGENTS_ARG_SAFE_BYTES = 100 * 1024;
|
|
17944
18119
|
askpassDenyPath = () => `${tmpdir3()}/hanoman-askpass/deny.sh`;
|
|
17945
18120
|
ASKPASS_DENY = `#!/bin/sh
|
|
17946
18121
|
echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
|
|
@@ -19087,9 +19262,28 @@ async function getSetting() {
|
|
|
19087
19262
|
return {
|
|
19088
19263
|
...parsed.data,
|
|
19089
19264
|
model: RETIRED_MODELS[parsed.data.model] ?? parsed.data.model,
|
|
19090
|
-
codex: normalizeCodex(parsed.data.codex)
|
|
19265
|
+
codex: normalizeCodex(parsed.data.codex),
|
|
19266
|
+
orchestration: normalizeOrchestration(parsed.data.orchestration)
|
|
19091
19267
|
};
|
|
19092
19268
|
}
|
|
19269
|
+
function normalizePhaseRuntime(runtime, cell2) {
|
|
19270
|
+
if (!cell2.model) return cell2;
|
|
19271
|
+
const model = runtime === "codex" ? normalizeCodexModel(cell2.model) : normalizeClaudeModel(cell2.model);
|
|
19272
|
+
const effort2 = runtime === "codex" && cell2.effort ? coerceCodexEffort(model, cell2.effort) : cell2.effort;
|
|
19273
|
+
return { ...cell2, model, effort: effort2 };
|
|
19274
|
+
}
|
|
19275
|
+
function normalizeOrchestration(o) {
|
|
19276
|
+
const out4 = {};
|
|
19277
|
+
for (const [flow2, cfg] of Object.entries(o)) {
|
|
19278
|
+
const cells = (runtime) => Object.fromEntries(Object.entries(cfg[runtime]).map(([phase, cell2]) => [phase, normalizePhaseRuntime(runtime, cell2)]));
|
|
19279
|
+
out4[flow2] = { ...cfg, claude: cells("claude"), codex: cells("codex") };
|
|
19280
|
+
}
|
|
19281
|
+
return out4;
|
|
19282
|
+
}
|
|
19283
|
+
function normalizePhaseOverrides(runtime, o) {
|
|
19284
|
+
if (!o) return o;
|
|
19285
|
+
return Object.fromEntries(Object.entries(o).map(([phase, v]) => [phase, normalizePhaseRuntime(runtime, v)]));
|
|
19286
|
+
}
|
|
19093
19287
|
function normalizeCodex(c) {
|
|
19094
19288
|
const model = codexModel(c.model) ? c.model : RETIRED_CODEX_MODELS[c.model] ?? c.model;
|
|
19095
19289
|
return { model, effort: coerceCodexEffort(model, c.effort) };
|
|
@@ -19118,13 +19312,13 @@ async function terminalAgentDefaults(o) {
|
|
|
19118
19312
|
const effort2 = o.effort ?? base2.effort;
|
|
19119
19313
|
return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort2) } : { agent: "claude", model, effort: effort2 };
|
|
19120
19314
|
}
|
|
19121
|
-
var STEP, DEFAULT_SETTING, RETIRED_MODELS;
|
|
19315
|
+
var STEP, DEFAULT_SETTING, normalizeClaudeModel, normalizeCodexModel, RETIRED_MODELS;
|
|
19122
19316
|
var init_settings3 = __esm({
|
|
19123
19317
|
"src/services/settings.ts"() {
|
|
19124
19318
|
"use strict";
|
|
19125
19319
|
init_db();
|
|
19126
19320
|
init_src();
|
|
19127
|
-
STEP = { model: "
|
|
19321
|
+
STEP = { model: "sonnet", effort: "medium" };
|
|
19128
19322
|
DEFAULT_SETTING = {
|
|
19129
19323
|
...STEP,
|
|
19130
19324
|
autoDefault: true,
|
|
@@ -19173,7 +19367,9 @@ var init_settings3 = __esm({
|
|
|
19173
19367
|
builtinRuntimeDefaults: BUILTIN_RUNTIME_DEFAULTS
|
|
19174
19368
|
// seed rekomendasi model/fase; user edit dipertahankan
|
|
19175
19369
|
};
|
|
19176
|
-
|
|
19370
|
+
normalizeClaudeModel = (m) => RETIRED_MODELS[m] ?? m;
|
|
19371
|
+
normalizeCodexModel = (m) => codexModel(m) ? m : RETIRED_CODEX_MODELS[m] ?? m;
|
|
19372
|
+
RETIRED_MODELS = { "claude-opus-4-8": "opus" };
|
|
19177
19373
|
}
|
|
19178
19374
|
});
|
|
19179
19375
|
|
|
@@ -20699,13 +20895,13 @@ function inferLegacyStates(raw) {
|
|
|
20699
20895
|
}
|
|
20700
20896
|
}
|
|
20701
20897
|
}
|
|
20702
|
-
const
|
|
20898
|
+
const claudeModel2 = !has("model") || object2.model === LEGACY_CLAUDE_RUNTIME_DEFAULTS.model ? "seeded" : "user";
|
|
20703
20899
|
const claudeEffort = !has("effort") || object2.effort === LEGACY_CLAUDE_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
|
|
20704
20900
|
const rawCodexObject = isRecord(rawCodex) ? rawCodex : {};
|
|
20705
20901
|
const codexModel2 = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "model") || rawCodexObject.model === LEGACY_CODEX_RUNTIME_DEFAULTS.model ? "seeded" : "user";
|
|
20706
20902
|
const codexEffort = !has("codex") || !Object.prototype.hasOwnProperty.call(rawCodexObject, "effort") || rawCodexObject.effort === LEGACY_CODEX_RUNTIME_DEFAULTS.effort ? "seeded" : "user";
|
|
20707
20903
|
return {
|
|
20708
|
-
claude: { model:
|
|
20904
|
+
claude: { model: claudeModel2, effort: claudeEffort },
|
|
20709
20905
|
codex: { model: codexModel2, effort: codexEffort },
|
|
20710
20906
|
orchestration
|
|
20711
20907
|
};
|
|
@@ -21578,7 +21774,7 @@ var init_engine_command = __esm({
|
|
|
21578
21774
|
init_src();
|
|
21579
21775
|
AGENT_LABEL = { claude: "Claude Code", codex: "Codex CLI" };
|
|
21580
21776
|
TELEGRAM_CONTROL_KIND = "gateway-control";
|
|
21581
|
-
claudeModels = () => MODELS.
|
|
21777
|
+
claudeModels = () => [...new Set(MODELS.flatMap((m) => m.resolved ? [m.id, m.resolved] : [m.id]))];
|
|
21582
21778
|
codexModels = () => CODEX_MODELS.map((m) => m.id);
|
|
21583
21779
|
effortsFor = (t) => t.agent === "codex" ? codexEfforts(t.model) : claudeEfforts(t.model);
|
|
21584
21780
|
usage = [
|
|
@@ -24246,7 +24442,7 @@ var require_sender = __commonJS({
|
|
|
24246
24442
|
*/
|
|
24247
24443
|
static frame(data, options2) {
|
|
24248
24444
|
let mask;
|
|
24249
|
-
let
|
|
24445
|
+
let merge = false;
|
|
24250
24446
|
let offset = 2;
|
|
24251
24447
|
let skipMasking = false;
|
|
24252
24448
|
if (options2.mask) {
|
|
@@ -24279,7 +24475,7 @@ var require_sender = __commonJS({
|
|
|
24279
24475
|
}
|
|
24280
24476
|
} else {
|
|
24281
24477
|
dataLength = data.length;
|
|
24282
|
-
|
|
24478
|
+
merge = options2.mask && options2.readOnly && !skipMasking;
|
|
24283
24479
|
}
|
|
24284
24480
|
let payloadLength = dataLength;
|
|
24285
24481
|
if (dataLength >= 65536) {
|
|
@@ -24289,7 +24485,7 @@ var require_sender = __commonJS({
|
|
|
24289
24485
|
offset += 2;
|
|
24290
24486
|
payloadLength = 126;
|
|
24291
24487
|
}
|
|
24292
|
-
const target3 = Buffer.allocUnsafe(
|
|
24488
|
+
const target3 = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
|
|
24293
24489
|
target3[0] = options2.fin ? options2.opcode | 128 : options2.opcode;
|
|
24294
24490
|
if (options2.rsv1) target3[0] |= 64;
|
|
24295
24491
|
target3[1] = payloadLength;
|
|
@@ -24306,7 +24502,7 @@ var require_sender = __commonJS({
|
|
|
24306
24502
|
target3[offset - 2] = mask[2];
|
|
24307
24503
|
target3[offset - 1] = mask[3];
|
|
24308
24504
|
if (skipMasking) return [target3, data];
|
|
24309
|
-
if (
|
|
24505
|
+
if (merge) {
|
|
24310
24506
|
applyMask(data, mask, target3, offset, dataLength);
|
|
24311
24507
|
return [target3];
|
|
24312
24508
|
}
|
|
@@ -27501,7 +27697,7 @@ function recommendedModel(row, runtime) {
|
|
|
27501
27697
|
if (!builtin) return null;
|
|
27502
27698
|
const model = builtin.models[runtime];
|
|
27503
27699
|
if (runtime === "claude" && (model === "haiku" || model === "sonnet")) return model;
|
|
27504
|
-
return
|
|
27700
|
+
return modelKnownForRuntime(runtime, model) ? model : null;
|
|
27505
27701
|
}
|
|
27506
27702
|
function toRuntimeDef(row, runtime) {
|
|
27507
27703
|
return { ...toDef(row), model: recommendedModel(row, runtime) };
|
|
@@ -41239,6 +41435,24 @@ async function reconciledSpecIdsSince(cutoff) {
|
|
|
41239
41435
|
});
|
|
41240
41436
|
return rows.map((r) => r.specId);
|
|
41241
41437
|
}
|
|
41438
|
+
async function reconciledRuntimesSince(cutoff) {
|
|
41439
|
+
const rows = await prisma.sessionHistory.findMany({
|
|
41440
|
+
where: { endedReason: RECONCILED, reconciledAt: { gte: cutoff }, specId: { not: null } },
|
|
41441
|
+
select: { specId: true, agent: true, model: true, effort: true },
|
|
41442
|
+
orderBy: { startedAt: "desc" }
|
|
41443
|
+
});
|
|
41444
|
+
const out4 = /* @__PURE__ */ new Map();
|
|
41445
|
+
for (const r of rows) {
|
|
41446
|
+
if (out4.has(r.specId)) continue;
|
|
41447
|
+
out4.set(r.specId, {
|
|
41448
|
+
// `agent` kolom String — nilai asing jatuh ke claude, cermin pembacaan @hanoman_agent (pty.ts).
|
|
41449
|
+
agent: r.agent === "codex" ? "codex" : "claude",
|
|
41450
|
+
...r.model ? { model: r.model } : {},
|
|
41451
|
+
...r.effort ? { effort: r.effort } : {}
|
|
41452
|
+
});
|
|
41453
|
+
}
|
|
41454
|
+
return out4;
|
|
41455
|
+
}
|
|
41242
41456
|
function installSessionHistory() {
|
|
41243
41457
|
registerSessionHooks({
|
|
41244
41458
|
onBirth: (b) => {
|
|
@@ -41905,13 +42119,15 @@ init_pty();
|
|
|
41905
42119
|
// src/services/orchestration.ts
|
|
41906
42120
|
init_src();
|
|
41907
42121
|
init_pty();
|
|
42122
|
+
init_settings3();
|
|
41908
42123
|
function sessionPhasePlan(setting, flow2, agent, orchestrator, phaseOverrides) {
|
|
41909
42124
|
return resolvePhasePlan({
|
|
41910
42125
|
flow: flow2,
|
|
41911
42126
|
runtime: agent,
|
|
41912
42127
|
orchestration: setting.orchestration,
|
|
41913
42128
|
orchestrator,
|
|
41914
|
-
|
|
42129
|
+
// S6 · override transient dinormalisasi sama seperti sel Setting (model pensiun, koersi codex).
|
|
42130
|
+
phaseOverrides: normalizePhaseOverrides(agent, phaseOverrides),
|
|
41915
42131
|
nativeAgents: nativeAgentsAvailable(agent)
|
|
41916
42132
|
});
|
|
41917
42133
|
}
|
|
@@ -42061,7 +42277,9 @@ async function startSpecSession(spec, opts) {
|
|
|
42061
42277
|
method,
|
|
42062
42278
|
verifyScope,
|
|
42063
42279
|
context: isGoalFlow ? goalContext(brief) : specContext(brief),
|
|
42064
|
-
fromAudit: fromAuditOf(spec.payload)
|
|
42280
|
+
fromAudit: fromAuditOf(spec.payload),
|
|
42281
|
+
// S2 · SPEC-394 · subagent tak melihat `resumeClause` orchestrator — ia dapat catatannya sendiri.
|
|
42282
|
+
...resumeCtx ? { resume: resumeCtx } : {}
|
|
42065
42283
|
}) : [];
|
|
42066
42284
|
const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
|
|
42067
42285
|
const s2 = createSession(spec.projectId, worktree, {
|
|
@@ -42077,6 +42295,9 @@ async function startSpecSession(spec, opts) {
|
|
|
42077
42295
|
prompt,
|
|
42078
42296
|
legacyPrompt,
|
|
42079
42297
|
phaseAgents,
|
|
42298
|
+
// S3 · SPEC-172 · berkas fase continue masih memuat `Execute done` run lama; fase yang DIKERJAKAN
|
|
42299
|
+
// ULANG sesi ini tak boleh dihitung `doneAtBirth`, supaya ⚠ bukti tetap bisa menyala.
|
|
42300
|
+
...isContinue && plan ? { rerunPhases: plan.phases.map((p3) => p3.phase) } : {},
|
|
42080
42301
|
env: scopeEnv
|
|
42081
42302
|
});
|
|
42082
42303
|
return resume ? { id: s2.id, resumed: true } : { id: s2.id };
|
|
@@ -42534,6 +42755,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
|
|
|
42534
42755
|
// src/services/session-event-relay.ts
|
|
42535
42756
|
init_session_event_token();
|
|
42536
42757
|
init_session_event_spool();
|
|
42758
|
+
init_ingress_policy();
|
|
42537
42759
|
import { constants as constants3 } from "node:fs";
|
|
42538
42760
|
import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
42539
42761
|
import { join as join25 } from "node:path";
|
|
@@ -42552,18 +42774,22 @@ function sessionEventRelayStatus(root = sessionEventSpoolRoot()) {
|
|
|
42552
42774
|
droppedEvents: 0
|
|
42553
42775
|
} };
|
|
42554
42776
|
}
|
|
42555
|
-
async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
|
|
42777
|
+
async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot(), host2 = null) {
|
|
42556
42778
|
const previous = sessionEventRelayStatus(root);
|
|
42557
|
-
const current = { retries: 0, dropped: 0 };
|
|
42779
|
+
const current = { retries: 0, dropped: 0, rejected: /* @__PURE__ */ new Map() };
|
|
42558
42780
|
let delivered = 0;
|
|
42559
42781
|
let failed = false;
|
|
42560
42782
|
try {
|
|
42561
|
-
delivered = await drainSpool(app3, root, current);
|
|
42783
|
+
delivered = await drainSpool(app3, root, current, host2);
|
|
42562
42784
|
return delivered;
|
|
42563
42785
|
} catch (error) {
|
|
42564
42786
|
failed = true;
|
|
42565
42787
|
throw error;
|
|
42566
42788
|
} finally {
|
|
42789
|
+
if (current.rejected.size) {
|
|
42790
|
+
const summary = [...current.rejected].map(([code, n2]) => `${n2}\xD7 ${code}`).join(", ");
|
|
42791
|
+
console.warn(`session event relay: route menolak event (${summary}) \u2014 event dibuang`);
|
|
42792
|
+
}
|
|
42567
42793
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42568
42794
|
observations.set(root, {
|
|
42569
42795
|
state: failed || current.retries > 0 ? "degraded" : "ready",
|
|
@@ -42576,7 +42802,7 @@ async function drainSessionEventSpool(app3, root = sessionEventSpoolRoot()) {
|
|
|
42576
42802
|
});
|
|
42577
42803
|
}
|
|
42578
42804
|
}
|
|
42579
|
-
async function drainSpool(app3, root, observation) {
|
|
42805
|
+
async function drainSpool(app3, root, observation, host2) {
|
|
42580
42806
|
await mkdir6(root, { recursive: true, mode: 448 });
|
|
42581
42807
|
let delivered = 0;
|
|
42582
42808
|
let examined = 0;
|
|
@@ -42590,8 +42816,10 @@ async function drainSpool(app3, root, observation) {
|
|
|
42590
42816
|
} catch {
|
|
42591
42817
|
continue;
|
|
42592
42818
|
}
|
|
42819
|
+
entries3.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
42593
42820
|
for (const entry of entries3) {
|
|
42594
42821
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
42822
|
+
const eventAt = /^(\d{1,15})-/.exec(entry.name)?.[1];
|
|
42595
42823
|
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
42596
42824
|
const path = join25(dir2, entry.name);
|
|
42597
42825
|
let payload;
|
|
@@ -42623,7 +42851,11 @@ async function drainSpool(app3, root, observation) {
|
|
|
42623
42851
|
url: "/api/session-events",
|
|
42624
42852
|
headers: {
|
|
42625
42853
|
authorization: `Bearer ${sessionEventToken(session.name)}`,
|
|
42626
|
-
"x-hanoman-session": session.name
|
|
42854
|
+
"x-hanoman-session": session.name,
|
|
42855
|
+
...host2 ? { host: host2 } : {},
|
|
42856
|
+
// S1 · waktu KEJADIAN event (bukan waktu drain) — route memakainya untuk membedakan
|
|
42857
|
+
// replay dari lanjutan relay subagent yang sama.
|
|
42858
|
+
...eventAt ? { "x-hanoman-event-at": eventAt } : {}
|
|
42627
42859
|
},
|
|
42628
42860
|
payload
|
|
42629
42861
|
});
|
|
@@ -42634,7 +42866,10 @@ async function drainSpool(app3, root, observation) {
|
|
|
42634
42866
|
await rm6(path, { force: true }).catch(() => {
|
|
42635
42867
|
});
|
|
42636
42868
|
if (response.statusCode >= 200 && response.statusCode < 300) delivered++;
|
|
42637
|
-
else
|
|
42869
|
+
else {
|
|
42870
|
+
observation.dropped++;
|
|
42871
|
+
observation.rejected.set(response.statusCode, (observation.rejected.get(response.statusCode) ?? 0) + 1);
|
|
42872
|
+
}
|
|
42638
42873
|
} catch {
|
|
42639
42874
|
observation.retries++;
|
|
42640
42875
|
}
|
|
@@ -42644,12 +42879,13 @@ async function drainSpool(app3, root, observation) {
|
|
|
42644
42879
|
}
|
|
42645
42880
|
function startSessionEventRelay(app3, options2 = {}) {
|
|
42646
42881
|
const root = options2.root ?? sessionEventSpoolRoot();
|
|
42882
|
+
const host2 = controlHost(loadIngressPolicy(options2.env ?? process.env));
|
|
42647
42883
|
let running = false;
|
|
42648
42884
|
const tick6 = async () => {
|
|
42649
42885
|
if (running) return;
|
|
42650
42886
|
running = true;
|
|
42651
42887
|
try {
|
|
42652
|
-
await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root);
|
|
42888
|
+
await drainSessionEventSpool({ inject: async (request) => app3.inject(request) }, root, host2);
|
|
42653
42889
|
} catch (error) {
|
|
42654
42890
|
console.error("session event relay gagal:", error);
|
|
42655
42891
|
} finally {
|
|
@@ -42762,6 +42998,15 @@ async function startAgentInvocation(input, io = {}) {
|
|
|
42762
42998
|
runtimeInvocationId: input.runtimeInvocationId
|
|
42763
42999
|
} }
|
|
42764
43000
|
});
|
|
43001
|
+
if (existing?.endedAt && startedAt.getTime() > existing.endedAt.getTime()) {
|
|
43002
|
+
const row2 = await prisma.agentInvocation.update({
|
|
43003
|
+
where: { id: existing.id },
|
|
43004
|
+
data: { status: "running", endedAt: null, durationMs: null }
|
|
43005
|
+
});
|
|
43006
|
+
const before3 = snapshot2(input.cwd, io.gitStatus);
|
|
43007
|
+
if (before3 !== null) snapshotHashes.set(keyOf(input), before3);
|
|
43008
|
+
return { row: row2, duplicate: false };
|
|
43009
|
+
}
|
|
42765
43010
|
const row = await prisma.agentInvocation.upsert({
|
|
42766
43011
|
where: { sessionId_runtimeInvocationId: {
|
|
42767
43012
|
sessionId: input.sessionId,
|
|
@@ -42812,7 +43057,8 @@ async function stopAgentInvocation(input, io = {}) {
|
|
|
42812
43057
|
...usage2,
|
|
42813
43058
|
resultExcerpt: cleanResult === null ? null : utf8Prefix(cleanResult, MAX_EXCERPT_BYTES),
|
|
42814
43059
|
resultHash: cleanResult === null ? null : hash3(cleanResult),
|
|
42815
|
-
|
|
43060
|
+
// S1 · putaran relay (baris dibuka ulang) tak menghapus perubahan workspace putaran sebelumnya.
|
|
43061
|
+
workspaceChanged: (existing?.workspaceChanged ?? false) || before2 !== void 0 && after !== null && before2 !== after,
|
|
42816
43062
|
// ADR-0164 · effort yang BENAR-BENAR dipakai runtime menang atas nilai start-time.
|
|
42817
43063
|
...input.effort ? { effort: input.effort } : {}
|
|
42818
43064
|
};
|
|
@@ -44402,6 +44648,7 @@ var ensureWorktree = (repoDir, wt, branchFrom) => {
|
|
|
44402
44648
|
var resumeNote = (reused) => reused ? `
|
|
44403
44649
|
|
|
44404
44650
|
${RESUMED_WORKTREE_NOTE}` : "";
|
|
44651
|
+
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
44652
|
async function terminal_default(app3, opts) {
|
|
44406
44653
|
app3.get("/terminal/sessions", async () => listSessions());
|
|
44407
44654
|
app3.post("/terminal/sessions", async (req, reply) => {
|
|
@@ -44496,7 +44743,12 @@ async function terminal_default(app3, opts) {
|
|
|
44496
44743
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
44497
44744
|
prompt: plan ? startProjectPrompt("reverse", brief, "reverse-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
44498
44745
|
legacyPrompt,
|
|
44499
|
-
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44746
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44747
|
+
flow: "reverse",
|
|
44748
|
+
method: PROJECT_METHOD,
|
|
44749
|
+
context: projectContext(brief),
|
|
44750
|
+
...phaseResume(reused, repoDir, id2, "reverse")
|
|
44751
|
+
}) : []
|
|
44500
44752
|
});
|
|
44501
44753
|
return { code: 201, body: { id: s3.id } };
|
|
44502
44754
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -44528,7 +44780,12 @@ async function terminal_default(app3, opts) {
|
|
|
44528
44780
|
decisionFile: decisionFilePath(repoDir, id2),
|
|
44529
44781
|
prompt: plan ? startScaffoldPrompt(brief, "scaffold-docs", plan) + resumeNote(reused) : legacyPrompt,
|
|
44530
44782
|
legacyPrompt,
|
|
44531
|
-
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44783
|
+
phaseAgents: plan ? buildPhaseAgents(plan, {
|
|
44784
|
+
flow: "scaffold",
|
|
44785
|
+
method: PROJECT_METHOD,
|
|
44786
|
+
context: scaffoldContext(brief),
|
|
44787
|
+
...phaseResume(reused, repoDir, id2, "scaffold")
|
|
44788
|
+
}) : []
|
|
44532
44789
|
});
|
|
44533
44790
|
return { code: 201, body: { id: s3.id } };
|
|
44534
44791
|
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
@@ -44569,7 +44826,8 @@ async function terminal_default(app3, opts) {
|
|
|
44569
44826
|
flow: "prd",
|
|
44570
44827
|
method: PROJECT_METHOD,
|
|
44571
44828
|
context: prdContext(project_, brief, audit),
|
|
44572
|
-
prd: { slug }
|
|
44829
|
+
prd: { slug },
|
|
44830
|
+
...phaseResume(reused, repoDir, id2, "prd")
|
|
44573
44831
|
}) : []
|
|
44574
44832
|
});
|
|
44575
44833
|
return { code: 201, body: { id: s3.id } };
|
|
@@ -44615,7 +44873,8 @@ async function terminal_default(app3, opts) {
|
|
|
44615
44873
|
flow: "breakdown",
|
|
44616
44874
|
method: PROJECT_METHOD,
|
|
44617
44875
|
context: breakdownContext(project_, prd),
|
|
44618
|
-
prd: { slug, title }
|
|
44876
|
+
prd: { slug, title },
|
|
44877
|
+
...phaseResume(reused, repoDir, id2, "breakdown")
|
|
44619
44878
|
}) : []
|
|
44620
44879
|
});
|
|
44621
44880
|
return { code: 201, body: { id: s3.id } };
|
|
@@ -45891,7 +46150,7 @@ async function vps_default(app3) {
|
|
|
45891
46150
|
init_config2();
|
|
45892
46151
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
45893
46152
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
45894
|
-
import { homedir as homedir8 } from "node:os";
|
|
46153
|
+
import { homedir as homedir8, userInfo } from "node:os";
|
|
45895
46154
|
import { join as join31 } from "node:path";
|
|
45896
46155
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
45897
46156
|
var TTL_MS2 = 3e4;
|
|
@@ -45907,19 +46166,30 @@ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
|
45907
46166
|
function credsFile() {
|
|
45908
46167
|
return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir8(), ".claude"), ".credentials.json");
|
|
45909
46168
|
}
|
|
45910
|
-
|
|
45911
|
-
|
|
46169
|
+
var securityRun = (args) => execFileSync5("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
46170
|
+
var osAccount = () => {
|
|
46171
|
+
try {
|
|
46172
|
+
return userInfo().username || null;
|
|
46173
|
+
} catch {
|
|
46174
|
+
return null;
|
|
46175
|
+
}
|
|
46176
|
+
};
|
|
46177
|
+
function keychainAccessToken(run4 = securityRun, account = osAccount()) {
|
|
46178
|
+
const base2 = ["find-generic-password", "-s", "Claude Code-credentials"];
|
|
46179
|
+
for (const scope of account ? [["-a", account], []] : [[]]) {
|
|
45912
46180
|
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;
|
|
46181
|
+
const tok = JSON.parse(run4([...base2, ...scope, "-w"]))?.claudeAiOauth?.accessToken;
|
|
46182
|
+
if (typeof tok === "string" && tok) return tok;
|
|
45920
46183
|
} catch {
|
|
45921
46184
|
}
|
|
45922
46185
|
}
|
|
46186
|
+
return null;
|
|
46187
|
+
}
|
|
46188
|
+
function readAccessToken() {
|
|
46189
|
+
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
46190
|
+
const tok = keychainAccessToken();
|
|
46191
|
+
if (tok) return tok;
|
|
46192
|
+
}
|
|
45923
46193
|
try {
|
|
45924
46194
|
return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
45925
46195
|
} catch {
|
|
@@ -46125,19 +46395,25 @@ var claudeRow = external_exports.object({
|
|
|
46125
46395
|
});
|
|
46126
46396
|
function parseClaudeModels(raw) {
|
|
46127
46397
|
const rows = external_exports.array(claudeRow).min(1).max(500).parse(raw);
|
|
46398
|
+
let preferred = null;
|
|
46128
46399
|
const models2 = /* @__PURE__ */ new Map();
|
|
46129
46400
|
for (const row of rows) {
|
|
46401
|
+
const resolved = row.resolvedModel ?? row.value;
|
|
46402
|
+
const head = row.description?.split(" \xB7 ")[0];
|
|
46130
46403
|
const model = {
|
|
46131
|
-
id: row.
|
|
46132
|
-
label: row.
|
|
46404
|
+
id: row.value,
|
|
46405
|
+
label: row.value === "default" && head ? `${row.displayName} \xB7 ${head}` : head || row.displayName,
|
|
46406
|
+
resolved,
|
|
46133
46407
|
...row.supportedEffortLevels ? { efforts: sorted(row.supportedEffortLevels) } : {}
|
|
46134
46408
|
};
|
|
46135
|
-
if (
|
|
46136
|
-
|
|
46137
|
-
|
|
46409
|
+
if (row.value === "default") {
|
|
46410
|
+
preferred ??= model;
|
|
46411
|
+
continue;
|
|
46138
46412
|
}
|
|
46413
|
+
const existing = models2.get(resolved);
|
|
46414
|
+
if (!existing || existing.id === resolved && row.value !== resolved) models2.set(resolved, model);
|
|
46139
46415
|
}
|
|
46140
|
-
return [...models2.values()];
|
|
46416
|
+
return preferred ? [preferred, ...models2.values()] : [...models2.values()];
|
|
46141
46417
|
}
|
|
46142
46418
|
var codexRow = external_exports.object({
|
|
46143
46419
|
slug: id,
|
|
@@ -46171,7 +46447,7 @@ function parseCachedCatalog(raw) {
|
|
|
46171
46447
|
error: external_exports.string().nullable()
|
|
46172
46448
|
});
|
|
46173
46449
|
return external_exports.object({
|
|
46174
|
-
claude: external_exports.array(external_exports.object({ id, label, efforts: efforts.optional() })).min(1).max(1e3),
|
|
46450
|
+
claude: external_exports.array(external_exports.object({ id, label, resolved: id.optional(), efforts: efforts.optional() })).min(1).max(1e3),
|
|
46175
46451
|
codex: external_exports.array(external_exports.object({ id, label, efforts: efforts.min(1), fallback: id, minClient: external_exports.string() })).min(1).max(1e3),
|
|
46176
46452
|
providers: external_exports.object({ claude: status2, codex: status2 })
|
|
46177
46453
|
}).parse(raw);
|
|
@@ -46297,7 +46573,6 @@ async function probeModelCatalog(agent) {
|
|
|
46297
46573
|
}
|
|
46298
46574
|
|
|
46299
46575
|
// src/services/model-catalog.ts
|
|
46300
|
-
var merge = (old, fresh) => [...new Map([...old, ...fresh].map((m) => [m.id, m])).values()];
|
|
46301
46576
|
function createModelCatalogService(deps) {
|
|
46302
46577
|
let state = bundledModelCatalog();
|
|
46303
46578
|
let initialized = false;
|
|
@@ -46324,7 +46599,7 @@ function createModelCatalogService(deps) {
|
|
|
46324
46599
|
try {
|
|
46325
46600
|
const models2 = await deps.probe(agent);
|
|
46326
46601
|
if (!models2.length) throw new Error("empty catalog");
|
|
46327
|
-
state = agent === "claude" ? { ...state, claude:
|
|
46602
|
+
state = agent === "claude" ? { ...state, claude: models2 } : { ...state, codex: models2 };
|
|
46328
46603
|
state = { ...state, providers: { ...state.providers, [agent]: {
|
|
46329
46604
|
source: "cli",
|
|
46330
46605
|
checkedAt,
|
|
@@ -48187,6 +48462,13 @@ init_pty();
|
|
|
48187
48462
|
var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
|
|
48188
48463
|
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
48189
48464
|
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
48465
|
+
var EVENT_AT_PAST_MS = 7 * 24 * 60 * 6e4;
|
|
48466
|
+
var EVENT_AT_FUTURE_MS = 6e4;
|
|
48467
|
+
var eventAtOf = (header2, now = Date.now()) => {
|
|
48468
|
+
if (typeof header2 !== "string" || !/^\d{1,15}$/.test(header2)) return void 0;
|
|
48469
|
+
const ms = Number(header2);
|
|
48470
|
+
return ms >= now - EVENT_AT_PAST_MS && ms <= now + EVENT_AT_FUTURE_MS ? new Date(ms) : void 0;
|
|
48471
|
+
};
|
|
48190
48472
|
async function session_events_default(app3) {
|
|
48191
48473
|
app3.post("/session-events", async (req, reply) => {
|
|
48192
48474
|
const sessionId2 = String(req.headers["x-hanoman-session"] ?? "");
|
|
@@ -48219,8 +48501,10 @@ async function session_events_default(app3) {
|
|
|
48219
48501
|
...meta.effort ? { effort: meta.effort } : {},
|
|
48220
48502
|
cwd: s2.cwd
|
|
48221
48503
|
};
|
|
48222
|
-
const
|
|
48504
|
+
const eventAt = eventAtOf(req.headers["x-hanoman-event-at"]);
|
|
48505
|
+
const outcome = lifecycle === "SubagentStart" ? await startAgentInvocation({ ...identity, ...eventAt ? { startedAt: eventAt } : {} }) : await stopAgentInvocation({
|
|
48223
48506
|
...identity,
|
|
48507
|
+
...eventAt ? { endedAt: eventAt } : {},
|
|
48224
48508
|
status: body.status === "interrupted" ? "interrupted" : "completed",
|
|
48225
48509
|
result: boundedString(body.last_assistant_message ?? body.result, 1e6),
|
|
48226
48510
|
transcriptPath: boundedString(
|
|
@@ -49542,7 +49826,7 @@ function toolsProblem(tools, catalogIds) {
|
|
|
49542
49826
|
}
|
|
49543
49827
|
function modelProblem(model, runtime) {
|
|
49544
49828
|
if (!model) return null;
|
|
49545
|
-
const ok2 =
|
|
49829
|
+
const ok2 = modelKnownForRuntime(runtime, model);
|
|
49546
49830
|
return ok2 ? null : { error: "model tak dikenal untuk runtime ini", model, runtime };
|
|
49547
49831
|
}
|
|
49548
49832
|
function effortProblem(effort2, runtime, model) {
|
|
@@ -53163,7 +53447,11 @@ var prodDeps2 = {
|
|
|
53163
53447
|
// dependency ADR-0093 TETAP berlaku (tak diberi `force`). Keputusan sadar risiko: mesin 8 GB
|
|
53164
53448
|
// operator sudah pernah kernel panic akibat sesi paralel berlebih (memori
|
|
53165
53449
|
// mac-mini-8gb-panic-agen-paralel) — operator memilih "semua kembali" di atas throttle.
|
|
53166
|
-
startSpec: (spec) => startSpecSession(spec, {
|
|
53450
|
+
startSpec: (spec, runtime) => startSpecSession(spec, {
|
|
53451
|
+
flow: flowForSource(spec.source),
|
|
53452
|
+
bypassCapacity: true,
|
|
53453
|
+
...runtime ?? {}
|
|
53454
|
+
}),
|
|
53167
53455
|
recordFail: recordFailure
|
|
53168
53456
|
};
|
|
53169
53457
|
async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
@@ -53171,9 +53459,10 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps2) {
|
|
|
53171
53459
|
const specIds = await reconciledSpecIdsSince(cutoff);
|
|
53172
53460
|
if (!specIds.length) return report;
|
|
53173
53461
|
const specs = await prisma.spec.findMany({ where: { id: { in: specIds }, stage: { not: "done" } } });
|
|
53462
|
+
const runtimes = await reconciledRuntimesSince(cutoff);
|
|
53174
53463
|
for (const spec of specs) {
|
|
53175
53464
|
try {
|
|
53176
|
-
await deps.startSpec(spec);
|
|
53465
|
+
await deps.startSpec(spec, runtimes.get(spec.id));
|
|
53177
53466
|
report.resumed.push(spec.id);
|
|
53178
53467
|
} catch (e) {
|
|
53179
53468
|
const reason = e instanceof Error ? e.message : String(e);
|