Compare commits

..

8 Commits

Author SHA1 Message Date
exe.dev user 1c5ce7c747 chore(tui): reference core type declarations 2026-08-06 22:12:44 +00:00
exe.dev user e288b2e6d0 chore(core): combine build mode checks 2026-08-06 21:30:58 +00:00
exe.dev user bb3e9fb1a1 chore(core): separate source and test configs 2026-08-06 21:13:17 +00:00
exe.dev user bc89008514 chore: limit incremental typechecking to core 2026-08-06 20:46:32 +00:00
exe.dev user 6bcec1b159 chore(core): enable composite source checks 2026-08-06 20:38:26 +00:00
exe.dev user b3ec867da3 perf(core): isolate instruction typecheck 2026-08-06 19:26:43 +00:00
exe.dev user 6efb07e805 perf: enable incremental typechecking 2026-08-06 18:22:58 +00:00
exe.dev user 4980355300 perf(core): reduce query type variance work 2026-08-06 17:59:33 +00:00
68 changed files with 380 additions and 308 deletions
+2 -16
View File
@@ -1937,23 +1937,9 @@ export type IntegrationInfo = {
connections: Array<ConnectionInfo>
}
export type FormInfo = {
id: string
sessionID: string
title: string
coalesce?: string
metadata?: FormMetadata
fields: FormFields
}
export type FormInfo = { id: string; sessionID: string; title: string; metadata?: FormMetadata; fields: FormFields }
export type FormInfo1 = {
id: string
sessionID: string
title: string
coalesce?: string
metadata?: FormMetadata1
fields: FormFields1
}
export type FormInfo1 = { id: string; sessionID: string; title: string; metadata?: FormMetadata1; fields: FormFields1 }
export type SessionInputAdmitted = {
id: string
+1 -1
View File
@@ -11,7 +11,7 @@
"fix-node-pty": "bun run script/fix-node-pty.ts",
"benchmark:location": "bun run script/benchmark-location.ts",
"test": "bun test --only-failures",
"typecheck": "tsgo --noEmit"
"typecheck": "tsgo -b tsconfig.json tsconfig.tests.json"
},
"bin": {
"opencode": "./bin/opencode"
+10 -6
View File
@@ -132,14 +132,16 @@ function renderMigration(name: string, sql: string) {
return `import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: ${JSON.stringify(name)},
up(tx) {
return Effect.gen(function* () {
${renderStatements(sql)}
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
`
}
@@ -147,13 +149,15 @@ function renderSchema(sql: string) {
return `import { Effect } from "effect"
import type { DatabaseMigration } from "./migration"
export default {
const schema: Omit<DatabaseMigration.Migration, "id"> = {
up(tx) {
return Effect.gen(function* () {
${renderStatements(sql)}
})
},
} satisfies Omit<DatabaseMigration.Migration, "id">
}
export default schema
`
}
@@ -191,10 +195,10 @@ async function formatTypescript(input: string) {
function renderRegistry(names: string[]) {
return `import type { DatabaseMigration } from "./migration"
export const migrations = (
export const migrations: DatabaseMigration.Migration[] = (
await Promise.all([
${names.map((name) => ` import("./migration/${name}"),`).join("\n")}
])
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
).map((module) => module.default)
`
}
+2 -2
View File
@@ -1,6 +1,6 @@
import type { DatabaseMigration } from "./migration"
export const migrations = (
export const migrations: DatabaseMigration.Migration[] = (
await Promise.all([
import("./migration/20260127222353_familiar_lady_ursula"),
import("./migration/20260211171708_add_project_commands"),
@@ -43,4 +43,4 @@ export const migrations = (
import("./migration/20260804233008_loose_psylocke"),
import("./migration/20260805200742_import_legacy_credentials"),
])
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
).map((module) => module.default)
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260127222353_familiar_lady_ursula",
up(tx) {
return Effect.gen(function* () {
@@ -104,4 +104,6 @@ export default {
yield* tx.run(`CREATE INDEX \`todo_session_idx\` ON \`todo\` (\`session_id\`);`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260211171708_add_project_commands",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`project\` ADD \`commands\` text;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260213144116_wakeful_the_professor",
up(tx) {
return Effect.gen(function* () {
@@ -20,4 +20,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260225215848_workspace",
up(tx) {
return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260227213759_add_session_workspace_id",
up(tx) {
return Effect.gen(function* () {
@@ -9,4 +9,6 @@ export default {
yield* tx.run(`CREATE INDEX \`session_workspace_idx\` ON \`session\` (\`workspace_id\`);`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260228203230_blue_harpoon",
up(tx) {
return Effect.gen(function* () {
@@ -27,4 +27,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260303231226_add_workspace_fields",
up(tx) {
return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`ALTER TABLE \`workspace\` DROP COLUMN \`config\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260309230000_move_org_to_state",
up(tx) {
return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`ALTER TABLE \`account\` DROP COLUMN \`selected_org_id\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260312043431_session_message_cursor",
up(tx) {
return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260323234822_events",
up(tx) {
return Effect.gen(function* () {
@@ -23,4 +23,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260410174513_workspace-name",
up(tx) {
return Effect.gen(function* () {
@@ -26,4 +26,6 @@ export default {
yield* tx.run(`PRAGMA foreign_keys=ON;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260413175956_chief_energizer",
up(tx) {
return Effect.gen(function* () {
@@ -21,4 +21,6 @@ export default {
yield* tx.run(`CREATE INDEX \`session_entry_time_created_idx\` ON \`session_entry\` (\`time_created\`);`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260423070820_add_icon_url_override",
up(tx) {
return Effect.gen(function* () {
@@ -11,4 +11,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260427172553_slow_nightmare",
up(tx) {
return Effect.gen(function* () {
@@ -27,4 +27,6 @@ export default {
yield* tx.run(`DROP TABLE \`session_entry\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260428004200_add_session_path",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`session\` ADD \`path\` text;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260501142318_next_venus",
up(tx) {
return Effect.gen(function* () {
@@ -9,4 +9,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session\` ADD \`model\` text;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260504145000_add_sync_owner",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`event_sequence\` ADD \`owner_id\` text;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260507164347_add_workspace_time",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`workspace\` ADD \`time_used\` integer NOT NULL DEFAULT 0;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260510033149_session_usage",
up(tx) {
return Effect.gen(function* () {
@@ -53,4 +53,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260511000411_data_migration_state",
up(tx) {
return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260511173437_session-metadata",
up(tx) {
return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session\` ADD \`metadata\` text;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260601010001_normalize_storage_paths",
up(tx) {
return Effect.gen(function* () {
@@ -19,4 +19,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260601202201_amazing_prowler",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`DROP TABLE \`permission\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260602002951_lowly_union_jack",
up(tx) {
return Effect.gen(function* () {
@@ -21,4 +21,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260602182828_add_project_directories",
up(tx) {
return Effect.gen(function* () {
@@ -17,4 +17,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260603001617_session_message_projection_indexes",
up(tx) {
return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260603040000_session_message_projection_order",
up(tx) {
return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260603141458_session_input_inbox",
up(tx) {
return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260603160727_jittery_ezekiel_stane",
up(tx) {
return Effect.gen(function* () {
@@ -17,4 +17,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260604172448_event_sourced_session_input",
up(tx) {
return Effect.gen(function* () {
@@ -44,4 +44,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260605003541_add_session_context_snapshot",
up(tx) {
return Effect.gen(function* () {
@@ -18,4 +18,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260605042240_add_context_epoch_agent",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`session_context_epoch\` ADD \`agent\` text DEFAULT 'build' NOT NULL;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260611035744_credential",
up(tx) {
return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260611192811_lush_chimera",
up(tx) {
return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260612174303_project_dir_strategy",
up(tx) {
return Effect.gen(function* () {
@@ -26,4 +26,6 @@ export default {
yield* tx.run(`PRAGMA foreign_keys=ON;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260622142730_simplify_session_context_epoch",
up(tx) {
return Effect.gen(function* () {
@@ -10,4 +10,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session_context_epoch\` DROP COLUMN \`revision\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260622170816_reset_v2_session_state",
up(tx) {
return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`DELETE FROM \`event_sequence\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260622202450_simplify_session_input",
up(tx) {
return Effect.gen(function* () {
@@ -14,4 +14,6 @@ export default {
yield* tx.run(`DELETE FROM \`workspace\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260804233008_loose_psylocke",
up(tx) {
return Effect.gen(function* () {
@@ -135,4 +135,6 @@ export default {
yield* tx.run(`DROP TABLE \`session_input\`;`)
})
},
} satisfies DatabaseMigration.Migration
}
export default migration
@@ -30,12 +30,14 @@ const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
const decodeValue = Schema.decodeUnknownOption(LegacyValue)
const wellKnownSourcesKey = "wellknown:sources"
export default {
const migration: DatabaseMigration.Migration = {
id: "20260805200742_import_legacy_credentials",
up(tx) {
return importLegacyCredentials(tx, path.join(Global.Path.data, "auth.json"))
},
} satisfies DatabaseMigration.Migration
}
export default migration
export function importLegacyCredentials(tx: Parameters<DatabaseMigration.Migration["up"]>[0], filepath: string) {
return Effect.gen(function* () {
+4 -2
View File
@@ -1,7 +1,7 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "./migration"
export default {
const schema: Omit<DatabaseMigration.Migration, "id"> = {
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`
@@ -248,4 +248,6 @@ export default {
)
})
},
} satisfies Omit<DatabaseMigration.Migration, "id">
}
export default schema
-1
View File
@@ -132,7 +132,6 @@ export const layer = Layer.effect(
id,
sessionID: input.sessionID,
title: input.title,
...(input.coalesce === undefined ? {} : { coalesce: input.coalesce }),
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
fields: input.fields,
}
+6 -6
View File
@@ -82,10 +82,10 @@ export class DuplicateKeyError extends Schema.TaggedErrorClass<DuplicateKeyError
}
}
export const empty: Instructions = []
export const empty: ReadonlyArray<Source> = []
/** Closes a typed definition into one `Source`, so differently typed sources compose. */
export function make<A>(source: Source.Definition<A>): Instructions {
export function make<A>(source: Source.Definition<A>): ReadonlyArray<Source> {
const decode = Schema.decodeUnknownOption(source.codec)
const encode = Schema.encodeSync(source.codec)
const initial = (value: A) => requireText(source.key, "initial", source.render.initial(value))
@@ -121,7 +121,7 @@ export function make<A>(source: Source.Definition<A>): Instructions {
]
}
export function combine(values: ReadonlyArray<Instructions>): Instructions {
export function combine(values: ReadonlyArray<ReadonlyArray<Source>>): ReadonlyArray<Source> {
const sources = values.flat()
const keys = new Set<Key>()
for (const source of sources) {
@@ -131,7 +131,7 @@ export function combine(values: ReadonlyArray<Instructions>): Instructions {
return sources
}
export function read(value: Instructions): Effect.Effect<ReadResult> {
export function read(value: ReadonlyArray<Source>): Effect.Effect<ReadResult> {
return Effect.forEach(
value,
(source) => source.read.pipe(Effect.map((observed) => ({ key: source.key, value: observed }))),
@@ -158,7 +158,7 @@ export function diff(observed: ReadResult, previous?: Values): Effect.Effect<Adm
return Effect.succeed({ delta, blobs })
}
export function renderInitial(value: Instructions, values: Readonly<Record<string, Schema.Json>>) {
export function renderInitial(value: ReadonlyArray<Source>, values: Readonly<Record<string, Schema.Json>>) {
return render(
value.flatMap((source) => {
if (!Object.hasOwn(values, source.key)) return []
@@ -169,7 +169,7 @@ export function renderInitial(value: Instructions, values: Readonly<Record<strin
}
export function renderUpdate(
value: Instructions,
value: ReadonlyArray<Source>,
previous: Readonly<Record<string, Schema.Json>>,
delta: Readonly<Record<string, Option.Option<Schema.Json>>>,
) {
@@ -59,7 +59,6 @@ export const Plugin = {
const response = yield* forms.ask({
sessionID: context.sessionID,
title: "Web Search",
coalesce: `${context.messageID}:websearch-consent`,
metadata: { kind: "websearch.provider" },
fields: [
{
@@ -92,7 +91,6 @@ export const Plugin = {
? yield* forms.ask({
sessionID: context.sessionID,
title: "Choose a web search provider",
coalesce: `${context.messageID}:websearch-provider`,
metadata: { kind: "websearch.provider" },
fields: [
{
+48 -44
View File
@@ -13,54 +13,13 @@ export abstract class NamedError extends Error {
static create<Name extends string, Fields extends Schema.Struct.Fields>(
name: Name,
fields: Fields,
): ReturnType<typeof NamedError.createSchemaClass<Name, Schema.Struct<Fields>>>
): ReturnType<typeof createSchemaClass<Name, Schema.Struct<Fields>>>
static create<Name extends string, DataSchema extends Schema.Top>(
name: Name,
data: DataSchema,
): ReturnType<typeof NamedError.createSchemaClass<Name, DataSchema>>
): ReturnType<typeof createSchemaClass<Name, DataSchema>>
static create<Name extends string>(name: Name, data: Schema.Top | Schema.Struct.Fields) {
return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data))
}
private static createSchemaClass<Name extends string, DataSchema extends Schema.Top>(name: Name, data: DataSchema) {
const schema = Schema.Struct({
name: Schema.Literal(name),
data,
}).annotate({ identifier: name })
type Data = Schema.Schema.Type<DataSchema>
const result = class extends NamedError {
public static readonly Schema = schema
public static readonly EffectSchema = schema
public static readonly tag = name
public override readonly name = name
constructor(
public readonly data: Data,
options?: ErrorOptions,
) {
super(name, options)
this.name = name
}
static isInstance(input: unknown): input is InstanceType<typeof result> {
return NamedError.hasName(input, name)
}
schema() {
return schema
}
toObject() {
return {
name: name,
data: this.data,
}
}
}
Object.defineProperty(result, "name", { value: name })
return result
return createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data), this)
}
public static readonly Unknown = NamedError.create("UnknownError", {
@@ -68,3 +27,48 @@ export abstract class NamedError extends Error {
ref: Schema.optional(Schema.String),
})
}
function createSchemaClass<Name extends string, DataSchema extends Schema.Top>(
name: Name,
data: DataSchema,
base: typeof NamedError = NamedError,
) {
const schema = Schema.Struct({
name: Schema.Literal(name),
data,
}).annotate({ identifier: name })
type Data = Schema.Schema.Type<DataSchema>
const result = class extends base {
public static readonly Schema = schema
public static readonly EffectSchema = schema
public static readonly tag = name
public override readonly name = name
constructor(
public readonly data: Data,
options?: ErrorOptions,
) {
super(name, options)
this.name = name
}
static isInstance(input: unknown): input is InstanceType<typeof result> {
return base.hasName(input, name)
}
schema() {
return schema
}
toObject() {
return {
name: name,
data: this.data,
}
}
}
Object.defineProperty(result, "name", { value: name })
return result
}
-2
View File
@@ -15,7 +15,6 @@ const input = {
id: formID,
sessionID: SessionSchema.ID.make("ses_test"),
title: "Test form",
coalesce: "test-form",
fields: [{ key: "name", type: "string", required: true }],
} satisfies Form.CreateInput
@@ -33,7 +32,6 @@ describe("Form", () => {
yield* Effect.addFinalizer(() => unsubscribe)
const fiber = yield* service.ask(input).pipe(Effect.forkScoped)
const form = yield* Deferred.await(created)
expect(form.coalesce).toBe("test-form")
yield* service.cancel(form.id)
@@ -241,7 +241,6 @@ describe("WebSearchTool registration", () => {
{
sessionID,
title: "Web Search",
coalesce: "msg_tool_test:websearch-consent",
metadata: { kind: "websearch.provider" },
fields: [
{
@@ -299,7 +298,6 @@ describe("WebSearchTool registration", () => {
expect(formRequests[1]).toEqual({
sessionID,
title: "Choose a web search provider",
coalesce: "msg_tool_test:websearch-provider",
metadata: { kind: "websearch.provider" },
fields: [
{
+11 -2
View File
@@ -2,6 +2,15 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"noUncheckedIndexedAccess": false
}
"composite": true,
"declaration": true,
"emitDeclarationOnly": true,
"incremental": true,
"noEmit": false,
"noUncheckedIndexedAccess": false,
"outDir": "node_modules/.ts-dist/source",
"rootDir": "src",
"tsBuildInfoFile": "node_modules/.ts-dist/source.tsbuildinfo"
},
"include": ["src"]
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"incremental": true,
"noUncheckedIndexedAccess": false,
"tsBuildInfoFile": "node_modules/.ts-dist/tests.tsbuildinfo"
},
"include": ["drizzle.config.ts", "script", "test"],
"references": [{ "path": "./tsconfig.json" }]
}
@@ -110,12 +110,12 @@ export type SQLiteEffectDelete<
export type AnySQLiteEffectDelete = SQLiteEffectDeleteBase<any, any, any, any, any, any>
export interface SQLiteEffectDeleteBase<
TTable extends SQLiteTable,
TRunResult,
TReturning extends Record<string, unknown> | undefined = undefined,
TDynamic extends boolean = false,
out TTable extends SQLiteTable,
out TRunResult,
out TReturning extends Record<string, unknown> | undefined = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
SQLWrapper,
Effect.Effect<
@@ -137,12 +137,12 @@ export interface SQLiteEffectDeleteBase<
}
export class SQLiteEffectDeleteBase<
TTable extends SQLiteTable,
TRunResult,
TReturning extends Record<string, unknown> | undefined = undefined,
TDynamic extends boolean = false,
out TTable extends SQLiteTable,
out TRunResult,
out TReturning extends Record<string, unknown> | undefined = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
>
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{
@@ -126,9 +126,9 @@ export type SQLiteEffectInsert<
export type AnySQLiteEffectInsert = SQLiteEffectInsertBase<any, any, any, any, any, any>
export class SQLiteEffectInsertBuilder<
TTable extends SQLiteTable,
TRunResult,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
in out TTable extends SQLiteTable,
out TRunResult,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> {
static readonly [entityKind]: string = "SQLiteEffectInsertBuilder"
@@ -194,12 +194,12 @@ export class SQLiteEffectInsertBuilder<
}
export interface SQLiteEffectInsertBase<
TTable extends SQLiteTable,
TRunResult,
TReturning = undefined,
TDynamic extends boolean = false,
in out TTable extends SQLiteTable,
out TRunResult,
out TReturning = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLWrapper,
RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
Effect.Effect<
@@ -221,12 +221,12 @@ export interface SQLiteEffectInsertBase<
}
export class SQLiteEffectInsertBase<
TTable extends SQLiteTable,
TRunResult,
TReturning = undefined,
TDynamic extends boolean = false,
in out TTable extends SQLiteTable,
out TRunResult,
out TReturning = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
>
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{
@@ -19,9 +19,9 @@ import type { SQLiteTable } from "drizzle-orm/sqlite-core/table"
import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session"
export class SQLiteEffectRelationalQueryBuilder<
TSchema extends TablesRelationalConfig,
out TSchema extends TablesRelationalConfig,
TFields extends TableRelationalConfig,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> {
static readonly [entityKind]: string = "SQLiteEffectRelationalQueryBuilderV2"
@@ -44,11 +44,12 @@ export type SQLiteEffectSelectPrepare<
TEffectHKT
>
// Explicit variance prevents comparisons from recursively scanning Drizzle's conditional select types.
export class SQLiteEffectSelectBuilder<
TSelection extends SelectedFields | undefined,
TRunResult,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
TBuilderMode extends "db" | "qb" = "db",
out TSelection extends SelectedFields | undefined,
out TRunResult,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TBuilderMode extends "db" | "qb" = "db",
> {
static readonly [entityKind]: string = "SQLiteEffectSelectBuilder"
@@ -151,18 +152,18 @@ export interface SQLiteEffectSelectHKT<TEffectHKT extends QueryEffectHKTBase = Q
}
export interface SQLiteEffectSelectBase<
TTableName extends string | undefined,
TRunResult,
TSelection extends ColumnsSelection,
TSelectMode extends SelectMode = "single",
TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
out TTableName extends string | undefined,
out TRunResult,
out TSelection extends ColumnsSelection,
out TSelectMode extends SelectMode = "single",
out TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
? Record<TTableName, "not-null">
: {},
TDynamic extends boolean = false,
out TDynamic extends boolean = false,
TExcludedMethods extends string = never,
TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
out TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLiteSelectQueryBuilderBase<
SQLiteEffectSelectHKT<TEffectHKT>,
TTableName,
@@ -179,18 +180,18 @@ export interface SQLiteEffectSelectBase<
Effect.Effect<TResult, TEffectHKT["error"], TEffectHKT["context"]> {}
export class SQLiteEffectSelectBase<
TTableName extends string | undefined,
TRunResult,
TSelection extends ColumnsSelection,
TSelectMode extends SelectMode = "single",
TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
out TTableName extends string | undefined,
out TRunResult,
out TSelection extends ColumnsSelection,
out TSelectMode extends SelectMode = "single",
out TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
? Record<TTableName, "not-null">
: {},
TDynamic extends boolean = false,
out TDynamic extends boolean = false,
TExcludedMethods extends string = never,
TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
out TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
>
extends SQLiteSelectQueryBuilderBase<
SQLiteEffectSelectHKT<TEffectHKT>,
@@ -303,10 +303,11 @@ export class SQLiteEffectPreparedQuery<
}
}
// Explicit variance prevents comparisons from recursively scanning the full Drizzle query-builder graph.
export abstract class SQLiteEffectSession<
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
TRunResult = unknown,
TRelations extends AnyRelations = EmptyRelations,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TRunResult = unknown,
out TRelations extends AnyRelations = EmptyRelations,
> {
static readonly [entityKind]: string = "SQLiteEffectSession"
@@ -404,9 +405,9 @@ export abstract class SQLiteEffectSession<
}
export abstract class SQLiteEffectTransaction<
TEffectHKT extends QueryEffectHKTBase,
TRunResult,
TRelations extends AnyRelations = EmptyRelations,
out TEffectHKT extends QueryEffectHKTBase,
out TRunResult,
out TRelations extends AnyRelations = EmptyRelations,
> extends SQLiteEffectDatabase<TEffectHKT, TRunResult, TRelations> {
static override readonly [entityKind]: string = "SQLiteEffectTransaction"
@@ -158,9 +158,9 @@ export type SQLiteEffectUpdateJoinFn<T extends AnySQLiteEffectUpdate> = <
) => T
export class SQLiteEffectUpdateBuilder<
TTable extends SQLiteTable,
TRunResult,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
in out TTable extends SQLiteTable,
out TRunResult,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> {
static readonly [entityKind]: string = "SQLiteEffectUpdateBuilder"
@@ -193,13 +193,13 @@ export class SQLiteEffectUpdateBuilder<
}
export interface SQLiteEffectUpdateBase<
TTable extends SQLiteTable = SQLiteTable,
TRunResult = unknown,
TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
TReturning = undefined,
TDynamic extends boolean = false,
out TTable extends SQLiteTable = SQLiteTable,
out TRunResult = unknown,
out TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
out TReturning = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLWrapper,
RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
Effect.Effect<
@@ -222,13 +222,13 @@ export interface SQLiteEffectUpdateBase<
}
export class SQLiteEffectUpdateBase<
TTable extends SQLiteTable = SQLiteTable,
TRunResult = unknown,
TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
TReturning = undefined,
TDynamic extends boolean = false,
out TTable extends SQLiteTable = SQLiteTable,
out TRunResult = unknown,
out TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
out TReturning = undefined,
out TDynamic extends boolean = false,
_TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
>
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Session } from "@opencode-ai/schema/session"
import type { SessionMessage } from "@opencode-ai/schema/session-message"
import type { Hooks, Transform } from "./registration.js"
interface ToolDraft {
export interface ToolDraft {
add<
Input extends Tool.ValueSchema<any>,
Output extends Tool.ValueSchema<any> | undefined,
-3
View File
@@ -124,9 +124,6 @@ const InfoBase = {
// on non-session owners anywhere else.
sessionID: Schema.String,
title: Schema.String,
coalesce: Schema.String.pipe(optional).annotate({
description: "Client-local key for displaying equivalent pending forms once and broadcasting one response.",
}),
metadata: Metadata.pipe(optional),
}
+1 -1
View File
@@ -7,7 +7,7 @@
"license": "MIT",
"scripts": {
"test": "bun test --timeout 30000 --only-failures",
"typecheck": "tsgo --noEmit"
"typecheck": "tsgo -b ../core/tsconfig.json && tsgo --noEmit"
},
"exports": {
".": "./src/index.tsx",
+2 -2
View File
@@ -93,13 +93,13 @@ export function Home() {
<box width="100%" flexShrink={0}>
<PluginSlot name="home.footer" input={{}} mode="replace" />
</box>
<Show when={forms()[0]?.coalesce ?? forms()[0]?.id} keyed>
<Show when={forms()[0]?.id} keyed>
{(_) => {
const form = forms()[0]
return form ? (
<box position="absolute" zIndex={2000} left={0} right={0} bottom={1} paddingLeft={2} paddingRight={2}>
<box width="100%">
<FormPrompt form={form} forms={forms()} />
<FormPrompt form={form} />
</box>
</box>
) : null
+41 -39
View File
@@ -42,7 +42,7 @@ function requestOptions(form: FormWithLocation) {
}
}
export function FormPrompt(props: { form: FormWithLocation; forms?: readonly FormWithLocation[] }) {
export function FormPrompt(props: { form: FormWithLocation }) {
const client = useClient()
const themes = useThemes()
const theme = useTheme("elevated")
@@ -69,11 +69,6 @@ export function FormPrompt(props: { form: FormWithLocation; forms?: readonly For
let textarea: TextareaRenderable | undefined
let review: ScrollBoxRenderable | undefined
const forms = createMemo(() => {
if (!props.form.coalesce) return [props.form]
return (props.forms ?? [props.form]).filter((form) => form.coalesce === props.form.coalesce)
})
const message = createMemo(() => {
const value = props.form.metadata?.["message"]
return typeof value === "string" ? value : undefined
@@ -185,30 +180,24 @@ export function FormPrompt(props: { form: FormWithLocation; forms?: readonly For
setStore("error", "")
}
function reply(answer: Record<string, FormValue>) {
Promise.all(
forms().map((form) =>
client.api.form.reply(
{
sessionID: form.sessionID,
formID: form.id,
answer,
},
requestOptions(form),
),
),
).catch((error: unknown) => {
setStore(
"error",
typeof error === "object" && error !== null && "message" in error && typeof error.message === "string"
? error.message
: "Invalid answer",
)
})
}
function replySingle(field: FormAnswerField, value: FormValue) {
reply({ [field.key]: value })
client.api.form
.reply(
{
sessionID: props.form.sessionID,
formID: props.form.id,
answer: { [field.key]: value },
},
requestOptions(props.form),
)
.catch((error: unknown) => {
setStore(
"error",
typeof error === "object" && error !== null && "message" in error && typeof error.message === "string"
? error.message
: "Invalid answer",
)
})
}
function pick(value: FormValue, customValue?: string) {
@@ -361,8 +350,7 @@ export function FormPrompt(props: { form: FormWithLocation; forms?: readonly For
}
function cancel() {
for (const form of forms())
void client.api.form.cancel({ sessionID: form.sessionID, formID: form.id }, requestOptions(form))
void client.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id }, requestOptions(props.form))
}
function openExternal() {
@@ -414,14 +402,28 @@ export function FormPrompt(props: { form: FormWithLocation; forms?: readonly For
setStore("error", formValidateValue(invalid, store.answers[invalid.key]) ?? "Invalid answer")
return
}
reply(
Object.fromEntries(
fields().flatMap((field) => {
const value = store.answers[field.key]
return value === undefined ? [] : [[field.key, value] as const]
}),
),
)
client.api.form
.reply(
{
sessionID: props.form.sessionID,
formID: props.form.id,
answer: Object.fromEntries(
fields().flatMap((field) => {
const value = store.answers[field.key]
return value === undefined ? [] : [[field.key, value] as const]
}),
),
},
requestOptions(props.form),
)
.catch((error: unknown) => {
setStore(
"error",
typeof error === "object" && error !== null && "message" in error && typeof error.message === "string"
? error.message
: "Invalid answer",
)
})
}
onMount(() => onCleanup(keymap.mode.push(FORM_MODE)))
+2 -2
View File
@@ -1026,10 +1026,10 @@ export function Session() {
</Show>
</Match>
<Match when={forms().length > 0}>
<Show when={forms()[0]?.coalesce ?? forms()[0]?.id} keyed>
<Show when={forms()[0]?.id} keyed>
{(_) => {
const form = forms()[0]
return form ? <FormPrompt form={form} forms={forms()} /> : null
return form ? <FormPrompt form={form} /> : null
}}
</Show>
</Match>
+3 -25
View File
@@ -15,7 +15,7 @@ import { TestTuiContexts } from "../../fixture/tui-environment"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
import { createApi, createEventStream, createFetch } from "../../fixture/tui-client"
async function mountForm(root: string, width = 80, coalesce = false) {
async function mountForm(root: string, width = 80) {
const state = path.join(root, "state")
await mkdir(state, { recursive: true })
@@ -24,7 +24,7 @@ async function mountForm(root: string, width = 80, coalesce = false) {
const events = createEventStream()
const transport = createFetch(
(url, request) =>
/^\/api\/session\/ses_test\/form\/frm_(?:test|other)\/reply$/.test(url.pathname)
url.pathname === "/api/session/ses_test/form/frm_test/reply"
? request.json().then((answer) => {
replies.push(answer)
return new Response(null, { status: 204 })
@@ -37,7 +37,6 @@ async function mountForm(root: string, width = 80, coalesce = false) {
id: "frm_test",
sessionID: "ses_test",
title: "Authorization required",
...(coalesce ? { coalesce: "authorization" } : {}),
fields: [
{
key: "authorization",
@@ -72,7 +71,7 @@ async function mountForm(root: string, width = 80, coalesce = false) {
<ClientProvider api={createApi(transport.fetch)}>
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({}) }}>
<ToastProvider>
<FormPrompt form={form} forms={coalesce ? [form, { ...form, id: "frm_other" }] : undefined} />
<FormPrompt form={form} />
</ToastProvider>
</ThemeProvider>
</ClientProvider>
@@ -127,24 +126,3 @@ test("includes external acknowledgements in progress", async () => {
prompt.app.renderer.destroy()
}
})
test("replies to every coalesced form", async () => {
await using tmp = await tmpdir()
const prompt = await mountForm(tmp.path, 80, true)
try {
prompt.app.mockInput.pressKey("right")
await prompt.app.waitForFrame((frame) => frame.includes("(acknowledgement required)"))
prompt.app.mockInput.pressEnter()
await prompt.app.waitForFrame((frame) => frame.includes("External action must be acknowledged"))
prompt.app.mockInput.pressKey("left")
prompt.app.mockInput.pressKey("c")
await prompt.app.waitForFrame((frame) => frame.includes("press enter to confirm"))
prompt.app.mockInput.pressEnter()
await prompt.app.waitForFrame((frame) => frame.includes("Acknowledged"))
prompt.app.mockInput.pressEnter()
await prompt.app.waitFor(() => prompt.replies.length === 2)
expect(prompt.replies).toEqual([{ answer: { authorization: true } }, { answer: { authorization: true } }])
} finally {
prompt.app.renderer.destroy()
}
})
+2 -1
View File
@@ -6,5 +6,6 @@
"jsxImportSource": "@opentui/solid",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"noUncheckedIndexedAccess": false
}
},
"references": [{ "path": "../core" }]
}
+3
View File
@@ -4,6 +4,9 @@
"globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"],
"tasks": {
"typecheck": {},
"@opencode-ai/tui#typecheck": {
"dependsOn": ["@opencode-ai/core#typecheck"]
},
"build": {
"dependsOn": [],
"outputs": ["dist/**"]