octto 0.1.3 → 0.2.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 +20 -0
- package/dist/config/index.d.ts +3 -3
- package/dist/config/loader.d.ts +12 -3
- package/dist/config/schema.d.ts +2 -0
- package/dist/index.js +76 -19
- 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,32 @@ 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
|
+
|
|
121
|
+
### Environment Variables
|
|
122
|
+
|
|
123
|
+
| Variable | Description |
|
|
124
|
+
|----------|-------------|
|
|
125
|
+
| `OCTTO_PORT` | Override port (takes precedence over config file) |
|
|
126
|
+
|
|
127
|
+
For Docker workflows, set a fixed port:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
OCTTO_PORT=3000 opencode
|
|
131
|
+
```
|
|
132
|
+
|
|
113
133
|
## Development
|
|
114
134
|
|
|
115
135
|
```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, OcttoConfig } from "./loader";
|
|
2
|
+
export { loadCustomConfig, resolvePort } from "./loader";
|
|
3
|
+
export { AgentOverrideSchema, OcttoConfigSchema, PortSchema } from "./schema";
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
2
|
-
import
|
|
2
|
+
import { AGENTS } from "@/agents";
|
|
3
3
|
export type { AgentOverride, OcttoConfig } from "./schema";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve port from environment variable or config.
|
|
6
|
+
* Priority: OCTTO_PORT env var > config port > default (0 = random)
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePort(configPort?: number): number;
|
|
9
|
+
export interface CustomConfig {
|
|
10
|
+
agents: Record<AGENTS, AgentConfig>;
|
|
11
|
+
port: number;
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* Load user configuration and merge with plugin agents.
|
|
6
|
-
* Returns merged agent configs with user overrides applied.
|
|
15
|
+
* Returns merged agent configs with user overrides applied, and resolved port.
|
|
7
16
|
*/
|
|
8
|
-
export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<
|
|
17
|
+
export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<CustomConfig>;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ 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>]>;
|
|
41
42
|
export declare const OcttoConfigSchema: v.ObjectSchema<{
|
|
42
43
|
readonly agents: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, Omit<v.ObjectSchema<{
|
|
43
44
|
readonly model: v.StringSchema<undefined>;
|
|
@@ -77,6 +78,7 @@ export declare const OcttoConfigSchema: v.ObjectSchema<{
|
|
|
77
78
|
readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
|
|
78
79
|
} | undefined;
|
|
79
80
|
}, undefined>, undefined>;
|
|
81
|
+
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>;
|
|
80
82
|
}, undefined>;
|
|
81
83
|
export type AgentOverride = v.InferOutput<typeof AgentOverrideSchema>;
|
|
82
84
|
export type OcttoConfig = v.InferOutput<typeof OcttoConfigSchema>;
|
package/dist/index.js
CHANGED
|
@@ -795,36 +795,93 @@ var AgentOverrideSchema = partial(object({
|
|
|
795
795
|
temperature: pipe(number(), minValue(0), maxValue(2)),
|
|
796
796
|
maxSteps: pipe(number(), integer(), minValue(1))
|
|
797
797
|
}));
|
|
798
|
+
var PortSchema = pipe(number(), integer(), minValue(0), maxValue(65535));
|
|
798
799
|
var OcttoConfigSchema = object({
|
|
799
|
-
agents: optional(record(enum_(AGENTS), AgentOverrideSchema))
|
|
800
|
+
agents: optional(record(enum_(AGENTS), AgentOverrideSchema)),
|
|
801
|
+
port: optional(PortSchema)
|
|
800
802
|
});
|
|
801
803
|
|
|
802
804
|
// src/config/loader.ts
|
|
805
|
+
var OCTTO_PORT_ENV = "OCTTO_PORT";
|
|
806
|
+
var DEFAULT_PORT = 0;
|
|
807
|
+
function resolvePort(configPort) {
|
|
808
|
+
const envValue = process.env[OCTTO_PORT_ENV];
|
|
809
|
+
if (envValue !== undefined) {
|
|
810
|
+
const parsed = Number(envValue);
|
|
811
|
+
if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535) {
|
|
812
|
+
return parsed;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return configPort ?? DEFAULT_PORT;
|
|
816
|
+
}
|
|
817
|
+
var VALID_AGENT_NAMES = Object.values(AGENTS);
|
|
818
|
+
function formatValidationErrors(issues) {
|
|
819
|
+
return issues.map((issue) => {
|
|
820
|
+
const path = issue.path?.map((p) => p.key).join(".") ?? "root";
|
|
821
|
+
return ` - ${path}: ${issue.message}`;
|
|
822
|
+
}).join(`
|
|
823
|
+
`);
|
|
824
|
+
}
|
|
803
825
|
async function load(configDir) {
|
|
804
826
|
const baseDir = configDir ?? join(homedir(), ".config", "opencode");
|
|
805
827
|
const configPath = join(baseDir, "octto.json");
|
|
828
|
+
let parsed;
|
|
806
829
|
try {
|
|
807
830
|
const content = await readFile(configPath, "utf-8");
|
|
808
|
-
|
|
809
|
-
const result = safeParse(OcttoConfigSchema, parsed);
|
|
810
|
-
if (!result.success) {
|
|
811
|
-
return null;
|
|
812
|
-
}
|
|
813
|
-
return result.output;
|
|
831
|
+
parsed = JSON.parse(content);
|
|
814
832
|
} catch {
|
|
815
833
|
return null;
|
|
816
834
|
}
|
|
835
|
+
const result = safeParse(OcttoConfigSchema, parsed);
|
|
836
|
+
if (result.success) {
|
|
837
|
+
return result.output;
|
|
838
|
+
}
|
|
839
|
+
console.warn(`[octto] Config validation errors in ${configPath}:`);
|
|
840
|
+
console.warn(formatValidationErrors(result.issues));
|
|
841
|
+
if (typeof parsed !== "object" || parsed === null || !("agents" in parsed)) {
|
|
842
|
+
console.warn("[octto] No valid agents found in config, using defaults");
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
const rawAgents = parsed.agents;
|
|
846
|
+
if (typeof rawAgents !== "object" || rawAgents === null) {
|
|
847
|
+
console.warn("[octto] Invalid agents format, using defaults");
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
const validAgents = {};
|
|
851
|
+
let hasValidAgent = false;
|
|
852
|
+
for (const [name, override] of Object.entries(rawAgents)) {
|
|
853
|
+
if (!VALID_AGENT_NAMES.includes(name)) {
|
|
854
|
+
console.warn(`[octto] Unknown agent "${name}" - valid names: ${VALID_AGENT_NAMES.join(", ")}`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const agentResult = safeParse(AgentOverrideSchema, override);
|
|
858
|
+
if (agentResult.success) {
|
|
859
|
+
validAgents[name] = agentResult.output;
|
|
860
|
+
hasValidAgent = true;
|
|
861
|
+
} else {
|
|
862
|
+
console.warn(`[octto] Invalid config for agent "${name}":`);
|
|
863
|
+
console.warn(formatValidationErrors(agentResult.issues));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (!hasValidAgent) {
|
|
867
|
+
console.warn("[octto] No valid agent overrides found, using defaults");
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
console.warn("[octto] Partial config loaded - some overrides applied despite errors");
|
|
871
|
+
return { agents: validAgents };
|
|
817
872
|
}
|
|
818
873
|
async function loadCustomConfig(agents2, configDir) {
|
|
819
874
|
const config = await load(configDir);
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
result[name] = { ...agents2[name], ...override };
|
|
875
|
+
const mergedAgents = { ...agents2 };
|
|
876
|
+
if (config?.agents) {
|
|
877
|
+
for (const [name, override] of Object.entries(config.agents)) {
|
|
878
|
+
mergedAgents[name] = { ...agents2[name], ...override };
|
|
879
|
+
}
|
|
826
880
|
}
|
|
827
|
-
return
|
|
881
|
+
return {
|
|
882
|
+
agents: mergedAgents,
|
|
883
|
+
port: resolvePort(config?.port)
|
|
884
|
+
};
|
|
828
885
|
}
|
|
829
886
|
// src/constants.ts
|
|
830
887
|
var DEFAULT_ANSWER_TIMEOUT_MS = 300000;
|
|
@@ -2463,10 +2520,10 @@ function getHtmlBundle() {
|
|
|
2463
2520
|
</html>`;
|
|
2464
2521
|
}
|
|
2465
2522
|
// src/session/server.ts
|
|
2466
|
-
async function createServer(sessionId, store) {
|
|
2523
|
+
async function createServer(sessionId, store, configuredPort) {
|
|
2467
2524
|
const htmlBundle = getHtmlBundle();
|
|
2468
2525
|
const server = Bun.serve({
|
|
2469
|
-
port: 0,
|
|
2526
|
+
port: configuredPort ?? 0,
|
|
2470
2527
|
fetch(req, server2) {
|
|
2471
2528
|
const url = new URL(req.url);
|
|
2472
2529
|
if (url.pathname === "/ws") {
|
|
@@ -2624,7 +2681,7 @@ function createSessionStore(options = {}) {
|
|
|
2624
2681
|
const store = {
|
|
2625
2682
|
async startSession(input) {
|
|
2626
2683
|
const sessionId = generateSessionId();
|
|
2627
|
-
const { server, port } = await createServer(sessionId, store);
|
|
2684
|
+
const { server, port } = await createServer(sessionId, store, options.port);
|
|
2628
2685
|
const url = `http://localhost:${port}`;
|
|
2629
2686
|
const session = {
|
|
2630
2687
|
id: sessionId,
|
|
@@ -16484,7 +16541,7 @@ function createOcttoTools(sessions, client) {
|
|
|
16484
16541
|
// src/index.ts
|
|
16485
16542
|
var Octto = async ({ client }) => {
|
|
16486
16543
|
const customConfig = await loadCustomConfig(agents);
|
|
16487
|
-
const sessions = createSessionStore();
|
|
16544
|
+
const sessions = createSessionStore({ port: customConfig.port });
|
|
16488
16545
|
const tracked = new Map;
|
|
16489
16546
|
const tools = createOcttoTools(sessions, client);
|
|
16490
16547
|
const originalExecute = tools.start_session.execute;
|
|
@@ -16502,7 +16559,7 @@ var Octto = async ({ client }) => {
|
|
|
16502
16559
|
return {
|
|
16503
16560
|
tool: tools,
|
|
16504
16561
|
config: async (config2) => {
|
|
16505
|
-
config2.agent = { ...config2.agent, ...customConfig };
|
|
16562
|
+
config2.agent = { ...config2.agent, ...customConfig.agents };
|
|
16506
16563
|
},
|
|
16507
16564
|
event: async ({ event }) => {
|
|
16508
16565
|
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>;
|