octto 0.1.4 → 0.3.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 +48 -0
- package/dist/config/index.d.ts +3 -3
- package/dist/config/loader.d.ts +14 -3
- package/dist/config/schema.d.ts +5 -0
- package/dist/hooks/fragment-injector.d.ts +37 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.js +207 -21
- package/dist/session/server.d.ts +1 -1
- package/dist/session/sessions.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,12 +104,60 @@ Optional `~/.config/opencode/octto.json`:
|
|
|
104
104
|
|
|
105
105
|
```json
|
|
106
106
|
{
|
|
107
|
+
"port": 3000,
|
|
107
108
|
"agents": {
|
|
108
109
|
"probe": { "model": "anthropic/claude-sonnet-4" }
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
### Options
|
|
115
|
+
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
|--------|------|---------|-------------|
|
|
118
|
+
| `port` | number | `0` (random) | Fixed port for the browser UI server |
|
|
119
|
+
| `agents` | object | - | Override agent models/settings |
|
|
120
|
+
| `fragments` | object | - | Custom instructions injected into agent prompts |
|
|
121
|
+
|
|
122
|
+
### Fragments
|
|
123
|
+
|
|
124
|
+
Inject custom instructions into agent prompts. Useful for customizing agent behavior per-project or globally.
|
|
125
|
+
|
|
126
|
+
**Global config** (`~/.config/opencode/octto.json`):
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"fragments": {
|
|
131
|
+
"octto": ["Always suggest 3 implementation approaches"],
|
|
132
|
+
"probe": ["Include emoji in every question"],
|
|
133
|
+
"bootstrapper": ["Focus on technical feasibility"]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Project config** (`.octto/fragments.json` in your project root):
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"octto": ["This project uses React - focus on component patterns"],
|
|
143
|
+
"probe": ["Ask about testing strategy for each feature"]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Fragments are merged: global fragments load first, project fragments append. Each fragment becomes a bullet point in a `<user-instructions>` block prepended to the agent's system prompt.
|
|
148
|
+
|
|
149
|
+
### Environment Variables
|
|
150
|
+
|
|
151
|
+
| Variable | Description |
|
|
152
|
+
|----------|-------------|
|
|
153
|
+
| `OCTTO_PORT` | Override port (takes precedence over config file) |
|
|
154
|
+
|
|
155
|
+
For Docker workflows, set a fixed port:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
OCTTO_PORT=3000 opencode
|
|
159
|
+
```
|
|
160
|
+
|
|
113
161
|
## Development
|
|
114
162
|
|
|
115
163
|
```bash
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
3
|
-
export { AgentOverrideSchema, OcttoConfigSchema } from "./schema";
|
|
1
|
+
export type { AgentOverride, CustomConfig, Fragments, OcttoConfig } from "./loader";
|
|
2
|
+
export { loadCustomConfig, resolvePort } from "./loader";
|
|
3
|
+
export { AgentOverrideSchema, FragmentsSchema, OcttoConfigSchema, PortSchema } from "./schema";
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
2
2
|
import { AGENTS } from "@/agents";
|
|
3
|
-
|
|
3
|
+
import { type Fragments } from "./schema";
|
|
4
|
+
export type { AgentOverride, Fragments, OcttoConfig } from "./schema";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve port from environment variable or config.
|
|
7
|
+
* Priority: OCTTO_PORT env var > config port > default (0 = random)
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolvePort(configPort?: number): number;
|
|
10
|
+
export interface CustomConfig {
|
|
11
|
+
agents: Record<AGENTS, AgentConfig>;
|
|
12
|
+
port: number;
|
|
13
|
+
fragments: Fragments;
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Load user configuration and merge with plugin agents.
|
|
6
|
-
* Returns merged agent configs with user overrides applied.
|
|
17
|
+
* Returns merged agent configs with user overrides applied, and resolved port.
|
|
7
18
|
*/
|
|
8
|
-
export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<
|
|
19
|
+
export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<CustomConfig>;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare const AgentOverrideSchema: Omit<v.ObjectSchema<{
|
|
|
38
38
|
readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
|
|
39
39
|
} | undefined;
|
|
40
40
|
};
|
|
41
|
+
export declare const PortSchema: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 65535, undefined>]>;
|
|
42
|
+
export declare const FragmentsSchema: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, undefined>;
|
|
41
43
|
export declare const OcttoConfigSchema: v.ObjectSchema<{
|
|
42
44
|
readonly agents: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, Omit<v.ObjectSchema<{
|
|
43
45
|
readonly model: v.StringSchema<undefined>;
|
|
@@ -77,6 +79,9 @@ export declare const OcttoConfigSchema: v.ObjectSchema<{
|
|
|
77
79
|
readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
|
|
78
80
|
} | undefined;
|
|
79
81
|
}, undefined>, undefined>;
|
|
82
|
+
readonly port: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 65535, undefined>]>, undefined>;
|
|
83
|
+
readonly fragments: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, undefined>;
|
|
80
84
|
}, undefined>;
|
|
81
85
|
export type AgentOverride = v.InferOutput<typeof AgentOverrideSchema>;
|
|
86
|
+
export type Fragments = v.InferOutput<typeof FragmentsSchema>;
|
|
82
87
|
export type OcttoConfig = v.InferOutput<typeof OcttoConfigSchema>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type FragmentsRecord = Record<string, string[]> | undefined;
|
|
2
|
+
/**
|
|
3
|
+
* Format fragments array as an XML block to prepend to agent prompts.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatFragmentsBlock(fragments: string[] | undefined): string;
|
|
6
|
+
/**
|
|
7
|
+
* Merge global and project fragments.
|
|
8
|
+
* Global fragments come first, project fragments append.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mergeFragments(global: FragmentsRecord, project: FragmentsRecord): Record<string, string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Load project-level fragments from .octto/fragments.json
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadProjectFragments(projectDir: string): Promise<Record<string, string[]> | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Calculate Levenshtein distance between two strings.
|
|
17
|
+
* Used for suggesting similar agent names for typos.
|
|
18
|
+
*/
|
|
19
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Warn about unknown agent names in fragments config.
|
|
22
|
+
* Suggests similar valid agent names for likely typos.
|
|
23
|
+
*/
|
|
24
|
+
export declare function warnUnknownAgents(fragments: Record<string, string[]> | undefined): void;
|
|
25
|
+
export interface FragmentInjectorContext {
|
|
26
|
+
projectDir: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a fragment injector that can modify agent system prompts.
|
|
30
|
+
* Returns merged fragments from global config and project config.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createFragmentInjector(ctx: FragmentInjectorContext, globalFragments: FragmentsRecord): Promise<Record<string, string[]>>;
|
|
33
|
+
/**
|
|
34
|
+
* Get the system prompt prefix for a specific agent.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getAgentSystemPromptPrefix(fragments: Record<string, string[]>, agentName: string): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFragmentInjector, type FragmentInjectorContext, formatFragmentsBlock, getAgentSystemPromptPrefix, levenshteinDistance, loadProjectFragments, mergeFragments, warnUnknownAgents, } from "./fragment-injector";
|
package/dist/index.js
CHANGED
|
@@ -509,6 +509,58 @@ function getFallback(schema, dataset, config$1) {
|
|
|
509
509
|
function getDefault(schema, dataset, config$1) {
|
|
510
510
|
return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
|
|
511
511
|
}
|
|
512
|
+
function array(item, message$1) {
|
|
513
|
+
return {
|
|
514
|
+
kind: "schema",
|
|
515
|
+
type: "array",
|
|
516
|
+
reference: array,
|
|
517
|
+
expects: "Array",
|
|
518
|
+
async: false,
|
|
519
|
+
item,
|
|
520
|
+
message: message$1,
|
|
521
|
+
get "~standard"() {
|
|
522
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
523
|
+
},
|
|
524
|
+
"~run"(dataset, config$1) {
|
|
525
|
+
const input = dataset.value;
|
|
526
|
+
if (Array.isArray(input)) {
|
|
527
|
+
dataset.typed = true;
|
|
528
|
+
dataset.value = [];
|
|
529
|
+
for (let key = 0;key < input.length; key++) {
|
|
530
|
+
const value$1 = input[key];
|
|
531
|
+
const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
|
|
532
|
+
if (itemDataset.issues) {
|
|
533
|
+
const pathItem = {
|
|
534
|
+
type: "array",
|
|
535
|
+
origin: "value",
|
|
536
|
+
input,
|
|
537
|
+
key,
|
|
538
|
+
value: value$1
|
|
539
|
+
};
|
|
540
|
+
for (const issue of itemDataset.issues) {
|
|
541
|
+
if (issue.path)
|
|
542
|
+
issue.path.unshift(pathItem);
|
|
543
|
+
else
|
|
544
|
+
issue.path = [pathItem];
|
|
545
|
+
dataset.issues?.push(issue);
|
|
546
|
+
}
|
|
547
|
+
if (!dataset.issues)
|
|
548
|
+
dataset.issues = itemDataset.issues;
|
|
549
|
+
if (config$1.abortEarly) {
|
|
550
|
+
dataset.typed = false;
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (!itemDataset.typed)
|
|
555
|
+
dataset.typed = false;
|
|
556
|
+
dataset.value.push(itemDataset.value);
|
|
557
|
+
}
|
|
558
|
+
} else
|
|
559
|
+
_addIssue(this, "type", dataset, config$1);
|
|
560
|
+
return dataset;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
512
564
|
function enum_(enum__, message$1) {
|
|
513
565
|
const options = [];
|
|
514
566
|
for (const key in enum__)
|
|
@@ -795,11 +847,27 @@ var AgentOverrideSchema = partial(object({
|
|
|
795
847
|
temperature: pipe(number(), minValue(0), maxValue(2)),
|
|
796
848
|
maxSteps: pipe(number(), integer(), minValue(1))
|
|
797
849
|
}));
|
|
850
|
+
var PortSchema = pipe(number(), integer(), minValue(0), maxValue(65535));
|
|
851
|
+
var FragmentsSchema = optional(record(enum_(AGENTS), array(string())));
|
|
798
852
|
var OcttoConfigSchema = object({
|
|
799
|
-
agents: optional(record(enum_(AGENTS), AgentOverrideSchema))
|
|
853
|
+
agents: optional(record(enum_(AGENTS), AgentOverrideSchema)),
|
|
854
|
+
port: optional(PortSchema),
|
|
855
|
+
fragments: FragmentsSchema
|
|
800
856
|
});
|
|
801
857
|
|
|
802
858
|
// src/config/loader.ts
|
|
859
|
+
var OCTTO_PORT_ENV = "OCTTO_PORT";
|
|
860
|
+
var DEFAULT_PORT = 0;
|
|
861
|
+
function resolvePort(configPort) {
|
|
862
|
+
const envValue = process.env[OCTTO_PORT_ENV];
|
|
863
|
+
if (envValue !== undefined) {
|
|
864
|
+
const parsed = Number(envValue);
|
|
865
|
+
if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535) {
|
|
866
|
+
return parsed;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return configPort ?? DEFAULT_PORT;
|
|
870
|
+
}
|
|
803
871
|
var VALID_AGENT_NAMES = Object.values(AGENTS);
|
|
804
872
|
function formatValidationErrors(issues) {
|
|
805
873
|
return issues.map((issue) => {
|
|
@@ -858,15 +926,125 @@ async function load(configDir) {
|
|
|
858
926
|
}
|
|
859
927
|
async function loadCustomConfig(agents2, configDir) {
|
|
860
928
|
const config = await load(configDir);
|
|
861
|
-
|
|
862
|
-
|
|
929
|
+
const mergedAgents = { ...agents2 };
|
|
930
|
+
if (config?.agents) {
|
|
931
|
+
for (const [name, override] of Object.entries(config.agents)) {
|
|
932
|
+
mergedAgents[name] = { ...agents2[name], ...override };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
agents: mergedAgents,
|
|
937
|
+
port: resolvePort(config?.port),
|
|
938
|
+
fragments: config?.fragments
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
// src/hooks/fragment-injector.ts
|
|
942
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
943
|
+
import { join as join2 } from "path";
|
|
944
|
+
var VALID_AGENT_NAMES2 = Object.values(AGENTS);
|
|
945
|
+
var ProjectFragmentsSchema = record(string(), array(string()));
|
|
946
|
+
function formatFragmentsBlock(fragments) {
|
|
947
|
+
if (!fragments || fragments.length === 0) {
|
|
948
|
+
return "";
|
|
949
|
+
}
|
|
950
|
+
const bulletPoints = fragments.map((f) => `- ${f}`).join(`
|
|
951
|
+
`);
|
|
952
|
+
return `<user-instructions>
|
|
953
|
+
${bulletPoints}
|
|
954
|
+
</user-instructions>
|
|
955
|
+
|
|
956
|
+
`;
|
|
957
|
+
}
|
|
958
|
+
function mergeFragments(global, project) {
|
|
959
|
+
const result = {};
|
|
960
|
+
if (global) {
|
|
961
|
+
for (const [agent4, frags] of Object.entries(global)) {
|
|
962
|
+
result[agent4] = [...frags];
|
|
963
|
+
}
|
|
863
964
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
965
|
+
if (project) {
|
|
966
|
+
for (const [agent4, frags] of Object.entries(project)) {
|
|
967
|
+
if (result[agent4]) {
|
|
968
|
+
result[agent4].push(...frags);
|
|
969
|
+
} else {
|
|
970
|
+
result[agent4] = [...frags];
|
|
971
|
+
}
|
|
972
|
+
}
|
|
867
973
|
}
|
|
868
974
|
return result;
|
|
869
975
|
}
|
|
976
|
+
async function loadProjectFragments(projectDir) {
|
|
977
|
+
const fragmentsPath = join2(projectDir, ".octto", "fragments.json");
|
|
978
|
+
try {
|
|
979
|
+
const content = await readFile2(fragmentsPath, "utf-8");
|
|
980
|
+
const parsed = JSON.parse(content);
|
|
981
|
+
const result = safeParse(ProjectFragmentsSchema, parsed);
|
|
982
|
+
if (!result.success) {
|
|
983
|
+
console.warn(`[octto] Invalid fragments.json schema in ${fragmentsPath}`);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
return result.output;
|
|
987
|
+
} catch {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function levenshteinDistance(a, b) {
|
|
992
|
+
if (a.length === 0)
|
|
993
|
+
return b.length;
|
|
994
|
+
if (b.length === 0)
|
|
995
|
+
return a.length;
|
|
996
|
+
const matrix = [];
|
|
997
|
+
for (let i = 0;i <= b.length; i++) {
|
|
998
|
+
matrix[i] = [i];
|
|
999
|
+
}
|
|
1000
|
+
for (let j = 0;j <= a.length; j++) {
|
|
1001
|
+
matrix[0][j] = j;
|
|
1002
|
+
}
|
|
1003
|
+
for (let i = 1;i <= b.length; i++) {
|
|
1004
|
+
for (let j = 1;j <= a.length; j++) {
|
|
1005
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1006
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1007
|
+
} else {
|
|
1008
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return matrix[b.length][a.length];
|
|
1013
|
+
}
|
|
1014
|
+
function warnUnknownAgents(fragments) {
|
|
1015
|
+
if (!fragments)
|
|
1016
|
+
return;
|
|
1017
|
+
for (const agentName of Object.keys(fragments)) {
|
|
1018
|
+
if (VALID_AGENT_NAMES2.includes(agentName)) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
let closest;
|
|
1022
|
+
let minDistance = Infinity;
|
|
1023
|
+
for (const validName of VALID_AGENT_NAMES2) {
|
|
1024
|
+
const distance = levenshteinDistance(agentName, validName);
|
|
1025
|
+
if (distance < minDistance && distance <= 3) {
|
|
1026
|
+
minDistance = distance;
|
|
1027
|
+
closest = validName;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
let message = `[octto] Unknown agent "${agentName}" in fragments config.`;
|
|
1031
|
+
if (closest) {
|
|
1032
|
+
message += ` Did you mean "${closest}"?`;
|
|
1033
|
+
}
|
|
1034
|
+
message += ` Valid agents: ${VALID_AGENT_NAMES2.join(", ")}`;
|
|
1035
|
+
console.warn(message);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async function createFragmentInjector(ctx, globalFragments) {
|
|
1039
|
+
const projectFragments = await loadProjectFragments(ctx.projectDir);
|
|
1040
|
+
const merged = mergeFragments(globalFragments, projectFragments);
|
|
1041
|
+
warnUnknownAgents(globalFragments);
|
|
1042
|
+
warnUnknownAgents(projectFragments);
|
|
1043
|
+
return merged;
|
|
1044
|
+
}
|
|
1045
|
+
function getAgentSystemPromptPrefix(fragments, agentName) {
|
|
1046
|
+
return formatFragmentsBlock(fragments[agentName]);
|
|
1047
|
+
}
|
|
870
1048
|
// src/constants.ts
|
|
871
1049
|
var DEFAULT_ANSWER_TIMEOUT_MS = 300000;
|
|
872
1050
|
|
|
@@ -2504,10 +2682,10 @@ function getHtmlBundle() {
|
|
|
2504
2682
|
</html>`;
|
|
2505
2683
|
}
|
|
2506
2684
|
// src/session/server.ts
|
|
2507
|
-
async function createServer(sessionId, store) {
|
|
2685
|
+
async function createServer(sessionId, store, configuredPort) {
|
|
2508
2686
|
const htmlBundle = getHtmlBundle();
|
|
2509
2687
|
const server = Bun.serve({
|
|
2510
|
-
port: 0,
|
|
2688
|
+
port: configuredPort ?? 0,
|
|
2511
2689
|
fetch(req, server2) {
|
|
2512
2690
|
const url = new URL(req.url);
|
|
2513
2691
|
if (url.pathname === "/ws") {
|
|
@@ -2665,7 +2843,7 @@ function createSessionStore(options = {}) {
|
|
|
2665
2843
|
const store = {
|
|
2666
2844
|
async startSession(input) {
|
|
2667
2845
|
const sessionId = generateSessionId();
|
|
2668
|
-
const { server, port } = await createServer(sessionId, store);
|
|
2846
|
+
const { server, port } = await createServer(sessionId, store, options.port);
|
|
2669
2847
|
const url = `http://localhost:${port}`;
|
|
2670
2848
|
const session = {
|
|
2671
2849
|
id: sessionId,
|
|
@@ -3085,7 +3263,7 @@ __export(exports_external, {
|
|
|
3085
3263
|
bigint: () => bigint2,
|
|
3086
3264
|
base64url: () => base64url2,
|
|
3087
3265
|
base64: () => base642,
|
|
3088
|
-
array: () =>
|
|
3266
|
+
array: () => array2,
|
|
3089
3267
|
any: () => any,
|
|
3090
3268
|
_function: () => _function,
|
|
3091
3269
|
_default: () => _default2,
|
|
@@ -3581,8 +3759,8 @@ function getEnumValues(entries) {
|
|
|
3581
3759
|
const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
|
|
3582
3760
|
return values;
|
|
3583
3761
|
}
|
|
3584
|
-
function joinValues(
|
|
3585
|
-
return
|
|
3762
|
+
function joinValues(array2, separator = "|") {
|
|
3763
|
+
return array2.map((val) => stringifyPrimitive(val)).join(separator);
|
|
3586
3764
|
}
|
|
3587
3765
|
function jsonStringifyReplacer(_, value) {
|
|
3588
3766
|
if (typeof value === "bigint")
|
|
@@ -14270,7 +14448,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
14270
14448
|
inst.nullable = () => nullable(inst);
|
|
14271
14449
|
inst.nullish = () => optional2(nullable(inst));
|
|
14272
14450
|
inst.nonoptional = (params) => nonoptional(inst, params);
|
|
14273
|
-
inst.array = () =>
|
|
14451
|
+
inst.array = () => array2(inst);
|
|
14274
14452
|
inst.or = (arg) => union([inst, arg]);
|
|
14275
14453
|
inst.and = (arg) => intersection(inst, arg);
|
|
14276
14454
|
inst.transform = (tx) => pipe2(inst, transform(tx));
|
|
@@ -14691,7 +14869,7 @@ var ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
|
|
|
14691
14869
|
inst.length = (len, params) => inst.check(_length(len, params));
|
|
14692
14870
|
inst.unwrap = () => inst.element;
|
|
14693
14871
|
});
|
|
14694
|
-
function
|
|
14872
|
+
function array2(element, params) {
|
|
14695
14873
|
return _array(ZodArray, element, params);
|
|
14696
14874
|
}
|
|
14697
14875
|
function keyof(schema) {
|
|
@@ -15153,7 +15331,7 @@ var ZodFunction = /* @__PURE__ */ $constructor("ZodFunction", (inst, def) => {
|
|
|
15153
15331
|
function _function(params) {
|
|
15154
15332
|
return new ZodFunction({
|
|
15155
15333
|
type: "function",
|
|
15156
|
-
input: Array.isArray(params?.input) ? tuple(params?.input) : params?.input ??
|
|
15334
|
+
input: Array.isArray(params?.input) ? tuple(params?.input) : params?.input ?? array2(unknown()),
|
|
15157
15335
|
output: params?.output ?? unknown()
|
|
15158
15336
|
});
|
|
15159
15337
|
}
|
|
@@ -15197,7 +15375,7 @@ var stringbool = (...args) => _stringbool({
|
|
|
15197
15375
|
}, ...args);
|
|
15198
15376
|
function json(params) {
|
|
15199
15377
|
const jsonSchema = lazy(() => {
|
|
15200
|
-
return union([string3(params), number3(), boolean2(), _null3(),
|
|
15378
|
+
return union([string3(params), number3(), boolean2(), _null3(), array2(jsonSchema), record2(string3(), jsonSchema)]);
|
|
15201
15379
|
});
|
|
15202
15380
|
return jsonSchema;
|
|
15203
15381
|
}
|
|
@@ -15263,7 +15441,7 @@ tool.schema = exports_external;
|
|
|
15263
15441
|
|
|
15264
15442
|
// src/state/persistence.ts
|
|
15265
15443
|
import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
|
|
15266
|
-
import { join as
|
|
15444
|
+
import { join as join3 } from "path";
|
|
15267
15445
|
function validateSessionId(sessionId) {
|
|
15268
15446
|
if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) {
|
|
15269
15447
|
throw new Error(`Invalid session ID: ${sessionId}`);
|
|
@@ -15272,7 +15450,7 @@ function validateSessionId(sessionId) {
|
|
|
15272
15450
|
function createStatePersistence(baseDir = ".brainstorm") {
|
|
15273
15451
|
function getFilePath(sessionId) {
|
|
15274
15452
|
validateSessionId(sessionId);
|
|
15275
|
-
return
|
|
15453
|
+
return join3(baseDir, `${sessionId}.json`);
|
|
15276
15454
|
}
|
|
15277
15455
|
function ensureDir() {
|
|
15278
15456
|
if (!existsSync(baseDir)) {
|
|
@@ -16523,9 +16701,17 @@ function createOcttoTools(sessions, client) {
|
|
|
16523
16701
|
}
|
|
16524
16702
|
|
|
16525
16703
|
// src/index.ts
|
|
16526
|
-
var Octto = async ({ client }) => {
|
|
16704
|
+
var Octto = async ({ client, directory }) => {
|
|
16527
16705
|
const customConfig = await loadCustomConfig(agents);
|
|
16528
|
-
const
|
|
16706
|
+
const fragments = await createFragmentInjector({ projectDir: directory }, customConfig.fragments);
|
|
16707
|
+
for (const agentName of Object.values(AGENTS)) {
|
|
16708
|
+
const prefix = getAgentSystemPromptPrefix(fragments, agentName);
|
|
16709
|
+
if (prefix && customConfig.agents[agentName]?.prompt) {
|
|
16710
|
+
customConfig.agents[agentName].prompt = prefix + customConfig.agents[agentName].prompt;
|
|
16711
|
+
}
|
|
16712
|
+
}
|
|
16713
|
+
warnUnknownAgents(customConfig.fragments);
|
|
16714
|
+
const sessions = createSessionStore({ port: customConfig.port });
|
|
16529
16715
|
const tracked = new Map;
|
|
16530
16716
|
const tools = createOcttoTools(sessions, client);
|
|
16531
16717
|
const originalExecute = tools.start_session.execute;
|
|
@@ -16543,7 +16729,7 @@ var Octto = async ({ client }) => {
|
|
|
16543
16729
|
return {
|
|
16544
16730
|
tool: tools,
|
|
16545
16731
|
config: async (config2) => {
|
|
16546
|
-
config2.agent = { ...config2.agent, ...customConfig };
|
|
16732
|
+
config2.agent = { ...config2.agent, ...customConfig.agents };
|
|
16547
16733
|
},
|
|
16548
16734
|
event: async ({ event }) => {
|
|
16549
16735
|
if (event.type !== "session.deleted")
|
package/dist/session/server.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { SessionStore } from "./sessions";
|
|
|
3
3
|
interface WsData {
|
|
4
4
|
sessionId: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function createServer(sessionId: string, store: SessionStore): Promise<{
|
|
6
|
+
export declare function createServer(sessionId: string, store: SessionStore, configuredPort?: number): Promise<{
|
|
7
7
|
server: Server<WsData>;
|
|
8
8
|
port: number;
|
|
9
9
|
}>;
|
|
@@ -3,6 +3,8 @@ import { type BaseConfig, type EndSessionOutput, type GetAnswerInput, type GetAn
|
|
|
3
3
|
export interface SessionStoreOptions {
|
|
4
4
|
/** Skip opening browser - useful for tests */
|
|
5
5
|
skipBrowser?: boolean;
|
|
6
|
+
/** Fixed port for the server (0 = random available port) */
|
|
7
|
+
port?: number;
|
|
6
8
|
}
|
|
7
9
|
export interface SessionStore {
|
|
8
10
|
startSession: (input: StartSessionInput) => Promise<StartSessionOutput>;
|