trip-optimizer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/dist/chunk-OSZUVWNK.js +282 -0
- package/dist/cli.js +2781 -0
- package/dist/commands/run-agent.js +98 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# trip-optimizer
|
|
2
|
+
|
|
3
|
+
Autonomously optimize travel plans using the autoresearch pattern -- an AI-powered CLI that researches, scores, and iteratively improves your itinerary.
|
|
4
|
+
|
|
5
|
+
支持 **English** 和 **中文(简体中文)** -- 在初始化时选择语言,整个体验随之适配:提示语、生成的行程、研究搜索、评分系统全部使用您选择的语言。
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g trip-optimizer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx trip-optimizer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
trip-optimizer init "Japan 2027"
|
|
23
|
+
cd japan-2027
|
|
24
|
+
trip-optimizer run # agent mode (default, interactive Claude Code)
|
|
25
|
+
trip-optimizer run --standalone # direct API calls
|
|
26
|
+
trip-optimizer run --headless # agent mode, non-interactive
|
|
27
|
+
trip-optimizer dashboard --watch
|
|
28
|
+
trip-optimizer plan --pdf # generate a formatted PDF itinerary
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| `init <name>` | Create a new trip project |
|
|
36
|
+
| `config` | Manage API keys and settings |
|
|
37
|
+
| `profile` | View travel profile |
|
|
38
|
+
| `score` | One-off absolute scoring |
|
|
39
|
+
| `research [city]` | Research sprint |
|
|
40
|
+
| `run` | Start optimization loop (agent mode) |
|
|
41
|
+
| `run --standalone` | Optimization via direct API calls |
|
|
42
|
+
| `run --headless` | Agent mode, non-interactive |
|
|
43
|
+
| `status` | Show progress |
|
|
44
|
+
| `dashboard` | Live optimization dashboard |
|
|
45
|
+
| `chart` | ASCII score chart |
|
|
46
|
+
| `plan` | Pretty-print travel plan |
|
|
47
|
+
| `plan --pdf` | Generate a PDF document |
|
|
48
|
+
| `debrief` | Post-trip feedback |
|
|
49
|
+
| `history` | View past trips |
|
|
50
|
+
|
|
51
|
+
## How It Works
|
|
52
|
+
|
|
53
|
+
Trip-optimizer follows the **autoresearch pattern**: it autonomously researches destinations, generates plan mutations, scores results, and keeps only improvements. Each optimization iteration proposes targeted changes -- swapping a restaurant, adjusting timing, adding a hidden-gem activity -- then evaluates whether the change improved the overall plan. Bad mutations are discarded; good ones accumulate.
|
|
54
|
+
|
|
55
|
+
Scoring uses a **3-pass pipeline**. First, the plan is evaluated across seven weighted dimensions (experience, logistics, food, time management, budget, accommodation, and transit). Then an adversarial critic searches for concrete flaws -- unconfirmed bookings, chain restaurants, vague transit -- and applies penalties. Finally, a holistic adjustment reconciles the dimension scores with the critic's findings into a single composite score.
|
|
56
|
+
|
|
57
|
+
The system builds **persistent memory** across trips. After each trip, a debrief captures what worked and what didn't. These learnings are stored in `learned.json` and feed back into scoring rubrics and research priorities for future trips, so the optimizer gets smarter over time.
|
|
58
|
+
|
|
59
|
+
### 语言与本地化
|
|
60
|
+
|
|
61
|
+
`init` 的第一个问题是语言选择。选择 **中文** 后:
|
|
62
|
+
|
|
63
|
+
- 所有命令行提示和消息以中文显示
|
|
64
|
+
- 生成的行程、评分标准和计划均以简体中文撰写
|
|
65
|
+
- 研究优先使用中文平台(小红书、大众点评、马蜂窝、携程),而非英文来源
|
|
66
|
+
- 搜索关键词使用中文(本地人推荐、避雷指南、苍蝇馆子),同时辅以英文补充搜索
|
|
67
|
+
- PDF 输出正确渲染中文内容
|
|
68
|
+
|
|
69
|
+
### Custom Model Support
|
|
70
|
+
|
|
71
|
+
During `init`, you can optionally configure a custom LLM instead of the default Anthropic/Vertex provider. Any OpenAI-compatible API works -- Kimi (Moonshot), DeepSeek, and others. Custom models run in `--standalone` mode; agent mode always uses Claude Code.
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
- Node.js 22+
|
|
76
|
+
- One of:
|
|
77
|
+
- Anthropic API key (via `trip-optimizer config` or `ANTHROPIC_API_KEY`)
|
|
78
|
+
- Google Cloud Vertex AI (`CLAUDE_CODE_USE_VERTEX=1` + `GOOGLE_CLOUD_PROJECT`)
|
|
79
|
+
- Custom OpenAI-compatible API (configured during `init`)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/data/config.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path2 from "path";
|
|
6
|
+
|
|
7
|
+
// src/data/paths.ts
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
function getGlobalDir() {
|
|
11
|
+
return path.join(os.homedir(), ".trip-optimizer");
|
|
12
|
+
}
|
|
13
|
+
function getTripHistoryPath() {
|
|
14
|
+
return path.join(getGlobalDir(), "trip-history.json");
|
|
15
|
+
}
|
|
16
|
+
function getLearnedPath() {
|
|
17
|
+
return path.join(getGlobalDir(), "learned.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/data/config.ts
|
|
21
|
+
var DEFAULT_CONFIG = {
|
|
22
|
+
provider: "anthropic",
|
|
23
|
+
api_key: "",
|
|
24
|
+
language: "en"
|
|
25
|
+
};
|
|
26
|
+
function loadConfig(dir) {
|
|
27
|
+
const configPath = path2.join(dir ?? getGlobalDir(), "config.json");
|
|
28
|
+
if (!fs.existsSync(configPath)) return { ...DEFAULT_CONFIG };
|
|
29
|
+
const loaded = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
30
|
+
return { ...DEFAULT_CONFIG, ...loaded };
|
|
31
|
+
}
|
|
32
|
+
function saveConfig(config, dir) {
|
|
33
|
+
const d = dir ?? getGlobalDir();
|
|
34
|
+
fs.mkdirSync(d, { recursive: true });
|
|
35
|
+
fs.writeFileSync(path2.join(d, "config.json"), JSON.stringify(config, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/i18n.ts
|
|
39
|
+
var messages = {
|
|
40
|
+
// Init flow
|
|
41
|
+
"init.title": { en: "trip-optimizer", zh: "trip-optimizer" },
|
|
42
|
+
"init.language": { en: "Language / \u8BED\u8A00:", zh: "\u8BED\u8A00 / Language:" },
|
|
43
|
+
"init.first_time": { en: "First time? Let's set up your profile.", zh: "\u9996\u6B21\u4F7F\u7528\uFF1F\u8BA9\u6211\u4EEC\u8BBE\u7F6E\u60A8\u7684\u4E2A\u4EBA\u8D44\u6599\u3002" },
|
|
44
|
+
"init.vertex_detected": { en: "Using Vertex AI (detected from environment).", zh: "\u4F7F\u7528 Vertex AI\uFF08\u4ECE\u73AF\u5883\u53D8\u91CF\u68C0\u6D4B\uFF09\u3002" },
|
|
45
|
+
"init.api_key": { en: "Anthropic API key:", zh: "Anthropic API \u5BC6\u94A5\uFF1A" },
|
|
46
|
+
"init.api_key_saved": { en: "API key saved.", zh: "API \u5BC6\u94A5\u5DF2\u4FDD\u5B58\u3002" },
|
|
47
|
+
"init.api_key_required": { en: "API key is required", zh: "API \u5BC6\u94A5\u4E3A\u5FC5\u586B\u9879" },
|
|
48
|
+
"init.model_override": { en: "Use a different LLM? (e.g., Kimi, DeepSeek)", zh: "\u4F7F\u7528\u5176\u4ED6\u5927\u6A21\u578B\uFF1F\uFF08\u5982 Kimi\u3001DeepSeek\uFF09" },
|
|
49
|
+
"init.model_override_yes": { en: "Yes, configure a custom model", zh: "\u662F\uFF0C\u914D\u7F6E\u81EA\u5B9A\u4E49\u6A21\u578B" },
|
|
50
|
+
"init.model_override_no": { en: "No, use default", zh: "\u5426\uFF0C\u4F7F\u7528\u9ED8\u8BA4" },
|
|
51
|
+
"init.model_name": { en: "Model name (e.g., moonshot-v1-128k):", zh: "\u6A21\u578B\u540D\u79F0\uFF08\u5982 moonshot-v1-128k\uFF09\uFF1A" },
|
|
52
|
+
"init.model_base_url": { en: "API base URL (e.g., https://api.moonshot.cn/v1):", zh: "API \u5730\u5740\uFF08\u5982 https://api.moonshot.cn/v1\uFF09\uFF1A" },
|
|
53
|
+
"init.model_api_key": { en: "API key for this model:", zh: "\u8BE5\u6A21\u578B\u7684 API \u5BC6\u94A5\uFF1A" },
|
|
54
|
+
"init.model_saved": { en: "Custom model configured.", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u5DF2\u914D\u7F6E\u3002" },
|
|
55
|
+
"init.model_removed": { en: "Custom model removed. Using default provider.", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u5DF2\u79FB\u9664\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u63D0\u4F9B\u8005\u3002" },
|
|
56
|
+
"init.model_keep_or_change": { en: "Custom model settings:", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u8BBE\u7F6E\uFF1A" },
|
|
57
|
+
"init.model_keep": { en: "Keep current settings", zh: "\u4FDD\u6301\u5F53\u524D\u8BBE\u7F6E" },
|
|
58
|
+
"init.model_edit": { en: "Edit model / API key / URL", zh: "\u7F16\u8F91\u6A21\u578B / API \u5BC6\u94A5 / \u5730\u5740" },
|
|
59
|
+
"init.model_remove": { en: "Remove custom model (use default)", zh: "\u79FB\u9664\u81EA\u5B9A\u4E49\u6A21\u578B\uFF08\u4F7F\u7528\u9ED8\u8BA4\uFF09" },
|
|
60
|
+
"init.model_note": { en: "Note: Custom models only work in standalone mode (trip-optimizer run --standalone).", zh: "\u6CE8\u610F\uFF1A\u81EA\u5B9A\u4E49\u6A21\u578B\u4EC5\u5728\u72EC\u7ACB\u6A21\u5F0F\u4E0B\u8FD0\u884C\uFF08trip-optimizer run --standalone\uFF09\u3002" },
|
|
61
|
+
// Profile setup
|
|
62
|
+
"profile.loyalty": { en: "Hotel loyalty program:", zh: "\u9152\u5E97\u4F1A\u5458\u8BA1\u5212\uFF1A" },
|
|
63
|
+
"profile.dietary": { en: "Dietary restrictions (Space to select, Enter to confirm):", zh: "\u996E\u98DF\u9650\u5236\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
|
|
64
|
+
// Trip questions
|
|
65
|
+
"trip.start_date": { en: "Start date (YYYY-MM-DD):", zh: "\u51FA\u53D1\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1A" },
|
|
66
|
+
"trip.end_date": { en: "End date (YYYY-MM-DD):", zh: "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1A" },
|
|
67
|
+
"trip.travelers": { en: "Number of travelers:", zh: "\u51FA\u884C\u4EBA\u6570\uFF1A" },
|
|
68
|
+
"trip.origin": { en: "Departing from (city):", zh: "\u51FA\u53D1\u57CE\u5E02\uFF1A" },
|
|
69
|
+
"trip.cities": { en: "Cities in order (comma-separated):", zh: "\u57CE\u5E02\u987A\u5E8F\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1A" },
|
|
70
|
+
"trip.cities_validate": { en: "Enter at least one city", zh: "\u8BF7\u81F3\u5C11\u8F93\u5165\u4E00\u4E2A\u57CE\u5E02" },
|
|
71
|
+
"trip.budget": { en: "Total budget (USD):", zh: "\u603B\u9884\u7B97\uFF08USD\uFF09\uFF1A" },
|
|
72
|
+
"trip.vibes": { en: "Pick your vibes (Space to select, Enter to confirm):", zh: "\u9009\u62E9\u65C5\u884C\u98CE\u683C\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
|
|
73
|
+
"trip.anti_patterns": { en: "Anything to avoid? (comma-separated, or press Enter to skip):", zh: "\u6709\u4EC0\u4E48\u8981\u907F\u514D\u7684\uFF1F\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u6216\u6309\u56DE\u8F66\u8DF3\u8FC7\uFF09\uFF1A" },
|
|
74
|
+
// Vibe choices
|
|
75
|
+
"vibe.wandering": { en: "Wandering & exploring", zh: "\u6F2B\u6B65\u63A2\u7D22" },
|
|
76
|
+
"vibe.food": { en: "Food & culinary", zh: "\u7F8E\u98DF\u4E4B\u65C5" },
|
|
77
|
+
"vibe.culture": { en: "Culture & arts", zh: "\u6587\u5316\u827A\u672F" },
|
|
78
|
+
"vibe.nature": { en: "Nature & outdoors", zh: "\u81EA\u7136\u6237\u5916" },
|
|
79
|
+
"vibe.adventure": { en: "Adventure & thrills", zh: "\u5192\u9669\u523A\u6FC0" },
|
|
80
|
+
"vibe.relaxation": { en: "Relaxation & wellness", zh: "\u4F11\u95F2\u517B\u751F" },
|
|
81
|
+
"vibe.nightlife": { en: "Nightlife & entertainment", zh: "\u591C\u751F\u6D3B\u5A31\u4E50" },
|
|
82
|
+
"vibe.history": { en: "History & heritage", zh: "\u5386\u53F2\u4EBA\u6587" },
|
|
83
|
+
"vibe.shopping": { en: "Shopping", zh: "\u8D2D\u7269" },
|
|
84
|
+
"vibe.family": { en: "Family-friendly", zh: "\u4EB2\u5B50\u6E38" },
|
|
85
|
+
"vibe.romantic": { en: "Romantic", zh: "\u6D6A\u6F2B\u4E4B\u65C5" },
|
|
86
|
+
// Edit flow
|
|
87
|
+
"edit.current_settings": { en: "Current settings:", zh: "\u5F53\u524D\u8BBE\u7F6E\uFF1A" },
|
|
88
|
+
"edit.what_to_do": { en: "What would you like to do?", zh: "\u60A8\u60F3\u505A\u4EC0\u4E48\uFF1F" },
|
|
89
|
+
"edit.regenerate": { en: "Regenerate with same settings", zh: "\u4F7F\u7528\u76F8\u540C\u8BBE\u7F6E\u91CD\u65B0\u751F\u6210" },
|
|
90
|
+
"edit.edit_fields": { en: "Edit specific fields", zh: "\u7F16\u8F91\u7279\u5B9A\u5B57\u6BB5" },
|
|
91
|
+
"edit.restart": { en: "Start over from scratch", zh: "\u4ECE\u5934\u5F00\u59CB" },
|
|
92
|
+
"edit.which_fields": { en: "Which fields to edit? (Space to select, Enter to confirm):", zh: "\u7F16\u8F91\u54EA\u4E9B\u5B57\u6BB5\uFF1F\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
|
|
93
|
+
// Field labels
|
|
94
|
+
"field.dates": { en: "Dates", zh: "\u65E5\u671F" },
|
|
95
|
+
"field.travelers": { en: "Travelers", zh: "\u4EBA\u6570" },
|
|
96
|
+
"field.origin": { en: "Origin", zh: "\u51FA\u53D1\u5730" },
|
|
97
|
+
"field.cities": { en: "Cities", zh: "\u57CE\u5E02" },
|
|
98
|
+
"field.budget": { en: "Budget", zh: "\u9884\u7B97" },
|
|
99
|
+
"field.vibes": { en: "Vibes", zh: "\u98CE\u683C" },
|
|
100
|
+
"field.anti_patterns": { en: "Anti-patterns", zh: "\u907F\u514D\u4E8B\u9879" },
|
|
101
|
+
// Progress
|
|
102
|
+
"progress.generating_rubrics": { en: "Generating scoring rubrics...", zh: "\u6B63\u5728\u751F\u6210\u8BC4\u5206\u6807\u51C6..." },
|
|
103
|
+
"progress.rubrics_done": { en: "Scoring rubrics generated", zh: "\u8BC4\u5206\u6807\u51C6\u5DF2\u751F\u6210" },
|
|
104
|
+
"progress.rubrics_fail": { en: "Failed to generate scoring rubrics", zh: "\u8BC4\u5206\u6807\u51C6\u751F\u6210\u5931\u8D25" },
|
|
105
|
+
"progress.generating_plan": { en: "Generating initial plan...", zh: "\u6B63\u5728\u751F\u6210\u521D\u59CB\u884C\u7A0B..." },
|
|
106
|
+
"progress.plan_done": { en: "Initial plan generated", zh: "\u521D\u59CB\u884C\u7A0B\u5DF2\u751F\u6210" },
|
|
107
|
+
"progress.plan_fail": { en: "Failed to generate initial plan", zh: "\u521D\u59CB\u884C\u7A0B\u751F\u6210\u5931\u8D25" },
|
|
108
|
+
"progress.creating_project": { en: "Creating trip project...", zh: "\u6B63\u5728\u521B\u5EFA\u65C5\u884C\u9879\u76EE..." },
|
|
109
|
+
"progress.project_created": { en: "Trip project created at", zh: "\u65C5\u884C\u9879\u76EE\u5DF2\u521B\u5EFA\u4E8E" },
|
|
110
|
+
// Next steps
|
|
111
|
+
"next.title": { en: "Next steps:", zh: "\u4E0B\u4E00\u6B65\uFF1A" },
|
|
112
|
+
"next.review": { en: "# Review constraints.yaml and rubrics.yaml", zh: "# \u67E5\u770B constraints.yaml \u548C rubrics.yaml" },
|
|
113
|
+
// Errors
|
|
114
|
+
"error.provider_fail": { en: "Failed to create LLM provider", zh: "LLM \u63D0\u4F9B\u8005\u521B\u5EFA\u5931\u8D25" },
|
|
115
|
+
"error.model_check": { en: "Check that your model is available", zh: "\u8BF7\u68C0\u67E5\u60A8\u7684\u6A21\u578B\u662F\u5426\u53EF\u7528" },
|
|
116
|
+
"error.auth_check": { en: 'Check your authentication: run "gcloud auth application-default login"', zh: '\u8BF7\u68C0\u67E5\u8BA4\u8BC1\uFF1A\u8FD0\u884C "gcloud auth application-default login"' }
|
|
117
|
+
};
|
|
118
|
+
var currentLanguage = "en";
|
|
119
|
+
function setLanguage(lang) {
|
|
120
|
+
currentLanguage = lang;
|
|
121
|
+
}
|
|
122
|
+
function getLanguage() {
|
|
123
|
+
return currentLanguage;
|
|
124
|
+
}
|
|
125
|
+
function t(key) {
|
|
126
|
+
const entry = messages[key];
|
|
127
|
+
return entry[currentLanguage] || entry.en;
|
|
128
|
+
}
|
|
129
|
+
function getLlmLanguageInstruction() {
|
|
130
|
+
if (currentLanguage === "zh") {
|
|
131
|
+
return "\n\nIMPORTANT: Generate ALL output in Chinese (Simplified Chinese / \u7B80\u4F53\u4E2D\u6587). All descriptions, recommendations, and commentary must be written in Chinese.";
|
|
132
|
+
}
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/generators/program.ts
|
|
137
|
+
function generateProgram(constraints, config) {
|
|
138
|
+
const hasSearchApi = !!config.search_api?.api_key;
|
|
139
|
+
const isZh = getLanguage() === "zh";
|
|
140
|
+
const researchSources = isZh ? `### \u7814\u7A76\u6765\u6E90\uFF08\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF09
|
|
141
|
+
|
|
142
|
+
1. **\u6D4F\u89C8\u5668\u7814\u7A76**\uFF08\u5982\u679C agent-browser \u6280\u80FD\u53EF\u7528\uFF09\uFF1A
|
|
143
|
+
- \u4F7F\u7528 \`agent-browser\` \u6280\u80FD\u8BBF\u95EE\u5927\u4F17\u70B9\u8BC4\u3001\u5C0F\u7EA2\u4E66\u3001\u9A6C\u8702\u7A9D\u3001\u643A\u7A0B\u3001Google Maps
|
|
144
|
+
- \u63D0\u53D6\u8BC4\u5206\u3001\u8BC4\u8BBA\u6570\u91CF\u3001\u5F53\u5B63\u83DC\u5355\u3001\u672C\u5730\u4EBA\u63A8\u8350
|
|
145
|
+
- \u8FD9\u662F\u6700\u771F\u5B9E\u7684\u5B9E\u65F6\u6570\u636E\u6765\u6E90
|
|
146
|
+
- \u4F18\u5148\u641C\u7D22\u4E2D\u6587\u5E73\u53F0\uFF08\u5C0F\u7EA2\u4E66\u3001\u5927\u4F17\u70B9\u8BC4\u3001\u9A6C\u8702\u7A9D\uFF09\uFF0C\u56E0\u4E3A\u672C\u5730\u4EBA\u7684\u63A8\u8350\u66F4\u53EF\u9760
|
|
147
|
+
|
|
148
|
+
${hasSearchApi ? `2. **\u7F51\u7EDC\u641C\u7D22 API**\uFF08\u5DF2\u914D\u7F6E\uFF09\uFF1A
|
|
149
|
+
- \u641C\u7D22 "[\u57CE\u5E02] \u672C\u5730\u4EBA\u63A8\u8350 \u9690\u85CF\u7F8E\u98DF"
|
|
150
|
+
- \u641C\u7D22 "[\u57CE\u5E02] \u907F\u96F7 \u8E29\u5751 \u6E38\u5BA2\u9677\u9631"
|
|
151
|
+
- \u641C\u7D22 "[\u57CE\u5E02] \u5C0F\u4F17\u666F\u70B9 \u5F53\u5730\u4EBA\u624D\u77E5\u9053"
|
|
152
|
+
- \u641C\u7D22 "[\u57CE\u5E02] \u5F53\u5B63\u7279\u8272 \u65F6\u4EE4\u7F8E\u98DF"
|
|
153
|
+
- \u540C\u65F6\u641C\u7D22\u4E2D\u82F1\u6587\u5173\u952E\u8BCD\u4EE5\u83B7\u53D6\u66F4\u5168\u9762\u7684\u7ED3\u679C
|
|
154
|
+
|
|
155
|
+
3. **LLM \u77E5\u8BC6**\uFF08\u5907\u9009\uFF09\uFF1A` : `2. **LLM \u77E5\u8BC6**\uFF08\u4E3B\u8981\u6765\u6E90\uFF09\uFF1A
|
|
156
|
+
`} - \u4F7F\u7528\u8BAD\u7EC3\u6570\u636E\u83B7\u53D6\u6D3B\u52A8\u548C\u9910\u5385\u63A8\u8350
|
|
157
|
+
- \u6807\u8BB0\u6765\u6E90\u4E3A "llm_knowledge" \u4EE5\u4FBF\u8BC4\u5206\u5668\u964D\u4F4E\u6743\u91CD
|
|
158
|
+
- \u5BF9\u77E5\u540D\u76EE\u7684\u5730\u6548\u679C\u597D\uFF0C\u5BF9\u51B7\u95E8\u5730\u70B9\u6548\u679C\u8F83\u5DEE` : `### Research Sources (in priority order)
|
|
159
|
+
|
|
160
|
+
1. **Browser research** (if agent-browser skill is available):
|
|
161
|
+
- Invoke the \`agent-browser\` skill to visit Google Maps, Dianping, Xiaohongshu, TripAdvisor
|
|
162
|
+
- Extract ratings, review counts, seasonal menus
|
|
163
|
+
- This gives real-time, ground-truth data
|
|
164
|
+
|
|
165
|
+
${hasSearchApi ? `2. **Web search API** (configured):
|
|
166
|
+
- Use WebSearch tool for "[city] hidden gems locals recommend"
|
|
167
|
+
- Search for "[city] overrated tourist traps to skip"
|
|
168
|
+
- Search for "[city] best street food locals recommend"
|
|
169
|
+
- Search for seasonal factors
|
|
170
|
+
|
|
171
|
+
3. **LLM knowledge** (fallback):` : `2. **LLM knowledge** (primary):
|
|
172
|
+
`} - Use training data for activity and restaurant recommendations
|
|
173
|
+
- Flag entries as source: "llm_knowledge" so scorer weights them lower
|
|
174
|
+
- Good for major destinations, weaker for obscure ones`;
|
|
175
|
+
const researchQueries = isZh ? `### \u7814\u7A76\u641C\u7D22\u5173\u952E\u8BCD
|
|
176
|
+
- "[\u57CE\u5E02] \u672C\u5730\u4EBA\u63A8\u8350 \u5C0F\u4F17\u666F\u70B9"
|
|
177
|
+
- "[\u57CE\u5E02] \u907F\u96F7\u6307\u5357 \u6E38\u5BA2\u9677\u9631"
|
|
178
|
+
- "[\u57CE\u5E02] \u5FC5\u5403\u7F8E\u98DF \u82CD\u8747\u9986\u5B50 \u672C\u5730\u4EBA\u6392\u961F"
|
|
179
|
+
- "[\u57CE\u5E02] \u503C\u5F97\u901B\u7684\u8857\u533A City Walk"
|
|
180
|
+
- "[\u57CE\u5E02] [\u5B63\u8282] \u5F53\u5B63\u7279\u8272 \u65F6\u4EE4\u63A8\u8350"
|
|
181
|
+
- "[\u57CE\u5E02] \u6DF1\u5EA6\u6E38 \u4E0D\u5728\u653B\u7565\u4E0A\u7684\u4F53\u9A8C"
|
|
182
|
+
- "[City] hidden gems locals only"\uFF08\u82F1\u6587\u8865\u5145\u641C\u7D22\uFF09
|
|
183
|
+
- "[City] authentic local food NOT tourist"\uFF08\u82F1\u6587\u8865\u5145\u641C\u7D22\uFF09` : `### Research Query Patterns
|
|
184
|
+
- "[City] hidden gems locals only"
|
|
185
|
+
- "[City] overrated tourist traps to skip"
|
|
186
|
+
- "[City] best street food locals recommend"
|
|
187
|
+
- "[City] atmospheric neighborhoods to walk"
|
|
188
|
+
- "[City] what to do in [season] seasonal"
|
|
189
|
+
- "[City] authentic experiences not on TripAdvisor"`;
|
|
190
|
+
const outputLanguage = isZh ? `
|
|
191
|
+
## \u8F93\u51FA\u8BED\u8A00
|
|
192
|
+
|
|
193
|
+
\u6240\u6709\u8F93\u51FA\uFF08plan.md\u3001activities_db.json \u4E2D\u7684\u63CF\u8FF0\u3001commit messages\uFF09\u90FD\u5FC5\u987B\u4F7F\u7528\u7B80\u4F53\u4E2D\u6587\u3002
|
|
194
|
+
` : "";
|
|
195
|
+
return `# trip-optimizer Agent Instructions
|
|
196
|
+
|
|
197
|
+
## Setup
|
|
198
|
+
1. Read \`constraints.yaml\` for fixed parameters and preferences
|
|
199
|
+
2. Read the current \`plan.md\` as baseline
|
|
200
|
+
3. Read \`activities_db.json\` (may be empty on first run)
|
|
201
|
+
4. Run scoring on the baseline plan, record as initial score
|
|
202
|
+
5. Confirm setup, then begin
|
|
203
|
+
|
|
204
|
+
## Phase 1: Research Sprint
|
|
205
|
+
|
|
206
|
+
Before mutating anything, build knowledge. For each city in the plan:
|
|
207
|
+
|
|
208
|
+
${researchSources}
|
|
209
|
+
|
|
210
|
+
${researchQueries}
|
|
211
|
+
|
|
212
|
+
After researching, add all findings to \`activities_db.json\` with scores and sources.
|
|
213
|
+
Git commit the updated database.
|
|
214
|
+
${outputLanguage}
|
|
215
|
+
## Phase 2: Optimization Loop
|
|
216
|
+
|
|
217
|
+
\`\`\`
|
|
218
|
+
LOOP FOREVER:
|
|
219
|
+
|
|
220
|
+
1. Pick a mutation type (rotate through these):
|
|
221
|
+
a. SWAP \u2014 replace an activity with a higher-scored alternative from activities_db
|
|
222
|
+
b. REALLOCATE \u2014 move a day between cities (within min/max bounds)
|
|
223
|
+
c. REORDER \u2014 rearrange a day's activities for better geographic clustering
|
|
224
|
+
d. UPGRADE \u2014 replace a restaurant with a more authentic option
|
|
225
|
+
e. SIMPLIFY \u2014 remove a low-scoring activity, leave free wandering time
|
|
226
|
+
f. RESEARCH \u2014 search for new options in the weakest-scoring city,
|
|
227
|
+
add to activities_db, then attempt a swap
|
|
228
|
+
|
|
229
|
+
2. Make ONE change to plan.md
|
|
230
|
+
3. Git commit with descriptive message
|
|
231
|
+
4. Score the new plan (comparative mode for most iterations, absolute every 10th)
|
|
232
|
+
5. If score improved \u2192 keep the commit
|
|
233
|
+
6. If score equal or worse \u2192 git reset --hard HEAD~1
|
|
234
|
+
7. Log to results.tsv (tab-separated):
|
|
235
|
+
iteration, commit, score_before, score_after, delta, status, mutation_type, description
|
|
236
|
+
8. NEVER STOP \u2014 run until interrupted
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
## Mutation Guidelines
|
|
240
|
+
|
|
241
|
+
- **One mutation per iteration.** Don't change multiple things at once.
|
|
242
|
+
- **Free time is valuable.** Empty afternoons for wandering can score higher than mediocre activities.
|
|
243
|
+
- **Respect geographic clustering.** Don't zigzag across the city.
|
|
244
|
+
- **Respect day bounds.** When reallocating days, stay within min/max in constraints.yaml.
|
|
245
|
+
- **Transit days are partial days.** A day with 5+ hours transit should not have full activities.
|
|
246
|
+
- **Research when stuck.** If 5+ consecutive discards, trigger a RESEARCH mutation.
|
|
247
|
+
|
|
248
|
+
## What You CANNOT Do
|
|
249
|
+
|
|
250
|
+
- Add or remove cities from the route
|
|
251
|
+
- Change the city ordering
|
|
252
|
+
- Modify constraints.yaml or rubrics.yaml
|
|
253
|
+
- Exceed min/max day bounds per city
|
|
254
|
+
- Schedule activities during transit time
|
|
255
|
+
${constraints.hard_requirements.map((r) => `- Violate: ${r}`).join("\n")}
|
|
256
|
+
|
|
257
|
+
## Crash/Failure Handling
|
|
258
|
+
|
|
259
|
+
- If scoring fails, revert and log as crash
|
|
260
|
+
- If 10 consecutive discards, try REALLOCATE or RESEARCH
|
|
261
|
+
- If score plateaus for 20+ iterations, shift to RESEARCH phase
|
|
262
|
+
- results.tsv survives git resets (it's gitignored)
|
|
263
|
+
|
|
264
|
+
## Context Management
|
|
265
|
+
|
|
266
|
+
- Write long outputs to files, don't flood context
|
|
267
|
+
- Write research to activities_db.json immediately
|
|
268
|
+
- Read results.tsv periodically to avoid repeating failed mutations
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
getGlobalDir,
|
|
274
|
+
getTripHistoryPath,
|
|
275
|
+
getLearnedPath,
|
|
276
|
+
loadConfig,
|
|
277
|
+
saveConfig,
|
|
278
|
+
setLanguage,
|
|
279
|
+
t,
|
|
280
|
+
getLlmLanguageInstruction,
|
|
281
|
+
generateProgram
|
|
282
|
+
};
|