@markjaquith/agency 2.68.0 → 2.69.1

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.
@@ -238,7 +238,7 @@ pr: null
238
238
  {
239
239
  taskId: "example",
240
240
  claimant: "orchestrator",
241
- runner: "agent",
241
+ agent: "agent",
242
242
  sessionId: "session-1",
243
243
  revision: inspected.revision,
244
244
  expiresAt: "2099-01-01T00:00:00.000Z",
@@ -523,7 +523,7 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
523
523
  {
524
524
  taskId: "finished-claim",
525
525
  claimant: "orchestrator",
526
- runner: "agent",
526
+ agent: "agent",
527
527
  sessionId: "session-1",
528
528
  revision: initial.revision,
529
529
  },
@@ -575,6 +575,8 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
575
575
  })
576
576
 
577
577
  test("materializes missing workspaces but leaves branch conflicts unresolved", async () => {
578
+ await Bun.write(join(root, "bin", "gh"), "#!/bin/sh\nprintf '[]\\n'\n")
579
+ await chmod(join(root, "bin", "gh"), 0o755)
578
580
  for (const [id, branch] of [
579
581
  ["missing", "feat/missing"],
580
582
  ["conflict", "feat/conflict"],
@@ -649,6 +651,158 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
649
651
  ).toBe(false)
650
652
  })
651
653
 
654
+ test("reconciles a recorded merged PR without materializing an absent checkout", async () => {
655
+ await runTestEffect(
656
+ TaskService.pipe(
657
+ Effect.flatMap((service) =>
658
+ service.create(
659
+ {
660
+ id: "recorded-merged",
661
+ ticketUrl: null,
662
+ repo: "agency",
663
+ branch: "feat/example",
664
+ base: "main",
665
+ },
666
+ root,
667
+ ),
668
+ ),
669
+ ),
670
+ )
671
+ await git(
672
+ ["remote", "set-url", "origin", "git@github.com:example/agency.git"],
673
+ join(root, "repos/agency"),
674
+ )
675
+ await runTestEffect(
676
+ PullRequestService.pipe(
677
+ Effect.flatMap((service) =>
678
+ service.setUrl(
679
+ "recorded-merged",
680
+ undefined,
681
+ "https://github.com/example/agency/pull/42",
682
+ root,
683
+ ),
684
+ ),
685
+ ),
686
+ )
687
+
688
+ const applied = await runTestEffect(
689
+ SyncService.pipe(
690
+ Effect.flatMap((service) =>
691
+ service.reconcile({
692
+ cwd: root,
693
+ apply: true,
694
+ taskId: "recorded-merged",
695
+ }),
696
+ ),
697
+ ),
698
+ )
699
+ expect(applied.changes.map((change) => change.kind)).toEqual([
700
+ "record-pr",
701
+ "mark-done",
702
+ ])
703
+ expect(applied.executions[0]?.checkouts).toEqual([])
704
+ expect(
705
+ await Bun.file(join(root, "tasks/recorded-merged/code/agency")).exists(),
706
+ ).toBe(false)
707
+ })
708
+
709
+ test("reconciles a uniquely discovered merged PR without materializing", async () => {
710
+ await runTestEffect(
711
+ TaskService.pipe(
712
+ Effect.flatMap((service) =>
713
+ service.create(
714
+ {
715
+ id: "discovered-merged",
716
+ ticketUrl: null,
717
+ repo: "agency",
718
+ branch: "feat/example",
719
+ base: "main",
720
+ },
721
+ root,
722
+ ),
723
+ ),
724
+ ),
725
+ )
726
+ await git(
727
+ ["remote", "set-url", "origin", "git@github.com:example/agency.git"],
728
+ join(root, "repos/agency"),
729
+ )
730
+
731
+ const applied = await runTestEffect(
732
+ SyncService.pipe(
733
+ Effect.flatMap((service) =>
734
+ service.reconcile({
735
+ cwd: root,
736
+ apply: true,
737
+ taskId: "discovered-merged",
738
+ }),
739
+ ),
740
+ ),
741
+ )
742
+ expect(applied.changes.map((change) => change.kind)).toEqual([
743
+ "record-pr",
744
+ "mark-done",
745
+ ])
746
+ expect(applied.executions[0]?.checkouts).toEqual([])
747
+ expect(
748
+ await Bun.file(
749
+ join(root, "tasks/discovered-merged/code/agency"),
750
+ ).exists(),
751
+ ).toBe(false)
752
+ })
753
+
754
+ test("materializes when discovered PR evidence is ambiguous", async () => {
755
+ await runTestEffect(
756
+ TaskService.pipe(
757
+ Effect.flatMap((service) =>
758
+ service.create(
759
+ {
760
+ id: "ambiguous",
761
+ ticketUrl: null,
762
+ repo: "agency",
763
+ branch: "feat/example",
764
+ base: "main",
765
+ },
766
+ root,
767
+ ),
768
+ ),
769
+ ),
770
+ )
771
+ const gh = await Bun.file(join(root, "bin", "gh")).text()
772
+ await Bun.write(
773
+ join(root, "bin", "gh"),
774
+ gh
775
+ .replace('[{"number":42', '[{"number":42')
776
+ .replace(
777
+ "]\nJSON\n",
778
+ `,{\"number\":43,\"state\":\"MERGED\",\"title\":\"Ship again\",\"isDraft\":false,\"headRefName\":\"feat/example\",\"baseRefName\":\"main\",\"headRepository\":{\"nameWithOwner\":\"example/agency\"},\"url\":\"https://github.com/example/agency/pull/43\",\"mergedAt\":\"2100-01-01T00:00:00Z\",\"mergeCommit\":{\"oid\":\"def\"},\"mergeable\":\"MERGEABLE\"}]\nJSON\n`,
779
+ ),
780
+ )
781
+
782
+ const applied = await runTestEffect(
783
+ SyncService.pipe(
784
+ Effect.flatMap((service) =>
785
+ service.reconcile({
786
+ cwd: root,
787
+ apply: true,
788
+ taskId: "ambiguous",
789
+ }),
790
+ ),
791
+ ),
792
+ )
793
+ expect(applied.unresolved).toContainEqual(
794
+ expect.objectContaining({ kind: "multiple-prs" }),
795
+ )
796
+ expect(applied.changes).toContainEqual(
797
+ expect.objectContaining({ kind: "materialize-workspace" }),
798
+ )
799
+ expect(
800
+ await Bun.file(
801
+ join(root, "tasks/ambiguous/code/agency/README.md"),
802
+ ).text(),
803
+ ).toBe("example\n")
804
+ })
805
+
652
806
  test("leaves a missing checkout registration unresolved", async () => {
653
807
  await runTestEffect(
654
808
  TaskService.pipe(
@@ -162,6 +162,46 @@ const commandErrorSummary = (stderr: string, fallback: string) =>
162
162
  .map((line) => line.trim())
163
163
  .find(Boolean) ?? fallback
164
164
 
165
+ interface PullRequestQuery {
166
+ readonly remoteUrl: string | null
167
+ readonly remoteRepository: string
168
+ readonly result:
169
+ | {
170
+ readonly exitCode: number
171
+ readonly stdout: string
172
+ readonly stderr: string
173
+ }
174
+ | undefined
175
+ }
176
+
177
+ const mergedPullRequestFromGitHub = (
178
+ data: ExecutionData,
179
+ query: PullRequestQuery | undefined,
180
+ ) => {
181
+ if (!query?.result || query.result.exitCode !== 0) return null
182
+ const existing = data.pr ? normalizePullRequestRecord(data.pr) : null
183
+ const details = existing
184
+ ? [parseJson<Record<string, unknown>>(query.result.stdout, {})]
185
+ : parseJson<Record<string, unknown>[]>(query.result.stdout, []).filter(
186
+ (item) =>
187
+ item.headRefName === data.branch && item.baseRefName === data.base,
188
+ )
189
+ if (details.length !== 1) return null
190
+ const current = recordFromGitHubJson(details[0]!)
191
+ if (
192
+ current.merged !== true ||
193
+ current.headRepository?.toLowerCase() !==
194
+ query.remoteRepository.toLowerCase() ||
195
+ current.headBranch !== data.branch ||
196
+ current.baseRepository?.toLowerCase() !==
197
+ current.repository.toLowerCase() ||
198
+ current.baseBranch !== data.base
199
+ ) {
200
+ return null
201
+ }
202
+ return current
203
+ }
204
+
165
205
  export class SyncService extends Effect.Service<SyncService>()("SyncService", {
166
206
  sync: () => ({
167
207
  reconcile: (
@@ -463,6 +503,14 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
463
503
  let revision = record.revision
464
504
  const codePath = join(dirname(record.path), "code")
465
505
  const checkoutStates: CheckoutState[] = []
506
+ const query = prQueries.get(record.key)
507
+ const remoteMergedPr = config.delivery
508
+ ? null
509
+ : mergedPullRequestFromGitHub(data, query)
510
+ const skipCheckoutReconciliation =
511
+ remoteMergedPr !== null &&
512
+ data.claim?.state !== "active" &&
513
+ !data.completion
466
514
  let materialize = false
467
515
  let workspaceConflict = false
468
516
  const declared: readonly (
@@ -473,7 +521,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
473
521
  ...(data.repos ?? []),
474
522
  ]
475
523
 
476
- for (const checkout of declared) {
524
+ for (const checkout of skipCheckoutReconciliation ? [] : declared) {
477
525
  const repositoryPath = join(root, "repos", checkout.repo)
478
526
  const checkoutPath = join(codePath, checkout.repo)
479
527
  const kind = "branch" in checkout ? "writable" : "reference"
@@ -811,7 +859,11 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
811
859
  state: "none",
812
860
  }
813
861
  let prConflict = false
814
- const query = prQueries.get(record.key)!
862
+ if (!query) {
863
+ return yield* new SyncError({
864
+ message: `Missing pull request query for '${record.key}'`,
865
+ })
866
+ }
815
867
  const { remoteUrl, remoteRepository } = query
816
868
 
817
869
  if (config.delivery && !remoteUrl) {
@@ -288,7 +288,7 @@ describe("VcsMigrationService", () => {
288
288
  {
289
289
  taskId: "example",
290
290
  claimant: "orchestrator",
291
- runner: "agent",
291
+ agent: "agent",
292
292
  sessionId: "session-1",
293
293
  revision: inspected.revision,
294
294
  },
@@ -41,6 +41,37 @@ describe("WorkbaseService", () => {
41
41
  ).toBe(false)
42
42
  })
43
43
 
44
+ test("loads and validates the global agent", async () => {
45
+ const configDirectory = join(root, "config")
46
+ await write(
47
+ configDirectory,
48
+ "agency/agency.json",
49
+ JSON.stringify({ agent: "pi" }),
50
+ )
51
+
52
+ const config = await runTestEffect(
53
+ WorkbaseService.pipe(
54
+ Effect.flatMap((service) => service.loadGlobalConfig(configDirectory)),
55
+ ),
56
+ )
57
+ expect(config).toEqual({ agent: "pi" })
58
+
59
+ await write(
60
+ configDirectory,
61
+ "agency/agency.json",
62
+ JSON.stringify({ agent: "codex" }),
63
+ )
64
+ await expect(
65
+ runTestEffect(
66
+ WorkbaseService.pipe(
67
+ Effect.flatMap((service) =>
68
+ service.loadGlobalConfig(configDirectory),
69
+ ),
70
+ ),
71
+ ),
72
+ ).rejects.toThrow("Invalid global Agency configuration")
73
+ })
74
+
44
75
  test("treats declared but missing repositories as valid aliases", async () => {
45
76
  await write(
46
77
  root,
@@ -415,13 +446,13 @@ status: done
415
446
  ).rejects.toThrow("Repository 'agency'")
416
447
  })
417
448
 
418
- test("rejects an unknown runner command placeholder", async () => {
449
+ test("rejects an unknown agent command placeholder", async () => {
419
450
  await write(
420
451
  root,
421
452
  "agency.json",
422
453
  JSON.stringify({
423
454
  version: 2,
424
- runners: { custom: { command: ["agent", "{unknown}"] } },
455
+ agents: { custom: { command: ["agent", "{unknown}"] } },
425
456
  }),
426
457
  )
427
458
 
@@ -8,6 +8,7 @@ import { parseFrontmatter } from "../workbase/frontmatter"
8
8
  import {
9
9
  EntityId,
10
10
  EpicFrontmatter,
11
+ GlobalConfig,
11
12
  LegacyWorkbaseRegistry,
12
13
  PhaseFrontmatter,
13
14
  TaskFrontmatter,
@@ -22,7 +23,7 @@ import {
22
23
  import { validateWorktreeCreateCommand } from "../workbase/worktree-command"
23
24
  import { validateWorkspaceCreateCommand } from "../workbase/workspace-command"
24
25
  import { validatePostCheckoutCommand } from "../workbase/checkout-command"
25
- import { validateRunners } from "../workbase/runner-command"
26
+ import { validateAgents } from "../workbase/agent-command"
26
27
  import { findDependencyCycles } from "../workbase/dependency-graph"
27
28
  import { validateDelivery } from "../workbase/delivery-command"
28
29
  import { preferredVersionControl } from "../workbase/version-control"
@@ -90,6 +91,15 @@ const registryPath = (configDirectory?: string) =>
90
91
  "workbases.json",
91
92
  )
92
93
 
94
+ const globalConfigPath = (configDirectory?: string) =>
95
+ join(
96
+ configDirectory ||
97
+ process.env.XDG_CONFIG_HOME ||
98
+ join(homedir(), ".config"),
99
+ "agency",
100
+ "agency.json",
101
+ )
102
+
93
103
  const registrationId = (path: string) =>
94
104
  `wb-${new Bun.CryptoHasher("sha256").update(path).digest("hex").slice(0, 12)}`
95
105
 
@@ -178,6 +188,32 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
178
188
  "WorkbaseService",
179
189
  {
180
190
  sync: () => ({
191
+ loadGlobalConfig: (configDirectory?: string) =>
192
+ Effect.gen(function* () {
193
+ const fs = yield* FileSystemService
194
+ const path = globalConfigPath(configDirectory)
195
+ if (!(yield* fs.exists(path))) return {}
196
+ const content = yield* fs.readFile(path)
197
+ let input: unknown
198
+ try {
199
+ input = JSON.parse(content)
200
+ } catch (cause) {
201
+ return yield* new WorkbaseConfigError({
202
+ path,
203
+ message: `Invalid JSON in global Agency configuration ${path}`,
204
+ cause,
205
+ })
206
+ }
207
+ const decoded = decode(GlobalConfig, input)
208
+ if (!decoded.success) {
209
+ return yield* new WorkbaseConfigError({
210
+ path,
211
+ message: `Invalid global Agency configuration in ${path}:\n${decoded.error}`,
212
+ })
213
+ }
214
+ return decoded.value
215
+ }),
216
+
181
217
  initialize: (path: string = process.cwd()) =>
182
218
  Effect.gen(function* () {
183
219
  const fs = yield* FileSystemService
@@ -309,7 +345,7 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
309
345
  }
310
346
  }
311
347
  try {
312
- validateRunners(decoded.value.runners)
348
+ validateAgents(decoded.value.agents)
313
349
  validateDelivery(decoded.value.delivery)
314
350
  } catch (cause) {
315
351
  return yield* new WorkbaseConfigError({
@@ -317,7 +353,7 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
317
353
  message:
318
354
  cause instanceof Error
319
355
  ? cause.message
320
- : "Invalid runner configuration",
356
+ : "Invalid agent configuration",
321
357
  })
322
358
  }
323
359
  return current
@@ -121,7 +121,7 @@ Do not rediscover commands that this recipe or a known-current
121
121
  prepare the task with
122
122
  `agency work prepare <slug> --evidence <creation-json> --dry-run --json`, then
123
123
  execute the returned plan through `task-document-split` to prepare the
124
- checkout and open or reuse the background tab. Stop before `runner-start`.
124
+ checkout and open or reuse the background tab. Stop before `agent-start`.
125
125
  3. For work/launch/start/kickoff intent, run that same preflight and execute its
126
126
  ordered kickoff steps. The plan owns worktree dry-run/preparation, a
127
127
  retry-safe background Herdr tab, the side-by-side task document,
@@ -131,8 +131,8 @@ Do not rediscover commands that this recipe or a known-current
131
131
  `agency-kickoff-v1` capability, execute the supplied actions directly. Do not
132
132
  call Herdr help, skill, or CLI discovery. If capability/version evidence is
133
133
  absent or stale, discovery is the compatibility path; then resume the same
134
- idempotency key rather than creating another tab, checkout, or runner.
135
- 5. After the one final context verification succeeds, leave the runner in the
134
+ idempotency key rather than creating another tab, checkout, or agent.
135
+ 5. After the one final context verification succeeds, leave the agent in the
136
136
  background and stop. Do not inspect, poll, or babysit it unless the user asks.
137
137
 
138
138
  Validation evidence is a local, auditable optimization, not authority. Preflight
@@ -153,12 +153,12 @@ IDs and remediation commands before retrying.
153
153
 
154
154
  `agency work` is the human launch flow: it reconciles managed integration,
155
155
  selects work, checks readiness, prepares checkouts, marks execution work
156
- `working` without creating a claim, and starts the runner. Epic and multi-phase
156
+ `working` without creating a claim, and starts the agent. Epic and multi-phase
157
157
  task launches remain orchestration-only. External orchestrators instead claim
158
- an execution unit, launch and monitor their runner separately, and finish or
158
+ an execution unit, launch and monitor their agent separately, and finish or
159
159
  release the claim with the current document revision.
160
160
 
161
- An Agency-launched runner receives process-local worker identity through both
161
+ An Agency-launched agent receives process-local worker identity through both
162
162
  the `AGENCY_SESSION_ID` and `AGENCY_TARGET` environment variables and a generated
163
163
  prompt beginning `Agency worker launch target: <target>.` Treat either form as
164
164
  launch evidence only after `agency context . --json` confirms the same target,
@@ -166,7 +166,7 @@ document paths, valid context, and expected write authority. Once confirmed,
166
166
  perform the assigned work directly and never invoke `agency work` to start the
167
167
  same target again.
168
168
 
169
- Some runner clients attach to a long-lived process and may not preserve launch
169
+ Some agent clients attach to a long-lived process and may not preserve launch
170
170
  environment variables. If the variables and prompt marker are absent, fail safe
171
171
  when the initial instruction is a generated `Start`, `Continue`, or `Work on`
172
172
  prompt whose absolute document paths match the current directory and the active,
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test"
2
2
  import {
3
3
  printableEnvironment,
4
- resolveRunnerCommand,
5
- runnerEnvironment,
6
- validateRunners,
7
- } from "./runner-command"
4
+ resolveAgentCommand,
5
+ agentEnvironment,
6
+ validateAgents,
7
+ } from "./agent-command"
8
8
 
9
9
  const variables = {
10
10
  prompt: "Read the task.",
@@ -17,45 +17,55 @@ const variables = {
17
17
  claimRevision: "revision-1",
18
18
  }
19
19
 
20
- describe("runner commands", () => {
20
+ describe("agent commands", () => {
21
21
  test("uses promptless interactive commands for built-in presets", () => {
22
22
  expect(
23
- resolveRunnerCommand("opencode2", undefined, variables, false).argv,
23
+ resolveAgentCommand("opencode2", undefined, variables, false).argv,
24
24
  ).toEqual(["opencode2"])
25
25
  expect(
26
- resolveRunnerCommand("opencode2", undefined, variables, true).argv,
26
+ resolveAgentCommand("opencode2", undefined, variables, true).argv,
27
27
  ).toEqual(["opencode2", "--continue"])
28
28
  expect(
29
- resolveRunnerCommand("opencode", undefined, variables, false).argv,
29
+ resolveAgentCommand("opencode", undefined, variables, false).argv,
30
30
  ).toEqual(["opencode"])
31
31
  expect(
32
- resolveRunnerCommand("opencode", undefined, variables, true).argv,
32
+ resolveAgentCommand("opencode", undefined, variables, true).argv,
33
33
  ).toEqual(["opencode", "--continue"])
34
+ expect(resolveAgentCommand("pi", undefined, variables, false).argv).toEqual(
35
+ ["pi"],
36
+ )
37
+ expect(resolveAgentCommand("pi", undefined, variables, true).argv).toEqual([
38
+ "pi",
39
+ "--continue",
40
+ ])
34
41
  expect(
35
- resolveRunnerCommand("claude", undefined, variables, true).argv,
42
+ resolveAgentCommand("claude", undefined, variables, true).argv,
36
43
  ).toEqual(["claude", "--continue"])
37
44
  })
38
45
 
39
46
  test("uses autonomous commands when a prompt is requested", () => {
40
47
  expect(
41
- resolveRunnerCommand("opencode2", undefined, variables, false, true).argv,
48
+ resolveAgentCommand("opencode2", undefined, variables, false, true).argv,
42
49
  ).toEqual(["opencode2", "--prompt", "Read the task."])
43
50
  expect(
44
- resolveRunnerCommand("opencode2", undefined, variables, true, true).argv,
51
+ resolveAgentCommand("opencode2", undefined, variables, true, true).argv,
45
52
  ).toEqual(["opencode2", "--continue", "--prompt", "Read the task."])
46
53
  expect(
47
- resolveRunnerCommand("opencode", undefined, variables, false, true).argv,
54
+ resolveAgentCommand("opencode", undefined, variables, false, true).argv,
48
55
  ).toEqual(["opencode", "--prompt", "Read the task."])
49
56
  expect(
50
- resolveRunnerCommand("opencode", undefined, variables, true, true).argv,
57
+ resolveAgentCommand("opencode", undefined, variables, true, true).argv,
51
58
  ).toEqual(["opencode", "--continue", "--prompt", "Read the task."])
52
59
  expect(
53
- resolveRunnerCommand("claude", undefined, variables, true, true).argv,
60
+ resolveAgentCommand("pi", undefined, variables, false, true).argv,
61
+ ).toEqual(["pi", "Read the task."])
62
+ expect(
63
+ resolveAgentCommand("claude", undefined, variables, true, true).argv,
54
64
  ).toEqual(["claude", "--continue", "Read the task."])
55
65
  })
56
66
 
57
67
  test("expands configured argv and environment without a shell", () => {
58
- const resolved = resolveRunnerCommand(
68
+ const resolved = resolveAgentCommand(
59
69
  "custom",
60
70
  {
61
71
  custom: {
@@ -79,33 +89,33 @@ describe("runner commands", () => {
79
89
  })
80
90
  })
81
91
 
82
- test("rejects --auto for configured runners without an auto command", () => {
92
+ test("rejects --auto for configured agents without an auto command", () => {
83
93
  expect(() =>
84
- resolveRunnerCommand(
94
+ resolveAgentCommand(
85
95
  "custom",
86
96
  { custom: { command: ["agent"] } },
87
97
  variables,
88
98
  false,
89
99
  true,
90
100
  ),
91
- ).toThrow("Runner 'custom' does not support --auto")
101
+ ).toThrow("Agent 'custom' does not support --auto")
92
102
  })
93
103
 
94
104
  test("rejects unknown placeholders", () => {
95
105
  expect(() =>
96
- validateRunners({ custom: { command: ["agent", "{unknown}"] } }),
97
- ).toThrow("Unknown runner 'custom' placeholder: {unknown}")
106
+ validateAgents({ custom: { command: ["agent", "{unknown}"] } }),
107
+ ).toThrow("Unknown agent 'custom' placeholder: {unknown}")
98
108
  })
99
109
 
100
110
  test("provides normalized Agency environment and filters secret values", () => {
101
111
  const environment = {
102
- ...runnerEnvironment("custom", variables),
112
+ ...agentEnvironment("custom", variables),
103
113
  VISIBLE: "yes",
104
114
  ACCESS_TOKEN: "secret",
105
115
  }
106
116
 
107
117
  expect(environment).toMatchObject({
108
- AGENCY_RUNNER: "custom",
118
+ AGENCY_AGENT: "custom",
109
119
  AGENCY_CLAIMANT: "orchestrator",
110
120
  AGENCY_SESSION_ID: "session-1",
111
121
  AGENCY_WORKBASE: "/workbase",